# munsellinterpol User Guide

#### Glenn Davis and Jose Gama

#### 2026-07-29

munsellinterpol is an R package that transforms from Munsell HVC to CIE xyY, and back again. Our package was inspired by Paul Centore’s An Open-Source Inversion Algorithm for the Munsell Renotation [1].

The goals of this package are:

1. in the forward conversion HVC \(\to\) xyY, if HVC is in the published tables ([13] and [8]) then the output xyY must match the tables
2. accurate round-trips HVC \(\to\) xyY \(\to\) HVC, and xyY \(\to\) HVC\(\to\) xyY
3. conversion for all colors within the MacAdam limits, including Munsell Values < 1
4. both conversions HVC \(\leftrightarrow\) xyY are class \(C^1\) (continuously differentiable), except at the neutrals where \(C^0\) (continuous) is acceptable

Goals 1, 2, and 3 have been met. This means that extensive tests were passed, with many color samples. There were initial problems with very dark samples where V \(<\) 1, so it is possible there could be some other dark samples that were missed, and where goal 3 fails.

In attempting to meet goal 4, the forward map HVC \(\to\) xyY uses Catmull-Rom spline interpolation in V (by default). But because of a change in Value spacing, the function HVC \(\to\) xyY is not \(C^1\) on the plane V=1. When V \(\ge\) 1 the Value spacing is 1, but when V \(\le\) 1 the Value spacing is 0.2. So goal 4 has not been met. For H and C bicubic interpolation is used, again with Catmull-Rom splines; see [29] and [28]. There are options to use simpler linear interpolation for V, and bilinear interpolation for H and C. These are useful for comparison with other algorithms, such as [18]. Of course, to meet goal 2 the options for forward and inverse maps must be the same.

Note that goal 1 does not include points from `all.dat`. This goal is actually met for V \(\ge\) 1, but for V \(<\) 1 we had to ignore the points from `all.dat` and re-extrapolate to make inversion work reliably. In general, in this package the xy for these non-real points is further from white than the corresponding points in `all.dat`. For the meaning of `all.dat` see Appendix A.

The package’s strong dependencies are:

- rootSolve [22] - for inverting the forward conversion HVC \(\to\) xyY
- spacesXYZ [4] - for conversion between CIE xyY and other CIE spaces
- spacesRGB [5] - for conversion between RGB and CIE XYZ
- logger [3] - for all logging to the console

The package microbenchmark [17] is suggested, for its high-precision timer.

Similar packages: munsell [23] also does HVC \(\leftrightarrow\) sRGB conversion, but only for discrete HVCs from the Munsell Book; there is no interpolation.

We assume that the reader is familiar with Munsell Hue, Value, and Chroma, which we abbreviate by HVC. A good introduction is [25]. All 3 are stored as floating-point numbers: H is in the interval (0,100], V is in [0,10], and C is non-negative with upper limit a complex function of H and V. These are cylindrical coordinates on the irregular Munsell object color solid. We assume that the reader is familiar with the Munsell character string notation `H V/C` for chromatic colors, and `N V/` for achromatics (neutrals).

We assume that the reader is familiar with sRGB; a good reference is [24].

We assume that the reader is familiar with the CIE spaces xyY, XYZ, and Lab. Y is the luminance factor and in this domain is considered to be a percentage in the interval [0,100]. A perfectly black surface has Y=0, and a perfectly reflecting diffuser has Y=100. An excellent reference is [12].

There are, and have been, many other software programs that do these conversions. The earliest one we know of is [19] (1960), which ran on an IBM 704. See [1] for a discussion of many other software programs and algorithms.

# Basics

The forward conversion HVC \(\to\) xyY is as simple as:

    MunsellToxyY( '4.2RP 5.5/8' )

    ##   SAMPLE_NAME HVC.H HVC.V HVC.C      xyY.x      xyY.y      xyY.Y
    ## 1 4.2RP 5.5/8  94.2   5.5   8.0  0.3624688  0.2733678 23.9680791

The return value is a `data.frame`. The first column `SAMPLE_NAME` is the input Munsell notation. The second column `HVC` is its numerical version. And the 3rd column is the output xyY. If the input is a character vector of length N, then the `data.frame` has N rows. The input can also be a numeric matrix HVC with N rows. There are many options for advanced users, see the man page for details.

And here is an example of the reverse conversion xyY \(\to\) HVC:

    xyY = MunsellToxyY( '4.2RP 5.5/8' )$xyY
    xyYtoMunsell( xyY )

    ##        xyY.x      xyY.y      xyY.Y HVC.H HVC.V HVC.C SAMPLE_NAME
    ## 1  0.3624688  0.2733678 23.9680791  94.2   5.5   8.0 4.2RP 5.5/8

And so the round-trip has returned us to `4.2RP 5.5/8`. In general, the round-trip is accurate to about 2 decimal places; the worst case is the Hue at near neutrals. Note that the return value is also a `data.frame`, but now `SAMPLE_NAME` comes at the end.

Other color spaces available are: sRGB, XYZ, Lab, Luv, and Colorlab. All the conversion functions accept multiple “samples”. Some of the functions return a `data.frame` and some return a plain matrix. See the Reference Guide for details.

