Color vision deficiency emulation fixed in colorspace 2.1-0

The color vision deficiency emulation provided by R package colorspace was inaccurate for some highly-saturated colors due to a bug that was fixed in version 2.1-0. The (typically small) differences are illustrated for a range of palettes.

Background

Functions for emulating color vision deficiencies have been part of the R package colorspace for several years now (since the release of version 1.4-0 in January 2019). They are crucial for assessing how well data visualizations work for viewers affected by color vision deficiencies (about 8% of all males and 0.5% of all females) and for illustrating problems with poor color choices.

The colorspace package implements the physiologically-based model of Machado, Oliveira, and Fernandes (2009) who provide a unified approach to various forms of deficiencies, in particular encompassing deuteranomaly (green cone cells defective), protanomaly (red cone cells defective), and tritanomaly (blue cone cells defective). See the corresponding package vignette for more details.

Bug and fix

Recently, an inaccuracy in the colorspace implementation of the Machado et al. method was reported by Matthew Petroff and fixed in colorspace 2.1.0 (released earlier this year) with some advice and guidance from Kenneth Knoblauch.

More specifically, Machado et al. provide linear transformations of RGB (red-green-blue) coordinates that simulate the different color vision deficiencies. Following some illustrations from the supplementary materials of Machado et al., earlier versions of the colorspace package had applied the transformations directly to gamma-corrected sRGB coordinates that can be obtained from color hex codes. However, the paper implicitly relies on a linear RGB space (see page 1294, column 1) where the linear matrix transformations for simulating color vision deficiencies should be applied. Therefore, a new argument linear = TRUE has been added to simulate_cvd() (and hence in deutan(), protan(), and tritan()) that first maps the provided colors to linearized RGB coordinates, applies the color vision deficiency transformation, and then maps back to gamma-corrected sRGB coordinates. Optionally, linear = FALSE can be used to restore the behavior from previous versions where the transformations are applied directly to the sRGB coordinates.

Illustration

For most colors the difference between the two strategies (in linear vs. gamma-corrected RGB coordinates) is negligible but for some highly-saturated colors it becomes more noticeable, e.g., for red, purple, or orange.

To illustrate this we set up a small convenience function cvd_compare() that contrasts both approaches for all three types of color vision deficiences using the swatchplot() function from colorspace.

cvd_compare <- function(pal) {
  x <- list(
    "Original" = rbind(pal),
    "Deutan" = rbind(
      "linear = TRUE " = colorspace::deutan(pal, linear = TRUE),
      "linear = FALSE" = colorspace::deutan(pal, linear = FALSE)
    ),
    "Protan" = rbind(
      "linear = TRUE " = colorspace::protan(pal, linear = TRUE),
      "linear = FALSE" = colorspace::protan(pal, linear = FALSE)
    ),
    "Tritan" = rbind(
      "linear = TRUE " = colorspace::tritan(pal, linear = TRUE),
      "linear = FALSE" = colorspace::tritan(pal, linear = FALSE)
    )
  )
  rownames(x$Original) <- deparse(substitute(pal))
  colorspace::swatchplot(x)
}

Subsequently, we apply this function to a selection of new base R palettes, that have been available since R 4.0.0 in functions palette.colors() and hcl.colors(). First, it is shown that for many palettes the two strategies lead to almost equivalent output: e.g., for the default qualitative palette in palette.colors(), Okabe-Ito (excluding black and gray), and the default sequential palette in hcl.colors(), Viridis.

cvd_compare(palette.colors()[2:8])
cvd_compare(hcl.colors(7))

Comparison of color vision deficiency emulations for Okabe-Ito palette Comparison of color vision deficiency emulations for Viridis palette

The comparison shows that both emulations lead to very similar output, bringing out clearly that both palettes are rather robust und color vision deficiencies.

However, for palettes with more flashy colors (especially highly-saturated red, purple, or orange) the differences may be noticeable and practically relevant. This is illustrated using two sequential HCL palettes, PuRd (inspired from ColorBrewer.org) and Rocket (from the Viridis family):

cvd_compare(hcl.colors(7, "PuRd"))
cvd_compare(hcl.colors(7, "Rocket"))

Comparison of color vision deficiency emulations for PuRd palette Comparison of color vision deficiency emulations for Rocket palette

The comparison shows that the emulation differs in particular for colors 2, 3, and 4 in both palettes, leading to slightly different insights regarding the properties of the palettes.

The differences can become even more pronounced for fully-satured colors like those in the infamous rainbow palette, shown below.

cvd_compare(rainbow(7))

Comparison of color vision deficiency emulations for rainbow palette

Luckily for palettes with better perceptual properties the differences between the old erroneous version and the new fixed one are typically rather small. Hence, we hope that the bug did not affect prior work too much and that the fixed version is even more useful for all users of the package.