Drive, driven. Gave, given.
•
• Give me a number of games.
• more quotes

very clickable

π day
·
ASCII
·
choices
·
clocks
·
color
·
covers
^{NEW}
·
deadly genomes
^{NEW}
·
debates
·
famous rat
·
gigapixel skies
^{NEW}
·
hitchens
·
huge voids
·
keyboards
·
languages
·
LOTRO
·
photography
·
questions
·
quotes
·
road trips
·
rockets
·
satire
·
sky constellations
^{NEW}
·
spam poetry
·
time
·
tripsum
·
type
·
unwords
·
words
·
writing
·
zaomm

visualization
**+** design

Here, I help you understand color blindness and describe a process by which you can make good color choices when designing for accessibility.

The opposite of color blindness is seeing all the colors and I can help you find 1,000 (or more) maximally distinct colors.

You can also delve into the mathematics behind the color blindness simulations and learn about copunctal points (the invisible color!) and lines of confusion.

R code for converting an RGB color for color blindness. For details see the math tab and the resources section for background reading.

--- title: 'RGB color correction for color blindess: protanopia, deuteranopia, tritanopia' author: 'Martin Krzywinski' web: http://mkweb.bcgsc.ca/colorblind --- ```{r} gamma = 2.4 ############################################### # Linear RGB to XYZ # https://en.wikipedia.org/wiki/SRGB XYZ = matrix(c(0.4124564, 0.3575761, 0.1804375, 0.2126729, 0.7151522, 0.0721750, 0.0193339, 0.1191920, 0.9503041), byrow=TRUE,nrow=3) SA = matrix(c(0.2126,0.7152,0.0722, 0.2126,0.7152,0.0722, 0.2126,0.7152,0.0722),byrow=TRUE,nrow=3) ############################################### # XYZ to LMS, normalized to D65 # https://en.wikipedia.org/wiki/LMS_color_space # Hunt, Normalized to D65 LMSD65 = matrix(c( 0.4002, 0.7076, -0.0808, -0.2263, 1.1653, 0.0457, 0 , 0 , 0.9182), byrow=TRUE,nrow=3) # Hunt, equal-energy illuminants LMSEQ = matrix(c( 0.38971, 0.68898,-0.07868, -0.22981, 1.18340, 0.04641, 0 , 0 , 1 ), byrow=TRUE,nrow=3) # CIECAM97 SMSCAM97 = matrix(c( 0.8951, 0.2664, -0.1614, -0.7502, 1.7135, 0.0367, 0.0389, -0.0685, 1.0296), byrow=TRUE,nrow=3) # CIECAM02 LMSCAM02 = matrix(c( 0.7328, 0.4296, -0.1624, -0.7036, 1.6975, 0.0061, 0.0030, 0.0136, 0.9834), byrow=TRUE,nrow=3) ############################################### # Determine the color blindness correction in LMS space # under the condition that the correction does not # alter the appearance of white as well as # blue (for protanopia/deuteranopia) or red (for tritanopia). # For achromatopsia, greyscale conversion is applied # to the linear RGB values. getcorrection = function(LMS,type="p",g=gamma) { red = matrix(c(255,0,0),nrow=3) blue = matrix(c(0,0,255),nrow=3) white = matrix(c(255,255,255),nrow=3) LMSr = LMS %*% XYZ %*% apply(red,1:2,linearize,g) LMSb = LMS %*% XYZ %*% apply(blue,1:2,linearize,g) LMSw = LMS %*% XYZ %*% apply(white,1:2,linearize,g) if(type == "p") { x = matrix(c(LMSb[2,1],LMSb[3,1], LMSw[2,1],LMSw[3,1]),byrow=T,nrow=2) y = matrix(c(LMSb[1,1],LMSw[1,1]),nrow=2) ab = solve(x) %*% y C = matrix(c(0,ab[1,1],ab[2,1],0,1,0,0,0,1),byrow=T,nrow=3) } else if (type == "d") { x = matrix(c(LMSb[1,1],LMSb[3,1], LMSw[1,1],LMSw[3,1]),byrow=T,nrow=2) y = matrix(c(LMSb[2,1],LMSw[2,1]),nrow=2) ab = solve(x) %*% y C = matrix(c(1,0,0,ab[1,1],0,ab[2,1],0,0,1),byrow=T,nrow=3) } else if (type == "t") { x = matrix(c(LMSr[1,1],LMSr[2,1], LMSw[1,1],LMSw[2,1]),byrow=T,nrow=2) y = matrix(c(LMSr[3,1],LMSw[3,1]),nrow=2) ab = solve(x) %*% y C = matrix(c(1,0,0,0,1,0,ab[1,1],ab[2,1],0),byrow=T,nrow=3) } else if (type == "a" | type == "g") { C = matrix(c(0.2126,0.7152,0.0722, 0.2126,0.7152,0.0722, 0.2126,0.7152,0.0722),byrow=TRUE,nrow=3) } return(C) } # rgb is a column vector convertcolor = function(rgb,LMS=LMSD65,type="d",g=gamma) { C = getcorrection(LMS,type) if(type == "a" | type == "g") { T = SA } else { M = LMS %*% XYZ Minv = solve(M) T = Minv %*% C %*% M } print(T) rgb_converted = T %*% apply(rgb,1:2,linearize,g) return(apply(rgb_converted,1:2,delinearize,g)) } # This function implements the method by Vienot, Brettel, Mollon 1999. # The approach is the same, just the values are different. # http://vision.psychol.cam.ac.uk/jdmollon/papers/colourmaps.pdf convertcolor2 = function(rgb,type="d",g=2.2) { xyz = matrix(c(40.9568, 35.5041, 17.9167, 21.3389, 70.6743, 7.98680, 1.86297, 11.4620, 91.2367),byrow=T,nrow=3) lms = matrix(c(0.15514, 0.54312, -0.03286, -0.15514, 0.45684,0.03286, 0,0,0.01608),byrow=T,nrow=3) rgb = (rgb/255)**g if(type=="p") { S = matrix(c(0,2.02344,-2.52581,0,1,0,0,0,1),byrow=T,nrow=3) rgb = 0.992052*rgb+0.003974 } else if(type=="d") { S = matrix(c(1,0,0,0.494207,0,1.24827,0,0,1),byrow=T,nrow=3) rgb = 0.957237*rgb+0.0213814 } else { stop("Only type p,d defined for this function.") } M = lms %*% xyz T = solve(M) %*% S %*% M print(T) rgb = T %*% rgb rgb = 255*rgb**(1/g) return(rgb) } ############################################### # RGB to Lab rgb2lab = function(rgb,g=gamma) { rgb = apply(rgb,1:2,linearize,g) xyz = XYZ %*% rgb delta = 6/29 xyz = xyz / (c(95.0489,100,108.8840)/100) f = function(t) { if(t > delta**3) { return(t**(1/3)) } else { return (t/(3*delta**2) + 4/29) } } L = 116*f(xyz[2]) - 16 a = 500*(f(xyz[1]) - f(xyz[2])) b = 200*(f(xyz[2]) - f(xyz[3])) return(matrix(c(L,a,b),nrow=3)) } # CIE76 (https://en.wikipedia.org/wiki/Color_difference) deltaE = function(rgb1,rgb2) { lab1 = rgb2lab(rgb1) lab2 = rgb2lab(rgb2) return(sqrt(sum((lab1-lab2)**2))) } clip = function(v) { return(max(min(v,1),0)) } ############################################### # RGB to/from linear RGB #https://en.wikipedia.org/wiki/SRGB linearize = function(v,g=gamma) { if(v <= 0.04045) { return(v/255/12.92) } else { return(((v/255 + 0.055)/1.055)**g) } } delinearize = function(v,g=gamma) { if(v <= 0.003130805) { return(255*12.92*clip(v)) } else { return(255*clip(1.055*(clip(v)**(1/g))-0.055)) } } pretty = function(x) { noquote(formatC(x,digits=10,format="f",width=9)) } # a dark red rgb1 = matrix(c(0,209,253),nrow=3) # dark green rgb2 = matrix(c(60,135,0),nrow=3) # simulate deuteranopia convertcolor(rgb1,type="d") convertcolor(rgb2,type="d") # get color distance before and after simulation deltaE(rgb1,rgb2) deltaE(convertcolor(rgb1,type="d"),convertcolor(rgb2,type="d")) # transformation matrices for each color blindness type M = LMSD65 %*% XYZ pretty(solve(M) %*% getcorrection(LMSD65,"p") %*% M) pretty(solve(M) %*% getcorrection(LMSD65,"d") %*% M) pretty(solve(M) %*% getcorrection(LMSD65,"t") %*% M) pretty(SA) # method by Vienot, Brettel, Mollon, 1999 convertcolor2(rgb1,type="d",g=2.2) convertcolor2(rgb2,type="d",g=2.2) ```