# Plotting

The most obvious thing to plot is a simulation of a page from the Munsell Book of Color:

    par( omi=c(0,0,0,0), mai=c(0.6,0.7,0.4,0.2) )
    plotPatchesH( "10GY", back='#f7f7f7' )

This is the forward conversion \(\text{HVC} \to \text{xyY}_C \to \text{XYZ}_C \to \text{XYZ}_{D65} \to \text{sRGB}\). There is no interpolation here, unless the Hue is not a multiple of 2.5, which is allowed. For each Value, the Chroma is extended up to the MacAdam limit, and this determines the limit of the Chroma axis. If the sRGB for a patch is outside the cube (before clamping), it is not drawn and the sRGB values are printed instead. This makes it easy to see why the patch is outside the sRGB gamut. The chromatic adaption method, the background color, and the main title can be controlled.

Note that the plot is titled with sRGB, and this plot is best viewed on a display calibrated for sRGB. Adobe RGB space is also available.

    par( omi=c(0,0,0,0), mai=c(0.6,0.7,0.4,0.2) )
    plotPatchesH( "10GY", space='AdobeRGB', back='#f7f7f7' )

Of course, this one is best viewed on a display calibrated for Adobe RGB. Note that the gamut is quite a bit larger in this figure. Users can add custom RGB spaces too.

In the Munsell arena, another standard plot is the curves of constant Hue and Chroma.

    par( omi=c(0,0,0,0), mai=c(0.6,0.7,0.6,0.2) )
    plotLociHC( value=8 )

The black-filled circles are from `real.dat` (see Appendix A), and the open circles are extrapolated from the “real” ones. The blue curve is the CIE inverted-U, and the red curve is the MacAdam limit for the given Value=8.

One can also plot in the a,b plane.

    par( omi=c(0,0,0,0), mai=c(0.6,0.7,0.6,0.2) )
    plotLociHC( value=8, coords='ab' )

The interpolation takes place in x,y and is then mapped to a,b. For similar plots, see the Loci of Contant Hue and Chroma vignette.

# ISCC-NBS Color Names

The ISCC-NBS System is a partition of Munsell Color Solid into 267 color blocks, see [11]. Each block has an ISCC-NBS Name; one of the goals of these names is that they should be “simple enough to be understood by the average person on the street”. Each block is a disjoint union of elementary blocks (of which there are 932) where an elementary block is defined by its minimum and maximum limits in Hue, Value, and Chroma. The peripheral blocks (of which there are 120) have arbitrary large chroma (maximum Chroma is \(\infty\)). Each block has an associated centroid color, see [10], which is in the interior of the block (even though some blocks are non-convex). Given a query point HVC, the function `ColorBlockFromMunsell()` searches for the one elementary block that contains that point.

Since 2000, the Pantone Color Institute has declared a “Color of the Year”, see [27]. And they publish the sRGB coordinates of the that color. We thought it would be interesting to compare the Pantone colors with the corresponding ISCC-NBS Names and centroids. Read the data for all colors since 2010.

    path = system.file( 'extdata/PantoneCoY.txt', package='munsellinterpol' )
    pantone = read.table( path, header=TRUE, sep='\t', strings=FALSE )
    pantone

    ##    Year            Name    Code   R   G   B
    ## 1  2010       Turquoise 15-5519  69 181 170
    ## 2  2011     Honeysuckle 18-2120 217  79 112
    ## 3  2012 Tangerine Tango 17-1463 221  65  36
    ## 4  2013         Emerald 17-5641   0 148 115
    ## 5  2014  Radiant Orchid 18-3224 177  99 163
    ## 6  2015         Marsala 18-1438 149  82  81
    ## 7  2016     Rose Quartz 13-1520 247 202 201
    ## 8  2016        Serenity 15-3913 146 168 209
    ## 9  2017        Greenery 15-0343 136 176  75
    ## 10 2018    Ultra Violet 18-3838  95  75 139
    ## 11 2019    Living Coral 16-1546 255 111  97
    ## 12 2020    Classic Blue 19-4052  15  76 129
    ## 13 2021    Illuminating 13-0647 245 223  77
    ## 14 2022       Very Peri 17-3938 102 103 171
    ## 15 2023    Viva Magenta 18-1750 190  52  85
    ## 16 2024      Peach Fuzz 13-1023 255 190 152
    ## 17 2025    Mocha Mousse 17-1230 164 120 100
    ## 18 2026    Cloud Dancer 11-4201 240 238 233

