Sun is on my face ...a beautiful day without you.
•
• be apart
• more quotes

very clickable

π day
·
ASCII
·
choices
·
clocks
·
color
·
constellations
^{NEW}
·
covers
^{NEW}
·
deadly genomes
^{NEW}
·
debates
·
emotions
·
famous rat
·
gigapixel skies
^{NEW}
·
hitchens
·
keyboards
·
languages
·
LOTRO
·
photography
·
questions
·
quotes
·
road trips
·
rockets
·
satire
·
spam poetry
·
time
·
tripsum
·
type
·
unwords
·
voids
·
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

*Huge empty areas of the universe called voids could help solve the greatest mysteries in the cosmos.*

My graphic accompanying How Analyzing Cosmic Nothing Might Explain Everything in the January 2024 issue of Scientific American depicts the entire Universe in a two-page spread — full of nothing.

The graphic uses the latest data from SDSS 12 and is an update to my Superclusters and Voids poster.

Michael Lemonick (editor) explains on the graphic:

“Regions of relatively empty space called cosmic voids are everywhere in the universe, and scientists believe studying their size, shape and spread across the cosmos could help them understand dark matter, dark energy and other big mysteries.

To use voids in this way, astronomers must map these regions in detail—a project that is just beginning.

Shown here are voids discovered by the Sloan Digital Sky Survey (SDSS), along with a selection of 16 previously named voids. Scientists expect voids to be evenly distributed throughout space—the lack of voids in some regions on the globe simply reﬂects SDSS’s sky coverage.”

Sofia Contarini, Alice Pisani, Nico Hamaus, Federico Marulli Lauro Moscardini & Marco Baldi (2023) Cosmological Constraints from the BOSS DR12 Void Size Function *Astrophysical Journal* **953**:46.

Nico Hamaus, Alice Pisani, Jin-Ah Choi, Guilhem Lavaux, Benjamin D. Wandelt & Jochen Weller (2020) Journal of Cosmology and Astroparticle Physics **2020**:023.

Sloan Digital Sky Survey Data Release 12

Alan MacRobert (Sky & Telescope), Paulina Rowicka/Martin Krzywinski (revisions & Microscopium)

Hoffleit & Warren Jr. (1991) The Bright Star Catalog, 5th Revised Edition (Preliminary Version).

*H*_{0} = 67.4 km/(Mpc·s), *Ω*_{m} = 0.315, *Ω*_{v} = 0.685. Planck collaboration Planck 2018 results. VI. Cosmological parameters (2018).

*It is the mark of an educated mind to rest satisfied with the degree of precision that the nature of the subject admits and not to seek exactness where only an approximation is possible. —Aristotle*

In regression, the predictors are (typically) assumed to have known values that are measured without error.

Practically, however, predictors are often measured with error. This has a profound (but predictable) effect on the estimates of relationships among variables – the so-called “error in variables” problem.

Error in measuring the predictors is often ignored. In this column, we discuss when ignoring this error is harmless and when it can lead to large bias that can leads us to miss important effects.

Altman, N. & Krzywinski, M. (2024) Points of significance: Error in predictor variables. *Nat. Methods* **20**.

Altman, N. & Krzywinski, M. (2015) Points of significance: Simple linear regression. *Nat. Methods* **12**:999–1000.

Lever, J., Krzywinski, M. & Altman, N. (2016) Points of significance: Logistic regression. *Nat. Methods* **13**:541–542 (2016).

Das, K., Krzywinski, M. & Altman, N. (2019) Points of significance: Quantile regression. *Nat. Methods* **16**:451–452.

*Nature uses only the longest threads to weave her patterns, so that each small piece of her fabric reveals the organization of the entire tapestry. – Richard Feynman*

Following up on our Neural network primer column, this month we explore a different kind of network architecture: a convolutional network.

The convolutional network replaces the hidden layer of a fully connected network (FCN) with one or more filters (a kind of neuron that looks at the input within a narrow window).

Even through convolutional networks have far fewer neurons that an FCN, they can perform substantially better for certain kinds of problems, such as sequence motif detection.

Derry, A., Krzywinski, M & Altman, N. (2023) Points of significance: Convolutional neural networks. *Nature Methods* **20**:1269–1270.

Derry, A., Krzywinski, M. & Altman, N. (2023) Points of significance: Neural network primer. Nature Methods **20**:165–167.

Lever, J., Krzywinski, M. & Altman, N. (2016) Points of significance: Logistic regression. Nature Methods **13**:541–542.

*Nature is often hidden, sometimes overcome, seldom extinguished. —Francis Bacon*

In the first of a series of columns about neural networks, we introduce them with an intuitive approach that draws from our discussion about logistic regression.

Simple neural networks are just a chain of linear regressions. And, although neural network models can get very complicated, their essence can be understood in terms of relatively basic principles.

We show how neural network components (neurons) can be arranged in the network and discuss the ideas of hidden layers. Using a simple data set we show how even a 3-neuron neural network can already model relatively complicated data patterns.

Derry, A., Krzywinski, M & Altman, N. (2023) Points of significance: Neural network primer. *Nature Methods* **20**:165–167.

Lever, J., Krzywinski, M. & Altman, N. (2016) Points of significance: Logistic regression. Nature Methods **13**:541–542.

Our cover on the 11 January 2023 Cell Genomics issue depicts the process of determining the parent-of-origin using differential methylation of alleles at imprinted regions (iDMRs) is imagined as a circuit.

Designed in collaboration with with Carlos Urzua.

Akbari, V. *et al.* Parent-of-origin detection and chromosome-scale haplotyping using long-read DNA methylation sequencing and Strand-seq (2023) Cell Genomics 3(1).

Browse my gallery of cover designs.

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

{ 10.9.234.151 }