# a dark red rgb1 = matrix(c(225,0,30),nrow=3) # dark green rgb2 = matrix(c(60,135,0),nrow=3) # simulate deuteranopia convertcolor(rgb1,type="d") [,1] [1,] 136.7002 [2,] 136.7002 [3,] 0.0000 convertcolor(rgb2,type="d") [,1] [1,] 116.76071 [2,] 116.76071 [3,] 16.73263 # get color distance before and after simulation deltaE(rgb1,rgb2) [1] 116.9496 deltaE(convertcolor(rgb1,type="d"),convertcolor(rgb2,type="d")) [1] 12.72204 # transformation matrices for each color blindness type M = LMSD65 %*% XYZ pretty(solve(M) %*% getcorrection(LMSD65,"p") %*% M) [,1] [,2] [,3] [1,] 0.1705569911 0.8294430089 0.0000000000 [2,] 0.1705569911 0.8294430089 -0.0000000000 [3,] -0.0045171442 0.0045171442 1.0000000000 pretty(solve(M) %*% getcorrection(LMSD65,"d") %*% M) [,1] [,2] [,3] [1,] 0.3306600735 0.6693399265 -0.0000000000 [2,] 0.3306600735 0.6693399265 0.0000000000 [3,] -0.0278553826 0.0278553826 1.0000000000 pretty(solve(M) %*% getcorrection(LMSD65,"t") %*% M) [,1] [,2] [,3] [1,] 1.0000000000 0.1273988634 -0.1273988634 [2,] -0.0000000000 0.8739092990 0.1260907010 [3,] 0.0000000000 0.8739092990 0.1260907010 pretty(SA) [,1] [,2] [,3] [1,] 0.2126000000 0.7152000000 0.0722000000 [2,] 0.2126000000 0.7152000000 0.0722000000 [3,] 0.2126000000 0.7152000000 0.0722000000 # method by Vienot, Brettel, Mollon, 1999 convertcolor2(rgb1,type="d",g=2.2) [,1] [,2] [,3] [1,] 0.29275003 0.70724967 -2.978356e-08 [2,] 0.29275015 0.70724997 1.232823e-08 [3,] -0.02233659 0.02233658 1.000000e+00 [,1] [1,] 131.81223 [2,] 131.81226 [3,] 36.37274 convertcolor2(rgb2,type="d",g=2.2) [,1] [,2] [,3] [1,] 0.29275003 0.70724967 -2.978356e-08 [2,] 0.29275015 0.70724997 1.232823e-08 [3,] -0.02233659 0.02233658 1.000000e+00 [,1] [1,] 122.71798 [2,] 122.71801 [3,] 48.34316