Add the block names and centroids.

    pantone$Year = NULL ;   pantone$Code = NULL
    RGB = as.matrix( pantone[ , c('R','G','B') ] )
    HVC = RGBtoMunsell( RGB, space='sRGB' )
    pantone$Munsell = MunsellNameFromHVC( HVC )
    block = ColorBlockFromMunsell( HVC )
    pantone[[ "ISCC-NBS Name" ]] = block$Name
    pantone[[ "ISCC-NBS Centroid" ]] = block$Centroid
    pantone

    ##               Name   R   G   B       Munsell           ISCC-NBS Name ISCC-NBS Centroid
    ## 1        Turquoise  69 181 170 4.9BG 6.6/6.8      light bluish green     4.5BG 6.5/5.0
    ## 2      Honeysuckle 217  79 112  0.16R 5.3/13     strong purplish red    7.0RP 4.5/11.9
    ## 3  Tangerine Tango 221  65  36     8.5R 5/15    vivid reddish orange     9.5R 5.5/15.5
    ## 4          Emerald   0 148 115  7.7G 5.3/7.7            strong green      6.0G 4.5/9.1
    ## 5   Radiant Orchid 177  99 163   9.7P 5.2/11   strong reddish purple    1.0RP 4.5/11.1
    ## 6          Marsala 149  82  81  4.8R 4.2/6.1             grayish red      4.6R 4.5/4.7
    ## 7      Rose Quartz 247 202 201    2.9R 8.5/4              light pink      2.5R 8.6/5.2
    ## 8         Serenity 146 168 209 5.9PB 6.8/6.1     light purplish blue     7.5PB 6.0/6.8
    ## 9         Greenery 136 176  75 6.4GY 6.6/8.1     strong yellow green     5.0GY 6.0/9.1
    ## 10    Ultra Violet  95  75 139 0.87P 3.6/8.6         moderate violet      1.0P 3.5/7.2
    ## 11    Living Coral 255 111  97   6.6R 6.4/13     deep yellowish pink     5.6R 6.0/12.4
    ## 12    Classic Blue  15  76 129   4.5PB 3.1/8           moderate blue     2.9PB 4.2/7.2
    ## 13    Illuminating 245 223  77  6.6Y 8.8/9.8        brilliant yellow      4.0Y 8.8/9.5
    ## 14       Very Peri 102 103 171 8.6PB 4.6/9.4 brilliant purplish blue    8.0PB 6.0/10.8
    ## 15    Viva Magenta 190  52  85   1.6R 4.4/13              strong red     4.0R 4.5/12.0
    ## 16      Peach Fuzz 255 190 152 3.3YR 8.2/6.1            light orange       4.5YR 8/8.1
    ## 17    Mocha Mousse 164 120 100 2.6YR 5.3/4.2     light reddish brown     0.5YR 5.5/4.1
    ## 18    Cloud Dancer 240 238 233 3.3Y 9.4/0.32                   white    3.0Y 9.25/0.06

Note that `Emerald` is unfortunately outside the sRGB cube. Now compare the original color, and the centroid approximation to it. The color match is poor.

    color.pant = rgb( RGB, max=255 )
    color.cent = rgb( MunsellToRGB( block$Centroid, space='sRGB' )$RGB, max=255 )
    tbl = data.frame( row.names=1:nrow(pantone) )
    tbl[[ "Pantone Name" ]]  = pantone$Name
    tbl[[ "Pantone Color" ]] = '' ;    tbl[[ "Centroid Color" ]] = ''
    tbl[[ "ISCC-NBS Name" ]] = block$Name
    library( flextable )
    myrt <- regulartable( tbl )

    ## Warning in regulartable(tbl): 'regulartable' is deprecated.
    ## Use 'flextable' instead.
    ## See help("Deprecated")

    myrt <- align( myrt, j=4, align='left', part='all' )
    myrt <- align( myrt, j=2:3, align='center', part='all' )
    myrt <- hrule( myrt, rule='exact' )
    myrt <- height( myrt, height=0.75 )
    myrt <- width( myrt, j=c(1,4), width=2 ) ; myrt <- width( myrt, j=2:3, width=2.5 )
    myrt <- fontsize( myrt, size=14, part='all' ) ; myrt <- fontsize( myrt, size=16, part='header' )
    for( i in 1:nrow(tbl) )
    { myrt <- bg(myrt, i=i, j=2, bg=color.pant[i]) ; myrt <- bg(myrt, i=i, j=3, bg=color.cent[i]) }
    myrt

Pantone Name | Pantone Color | Centroid Color | ISCC-NBS Name
--- | --- | --- | ---
Turquoise |  |  | light bluish green
Honeysuckle |  |  | strong purplish red
Tangerine Tango |  |  | vivid reddish orange
Emerald |  |  | strong green
Radiant Orchid |  |  | strong reddish purple
Marsala |  |  | grayish red
Rose Quartz |  |  | light pink
Serenity |  |  | light purplish blue
Greenery |  |  | strong yellow green
Ultra Violet |  |  | moderate violet
Living Coral |  |  | deep yellowish pink
Classic Blue |  |  | moderate blue
Illuminating |  |  | brilliant yellow
Very Peri |  |  | brilliant purplish blue
Viva Magenta |  |  | strong red
Peach Fuzz |  |  | light orange
Mocha Mousse |  |  | light reddish brown
Cloud Dancer |  |  | white

This table is best viewed on a display calibrated for sRGB. In the 6 levels of color designation (from coarsest to finest) in [11], ISCC-NBS is Level 3 and Pantone would most likely be Level 4.

# Possible Future Work

Here are a few possible improvements and additions.

The conversions are already pretty fast. On my PC with 3GHz Intel Core Duo, the forward conversion HVC \(\to\) xyY takes about 1 msec/sample, and the reverse xyY \(\to\) HVC takes about 8 msec/sample. This assumes a large number of samples per function call. The next obvious speed-up is to move the interpolation code for the forward conversion from R to compiled C. It would have the added benefit that the 2x2 Jacobian could also be computed during the inversion xyY \(\to\) HVC, which would reduce the number of function evaluations per Newton-Raphson iteration from 3 to 1.

For V \(<\) 1, it might be possible to restore the extrapolated points from `all.dat`. But it will probably take better 3D visualization to assist in viewing the xy irregularities in these very dark colors.

# References

[1]
CENTORE, Paul. An open-source inversion algorithm for the Munsell Renotation. Color Research & Application. 2012, 37(6), 455–464.
[2]
D1535-97, ASTM. Standard Practice for Specifying Color by the Munsell System [online]. Standard. West Conshohocken, PA: American Society for Testing; Materials. 1997. Available at: https://www.astm.org/d1535-97.html
[3]
DARÓCZI, Gergely. Logger: A lightweight, modern and flexible logging utility [online]. 2024. Available at: https://cran.r-project.org/package=logger
[4]
DAVIS, Glenn. spacesXYZ: CIE XYZ and some of Its Derived Color Spaces [online]. 2025. Available at: https://cran.r-project.org/package=spacesXYZ
[5]
DAVIS, Glenn. spacesRGB: Standard and User-Defined RGB Color Spaces, with Conversion Between RGB and CIE XYZ and Lab [online]. 2025. Available at: https://cran.r-project.org/package=spacesRGB
[6]
FREDERICK T. SIMON. Color Order. In: FRANC GRUM AND C. JAMES BARTLESON, ed. Optical Radiation Measurements, vol. 2. B.m.: Academic Press, 1980, p. 165–233. ISBN 9780123049025.
[7]
GRANVILLE, Walter C., NICKERSON, Dorothy and FOSS, Carl E. Trichromatic Specifications for Intermediate and Special Colors of the Munsell System. J. Opt. Soc. Am. [online]. 1943, 33(7), 376–385. Available at: https://opg.optica.org/josa/abstract.cfm?uri=josa-33-7-355
[8]
JUDD, Deane B. and WYSZECKI, Günter. Extension of the Munsell Renotation System to Very Dark Colors. J. Opt. Soc. Am. [online]. 1956, 46(4), 281–284. Available at: doi:10.1364/JOSA.46.000281
[9]
KELLY, Kenneth L., GIBSON, Kasson S. and NICKERSON, Dorothy. Tristimulus Specification of the Munsell Book of Color from Spectrophotometric Measurements. J. Opt. Soc. Am. [online]. 1943, 33(7), 355–376. Available at: doi:10.1364/JOSA.33.000355
[10]
KELLY, KENNETH LOW. Central Notations for the Revised ISCC-NBS Color-Name Blocks. Journal of Research of the National Bureau of Standards. 1958, 61(5, Research Paper 2911), 427–431.
[11]
KELLY, K.L. AND JUDD, D.B. Color: Universal Language and Dictionary of Names. B.m.: U.S. Department of Commerce, National Bureau of Standards, 1976. National Bureau of Standards Special Publication, no. 440.
[12]
LINDBLOOM, Bruce. Useful color information, studies and files [online]. no date. Available at: http://brucelindbloom.com/
[13]
NEWHALL, Sidney M., NICKERSON, Dorothy and JUDD, Deane B. Final Report of the O.S.A. Subcommittee on the Spacing of the Munsell Colors. J. Opt. Soc. Am. [online]. 1943, 33(7), 385–418. Available at: doi:10.1364/JOSA.33.000385
[14]
NICKERSON, Dorothy. History of the Munsell Color System, Company, and Foundation. I. Color Research & Application [online]. 1976, 1(1), 7–10. Available at: https://munsell.com/about-munsell-color/published-work/history-of-munsell-dorothy-nickerson/
[15]
NICKERSON, Dorothy. History of the Munsell Color System, Company, and Foundation. II. Its Scientific Application. Color Research & Application [online]. 1976, 1(2), 69–77. Available at: https://munsell.com/about-munsell-color/published-work/history-of-munsell-dorothy-nickerson/
[16]
NICKERSON, Dorothy. History of the Munsell Color System, Company, and Foundation. III. Color Research & Application [online]. 1976, 1(3), 121–130. Available at: https://munsell.com/about-munsell-color/published-work/history-of-munsell-dorothy-nickerson/
[17]
OLAF MERSMANN, Rainer Hurling, Claudia Beleites. Microbenchmark: Accurate timing functions [online]. 2018. Available at: https://cran.r-project.org/package=microbenchmark
[18]
RHEINBOLDT, Werner C. and MENARD, John P. Mechanized Conversion of Colorimetric Data to Munsell Renotations (Abstract). J. Opt. Soc. Am. 1958, 11(11), 864.
[19]
RHEINBOLDT, Werner C. and MENARD, John P. Mechanized Conversion of Colorimetric Data to Munsell Renotations. J. Opt. Soc. Am. [online]. 1960, 50(8), 802–807. Available at: doi:10.1364/JOSA.50.000802
[20]
ROCHESTER INSTITUTE OF TECHNOLOGY. Program of Color Science [online]. Available at: https://www.rit.edu/science/munsell-color-lab. [Online; accessed 23-April-2018]
[21]
SCHLETER, J. C, JUDD, D. B. and KEEGAN, H. J. Extension of the Munsell Renotation System (Abstract). J. Opt. Soc. Am. 1958, 48(11), 863–864.
[22]
SOETAERT, Karline. rootSolve: Nonlinear root finding, equilibrium and steady-state analysis of ordinary differential equations [online]. 2016. Available at: https://cran.r-project.org/package=rootSolve
[23]
WICKHAM, Charlotte. Munsell: Utilities for using munsell colours [online]. 2018. Available at: https://cran.r-project.org/package=munsell
[24]
WIKIPEDIA. SRGB — wikipedia, the free encyclopedia [online]. 2017. Available at: https://en.wikipedia.org/w/index.php?title=SRGB. [Online; accessed 13-November-2017]
[25]
WIKIPEDIA CONTRIBUTORS. Munsell color system — Wikipedia, the free encyclopedia [online]. 2018. Available at: https://en.wikipedia.org/w/index.php?title=Munsell_color_system&oldid=828769698. [Online; accessed 23-April-2018]
[26]
WIKIPEDIA CONTRIBUTORS. Newton’s method — Wikipedia, the free encyclopedia [online]. 2018. Available at: https://en.wikipedia.org/w/index.php?title=Newton's_method. [Online; accessed 2-May-2018]
[27]
WIKIPEDIA CONTRIBUTORS. Pantone — Wikipedia, the free encyclopedia [online]. 2018. Available at: https://en.wikipedia.org/w/index.php?title=Pantone. [Online; accessed 2-May-2018]
[28]
WIKIPEDIA CONTRIBUTORS. Bicubic interpolation — Wikipedia, the free encyclopedia [online]. 2017. Available at: https://en.wikipedia.org/w/index.php?title=Bicubic_interpolation. [Online; accessed 10-May-2018]
[29]
WIKIPEDIA CONTRIBUTORS. Cubic hermite spline — Wikipedia, the free encyclopedia [online]. 2018. Available at: https://en.wikipedia.org/w/index.php?title=Cubic_Hermite_spline. [Online; accessed 10-May-2018]
[30]
WYSZECKI, G. and STILES, W. S. Color Science: Concepts and Methods, Quantitative Data and Formulae, Second Edition. B.m.: John Wiley, 1982. Wiley classics library. ISBN 9780471021063.