news
**+** thoughts

My cover design on the 1 September 2022 Annals of Oncology issue shows 570 individual cases of difficult-to-treat cancers. Each case shows the number and type of actionable genomic alterations that were detected and the length of therapies that resulted from the analysis.

Pleasance E *et al.* Whole-genome and transcriptome analysis enhances precision cancer treatment options (2022) *Annals of Oncology* **33**:939–949.

Browse my gallery of cover designs.

*Love's the only engine of survival. —L. Cohen*

We begin a series on survival analysis in the context of its two key complications: skew (which calls for the use of probability distributions, such as the Weibull, that can accomodate skew) and censoring (required because we almost always fail to observe the event in question for all subjects).

We discuss right, left and interval censoring and how mishandling censoring can lead to bias and loss of sensitivity in tests that probe for differences in survival times.

Dey, T., Lipsitz, S.R., Cooper, Z., Trinh, Q., Krzywinski, M & Altman, N. (2022) Points of significance: Survival analysis—time-to-event data and censoring. *Nature Methods* **19**:906–908.

*See How Scientists Put Together the Complete Human Genome.*

My graphic in Scientific American's Graphic Science section in the August 2022 issue shows the full history of the human genome assembly — from its humble shotgun beginnings to the gapless telomere-to-telomere assembly.

Read about the process and methods behind the creation of the graphic.

See all my Scientific American Graphic Science visualizations.

My poster showing the genome structure and position of mutations on all SARS-CoV-2 variants appears in the March/April 2022 issue of American Scientist.

An accompanying piece breaks down the anatomy of each genome — by gene and ORF, oriented to emphasize relative differences that are caused by mutations.

My cover design on the 11 April 2022 Cancer Cell issue depicts depicts cellular heterogeneity as a kaleidoscope generated from immunofluorescence staining of the glial and neuronal markers MBP and NeuN (respectively) in a GBM patient-derived explant.

LeBlanc VG *et al.* Single-cell landscapes of primary glioblastomas and matched explants and cell lines show variable retention of inter- and intratumor heterogeneity (2022) *Cancer Cell* **40**:379–392.E9.

Browse my gallery of cover designs.

© 1999–2022 Martin Krzywinski | contact | Canada's Michael Smith Genome Sciences Centre ⊂ BC Cancer Research Center ⊂ BC Cancer ⊂ PHSA