## Appendix A - Munsell Renotation Details

For history prior to 1943, please consult [14], [15], and [16].

The OSA Subcommitte on the Spacing of the Munsell Colors issued their Final Report in 1943 [13]. There were 2734 published coordinates (what might be called aim points), for Munsell Values from 1 to 9. The work was based on visual interpolation and smoothing on xy graphs of spectrophotometrically measured physical samples from the Munsell Book of Color (1929). There were 421 samples from [9] and 561 samples from [7]. There were a few obvious transcription errors in this data, which will be discussed below. After 1943, the Munsell Book of Color used these aim points, which are called the renotations.

In 1956 Judd and Wyszecki extended the renotation to Values 0.2/, 0.4/, 0.6/, and 0.8/, in [8]. The Chromas are /1, /2, /3, /4, /6, and /8. However, Chroma /1 was obtained by averaging /2 with white, and /3 was obtained by averaging /2 with /4. If Chromas /1 and /3 are dropped there are 355 very dark samples that remain. There were now 2734 + 355 = 3089 renotations, with even Chroma, in 1956.

In 1958, at the Forty-Third Annual Meeting of the Optical Society of America, a 15-minute talk [21] was given on extrapolation of these renotations for a computer program in development. Since the abstract of [21] is so short, we think it is appropriate to quote it in full:

> TA15. Extension of the Munsell Renotation System. J . C. SCHLETER, DEANE B. JUDD, AND H. J. KEEGAN, National Bureau of Standards, Washington, D. C. - In order to convert tristimulus data to Munsell renotations by using the tri-dimensional, linear, interpolation program developed at the NBS for the high-speed, digital IBM 704 computer, it was necessary to extend the published data of the Munsell re-notation system1,2 by extrapolation so that, for each point in Munsell color space, the enclosing six-sided volume would be defined by its eight corners. The approximately 2000 corners to be defined by CIE chromaticity coordinates, x, y, are at a given value level the intersections of the hue and chroma loci that cross the MacAdam limit3 at that value level and at the next lower level. Tentative definitions were found by graphical extrapolation on plots at each of the 14 value levels similar to those published. A second set of tentative definitions were found by plotting x and y against Munsell value for each of the 40 Munsell hues and extrapolating to value 10/. The final definitions were found by adjustment of the two tentative sets to produce smooth loci in both types of plots. Plots at selected value levels will be shown illustrating the extended data. (15 min.)
> 1 Newhall, Nickerson, and Judd, J. Opt. Soc. Am. 33, 385 (1943).
> 2 D. B. Judd and G. Wyszecki, J. Opt. Soc. Am. 46, 281 (1956).
> 3 D. MacAdam, J. Opt. Soc. Am. 25, 361 (1935).

The “2000 corners” is the number of points in the extension; we will call these the Schleter points.

The next talk [18] was a preliminary report on the above-mentioned software program. The authors note that 1) the extended grid from [21] “consists of approximately 5000 points”, 2) the program stores these points on magnetic tape, and 3) an xyY \(\to\) HVC conversion takes “approximately 20 seconds per sample”.

In 1960 there was an updated report [19] on the program. Rheinboldt and Menard report that the 1956 grid of 3089 points was extended by 1907 points to yield 4996 points. So the exact number of Schleter points is 1907. We also learn that the memory of the NBS IBM 704 “had been increased to 8000 words” for “the second version of the code”. This made it possible to store the entire grid in memory. They do not report the improved conversion time per sample.

Skip now to contemporary times. The Program of Color Science at the Rochester Institute Technology has posted 2 files: `real.dat` and `all.dat`, see [20]. The first file `real.dat` is described as “those colors listed the original 1943 renotation article” [13]. There are 2734 samples in the file, so this is an exact match ! Paul Centore found that before June 2010 there were 5 samples missing, but this has now been corrected (in 2018). Note that `real.dat` does not include the very dark colors, even though they are inside the MacAdam limits.

The second file `all.dat` is described this way:

> These are all the Munsell data, including the extrapolated colors. Note that extrapolated colors are in some cases unreal. That is, some lie outsize the Macadam [sic] limits.

> This file should be used for those performing multidimensional interpolation to/from Munsell data. You will need the unreal colors in order to completely encompass the real colors, which is required to do the interpolation when near the Macadam limits.

There are 4995 samples in `all.dat`, which differs from the count in [19] by only 1. Perhaps the latter count includes Illuminant C ? These two counts are so close that we are confident that the extended grid in the NBS software from 1958 survived, and made its way to the Program of Color Science at RIT, and then into this package. The above-quoted abstract is likely all we will ever know about the methods used to extrapolate the values in `all.dat`. Note that `all.dat` does include the very dark colors.

Now return to the NBS computer program in [19]. For the forward conversion HVC \(\to\) xy they first determine the two adjacent planes of constant V that the given point is between. In both of these two HC planes they use bilinear interpolation to determine xy. They then linearly interpolate these 2 xy vectors. So a single conversion requires a table lookup of 8 points in general. With this method, the plotted ovoids of constant chroma are 40-sided polygons, and not the smooth ovoids in the published plots by Dorothy Nickerson. For an example, which looks more like a spiderweb, see the Loci of Contant Hue and Chroma vignette.

To get smooth ovoids, the default method in munsellinterpol uses bicubic interpolation in the HC plane. The forward conversion is then class \(C^1\) in H and C (except at C=0). To get \(C^1\) continuity in V as well, the default conversion uses cubic interpolation for 4 planes of constant value - 2 above the input value and 2 below. This requires a table lookup of 64 points in general, and it requires extending the renotations even more ! In the terminology of mathematical morphology the required binary image is the dilation of the 4995 points with a 3x3x3 structuring element. The grid in munsellinterpol currently has 7606 points.

The extrapolation was done, again from the grid of 4995 points, using the function `gam()` in the package mgcv which is preinstalled with R itself. It is a thin-plate spline model. A separate model was computed for each of the Munsell values: 0.2,0.4,0.6,0,8,1,…10. Unfortunately, for V=1.5 (on almost any V near 1.5) the forward conversion HVC \(\to\) xy was badly non-injective for small Chroma in the area of the hue `GY`. It is because the extrapolated xy values at V=0.8 seem to diverge a lot from V=1 and V=2 (they went too far into halfplane x+y>1). Recall that a conversion at V=1.5 requires lookup at 4 planes V=0.8,1,2, and 3. Also note that the weight at V=0.8 is negative. Cubic interpolation has overshoot and undershoot - and linear interpolation (where weights are non-negative) does not.

So for dark colors V<1 we decided to ignore the extrapolations in [21], and re-extrapolate, using a `mgcv::gam()` model, from all real points with V \(\le\) 1. This extrapolation was satisfactory.

## Appendix B - Munsell Renotation Discrepancies

There have been at least 4 versions of the renotation tables. In chronological order they are:

- original tables in [13]
- tables in the “Color Bible” [30]
- tables in ASTM standard [2]
- digital file `all.dat` at [20] mentioned in the previous Appendix

I have noticed 4 discrepancies, and there might be more. They appear to be either simple transcription errors, or adjustments to make the data smoother. This package uses `all.dat`, except for 2.5PB 10/2 noted below.

For samples 7.5BG 4/16 and 5BG 1/4 it looks like a simple transcription error in [30], which was corrected in [2] and [20]. For 10PB 8/4 it looks like [2] made a smoothing adjustment, which was carried over to [20]. For 2.5R 9/2 it looks like [20] made a smoothing adjustment.

Finally, in the extrapolated data in [20], we have made a small smoothing adjustment at 2.5PB 10/2 for this package. Before the adjustment, the forward mapping HVC \(\to\) xyY was not injective near N 10/, which caused the root-finding iteration to cycle. This was discovered during testing phase; more ambitious “vetting” software, which specifically searches for non-injectivity, might uncover more of these.

## Appendix C - Inversion Details

Given an xy target point, the method in [19] searches for the exact quadrilateral that contains xy. It then solves a pair of quadratic equations to determine H and C. For bicubic interpolation, a similar explicit solution is not really feasible. The method in [1] starts with an initial approximation HC\(_0\), and modifies H and C alternately to converge to an HC that maps to xy, within a certain tolerance.

We decided to use a general root-solver from the package rootSolve that uses Newton-Raphson iteration. In our case, this iteration really does double-duty: the first iteration or two locate the quadrilateral that contains xy, and the remaining iterations polish the root. It typically takes about 4 or 5 total iterations. The theory of Newton’s Method only covers functions of class \(C^2\) (see [26]) and our function is at best \(C^1\). However, it is \(C^\infty\) on the quadrilateral interiors, so if the root is in the interior of a quadrilateral, and the iteration gets close enought to the root, then it typically stays inside that quadrilateral and converges. Even when a root is on the boundary of quadrilateral, we have found that the basin of attraction is still large enough to find it. The usual trouble is when the intial estimate is too far from the root, and then the iterations wanders outside the lookup table itself. Cycling is another possible problem - the iteration could jump around from quadrilateral to quadrilateral. But we have only seen this happen once, when the function was non-injective near white (mentioned at the end of the previous section). The quadrilaterals become small triangular slivers there, which causes more trouble near all neutrals.

Instead of HC itself, we decided to use a rectangular system: \[A = C \cos \left( \frac{\pi}{50} H \right) ~~~~~~~~ B = C \sin \left( \frac{\pi}{50} H \right)\] with the inverse: \[H=\left( \frac{50}{\pi} \right) \text{atan2}(B,A) ~~~~~~~~ C = \text{sqrt}( A^2 + B^2 )\]

\(A\) and \(B\) are analogous to \(a\) and \(b\) in \(Lab\) space. The symbols \(a''\) and \(b''\) are used for the same coordinate system in [6] page 207. So now we are trying to invert \(AB \to HC \to xy\). Each iteration requires an extra conversion \(AB \to HC\), but this is very fast.

For the initial estimate, the algorithm in [1] recommends: \[A_0 = a/5.5 ~~~~~~~~~ B_0 = b/5.5\] We use a cubic polynomial in \(a\) and \(b\) for better accuracy. A separate polynomial is computed for each V-plane, using R’s built-in function for linear models with no intercept:

    stats::lm( A ~ polym(a,b,degree=3,raw=TRUE) + 0, ... )

and similarly for \(B\). We precompute the model, but only save the coefficients. For V < 1, a cubic polynomial in \(a\) and \(b\) was still not good enough, and it was found that a cubic in \(\Delta x\) and \(\Delta y\) worked better; where \(\Delta x = x - x_C\) and \(\Delta y = y - y_C\), and \(x_C,y_C\) are the chromaticity of Illuminant C. Evaluation of the polynomial takes much longer than division by 5.5, but it is still negligible compared to the iterations to come.

The root-solver is called like this:

    rootSolve::multiroot( forwardfun, AB0, rtol=1.e-8, atol=1.e-6, ctol=0, xy_target=xy_target, ...)

The function `forwardfun()` performs the equivalent of `MunsellToxyY()` but does not call it explicitly. The argument `AB0` is the initial estimate. It usually takes 4 or 5 iterations to converge with the indicated tolerances, which are in the xy plane.

## Appendix D - Glossary

luminance factor
  The luminance factor of an object is the quotient of the luminance of the object (for a specific illuminant and in a specific viewing direction) divided by that of the perfect reflecting diffuser. In Munsell work this quotient is denoted by Y and is a percentage in the interval [0,100]. For Lambertian surfaces, Y does not depend on the viewing direction.
uniform lightness scale
  A uniform lightness scale is a reparameterization of luminance factor, so that equal numerical steps in lightness produce equal perceptual steps. The Munsell Value is an example of such a scale; in early investigations it was found that the luminance factor of the background affects the perception. Another popular scale is CIE Lightness (1976). For more on these, see the Uniform Lightness Scales vignette.

## Appendix E - Logging and Condition Signaling

Logging is performed using the package logger [3]. This is a powerful package that allows a separate configuration for logging from within munsellinterpol, and that is what I have done. During package loading, the logging threshold is changed from `INFO` to `WARN`. To change it back again, one can execute:
`logger::log_threshold( logger::INFO, namespace="munsellinterpol" ) # preferred`
or
`library( logger ) # not preferred`
`log_threshold( INFO, namespace="munsellinterpol" )`

The layout callback function is customized; it adds the name of the calling function to the message. To restore the default, one can execute:
`log_layout( layout_simple, namespace="munsellinterpol" )`
or to install one’s own layout function, one can execute:
`log_layout( <your function>, namespace="munsellinterpol" )`.

The appender callback functions is also customized. To restore the default, one can execute:
`log_appender( appender_console, namespace="munsellinterpol" )`

The formatter callback function is initialized to be `formatter_sprintf()`; this must not be changed.

If the level is `FATAL`, `ERROR`, or `WARN` then a condition object is constructed and signaled. The condition object is a list with these items:

`message` | a brief description of the error or warning. It is required in all R condition objects.
`call` | always NULL. It is the expression that created the condition. It is required in all R condition objects.
`level` | The logging level; always `FATAL`, `ERROR`, or `WARN`.
`package` | always `"munsellinterpol"`.
`record` | The single-line logging record; as returned by current `log_layout()` function. `message` is always a trailing substring of `record`.
`...` | Some functions may add more items to the list; see the man pages for the individual functions.

For levels `FATAL` and `ERROR` the S3 `class` attribute is set to `c("munsellinterpol_error", "error", "condition")`. So if desired, the condition can be handled with either `error = function(e)` or `munsellinterpol_error = function(e)`. The condition is always signaled, no matter what the current logging threshold is. Moreover, some functions may prepend more strings to the `class` that indicate the type of error that occurred. For example, a very common type of error is `"invalid_argument"`. In that case the class attribute is set to `c( "invalid_argument", "munsellinterpol_error", "error", "condition")`, and the condition can be handled with `invalid_argument = function(e)`. See the man pages for the individual functions. If the condition is handled, then `record` is not appended, but it is available in the condition object. If the condition is not handled, then `record` is appended (subject to the logging threshold), and execution halts. If the `record` was appended, then execution halts silently; otherwise the `message` is printed and then execution halts.

For level `WARN` the S3 `class` attribute is set to `c("munsellinterpol_warning", "warning", "condition")`. So if desired, the condition can be handled with either `warning = function(w)` or `munsellinterpol_warning = function(w)`. The condition is always signaled, no matter what the current logging threshold is. Moreover, some functions may prepend more strings to the `class` that indicate the type of warning. For example, many functions convert an entire vector or matrix of values. If not all could be converted, i.e. the conversion is not complete, then the class attribute might be set to `c( "incomplete", "munsellinterpol_warning", "warning", "condition")`, and the condition can be handled with `incomplete = function(w)`. See the man pages for the individual functions. If the condition is not handled, then `record` is appended (subject to the logging threshold), unless the calling function is wrapped in `suppressWarnings()`. If the condition is handled, then `record` is not appended, but it is available in the condition object. In both cases, execution continues normally.

The condition signaling architecture is designed, and mostly written, by Hugo Rodrigues.

## Session Information

    R version 4.6.1 (2026-06-24 ucrt)
    Platform: x86_64-w64-mingw32/x64
    Running under: Windows 11 x64 (build 26200)

    Matrix products: default
      LAPACK version 3.12.1

    locale:
    [1] LC_COLLATE=C                           LC_CTYPE=English_United States.utf8
    [3] LC_MONETARY=English_United States.utf8 LC_NUMERIC=C
    [5] LC_TIME=English_United States.utf8

    time zone: America/Los_Angeles
    tzcode source: internal

    attached base packages:
    [1] stats     graphics  grDevices utils     datasets  methods   base

    other attached packages:
    [1] flextable_0.9.12      spacesXYZ_1.6-0       spacesRGB_1.7-0       munsellinterpol_3.5-1

    loaded via a namespace (and not attached):
     [1] sass_0.4.10             generics_0.1.4          tidyr_1.3.2             fontLiberation_0.1.0
     [5] xml2_1.6.0              digest_0.6.39           magrittr_2.0.5          RColorBrewer_1.1-3
     [9] evaluate_1.0.5          grid_4.6.1              fastmap_1.2.0           jsonlite_2.0.0
    [13] zip_3.0.0               purrr_1.2.2             scales_1.4.0            fontBitstreamVera_0.1.1
    [17] microbenchmark_1.5.0    textshaping_1.0.5       jquerylib_0.1.4         cli_3.6.6
    [21] rlang_1.2.0             fontquiver_0.2.1        cachem_1.1.0            yaml_2.3.12
    [25] gdtools_0.5.1           rootSolve_1.8.2.4       tools_4.6.1             officer_0.7.5
    [29] uuid_1.2-2              dplyr_1.2.1             ggplot2_4.0.3           vctrs_0.7.3
    [33] logger_0.4.2            R6_2.6.1                lifecycle_1.0.5         ragg_1.5.2
    [37] pkgconfig_2.0.3         pillar_1.11.1           bslib_0.11.0            gtable_0.3.6
    [41] data.table_1.18.4       glue_1.8.1              Rcpp_1.1.1-1.1          systemfonts_1.3.2
    [45] xfun_0.59               tibble_3.3.1            tidyselect_1.2.1        knitr_1.51
    [49] farver_2.1.2            htmltools_0.5.9         rmarkdown_2.31          compiler_4.6.1
    [53] S7_0.2.2                askpass_1.2.1           openssl_2.4.2