# Soil Colors

#### Glenn Davis

#### 2026-03-12

# Introduction

    library(munsellinterpol)

The goal of this vignette is to give examples of rounding to the closest official color chip in one or more Munsell chart sets. The five chart sets are Soil [11], Rock [12], Bead [13], Plant [14], and The New Student Color Set [10]. For the color chip data we depend completely on Ferguson [7]. The supplementary material from this paper was put in computer readable form by Willie Ondricek, who greatly improved the wording of this vignette. One of the fields in this supplementary material is a color name for each chip; we call this name the `FergusonName`. In this vignette, we only use examples from the Munsell Soil-Color Charts.

The key function in this vignette is `roundHVC()`. Other featured functions from munsellinterpol are `XYZtoMunsell()`, `MunsellNameFromHVC()`, and `ColorBlockFromMunsell()`. Other packages that play a very important part are OxSR [8], colorSpec [5], and spacesXYZ [4]. But we will not attach these and prefix their functions with the namespace to make it easier to locate them. The package flextable [9] is used in one section to display some RGB color patches.

# Load and Plot Selected Soil Spectra

The package OxSR has some high resolution (0.5 nm) reflectance spectra for 23 soil samples; these spectra are stored in the object `OxSR::soil_refle`. Of these 23 we have pre-selected a subset of 6 samples with high variability. The following function also adds an ideal neutral sample with constant reflectance of 18% - an 18% gray card [16].

    load_soil <- function( soil_raw=OxSR::soil_refle, subset=c( "a4", "a6", "a8", "a11", "a14", "a15" ),
                                             wave=380:780 )
        {
        #   locate only the wavelengths in OxSR::soil_refle that are relevant for color
        #   the given data.frame OxSR::soil_refle goes way into the infra-red and these are not needed
        #   We already know that all relevant multiples of 1nm are in soil_raw
        idx     = match( wave, soil_raw$wavelength_nm )

        #   extract just a subset of samples with high variability
        #   the reflectances in OxSR::soil_refle are percentages, so divide by 100
        mat     = as.matrix(soil_raw)[ idx, subset ] / 100

        #   convert the matrix mat to a colorSpec object
        soil_spec = colorSpec::colorSpec( mat, wavelength=wave, quantity='reflectance' )

        #   add neutral gray as a test sample
        gray      = colorSpec::neutralMaterial( 0.18, wavelength=wave )
        soil_spec = colorSpec::bind( soil_spec, gray )

        return( soil_spec )
        }

Load the spectra and plot them.

    soil_spec = load_soil()
    par( omi=c(0,0,0,0), mai=c(0.6,0.6,0.3,0.3) )
    plot( soil_spec, legend="topleft" )

The graphed colors here are computed for Illuminant D65. This may not always be appropriate, so here is another function that can create and use illuminants of any Correlated Color Temperature (CCT).

# A Function to Compute HVC and Lab from Reflectance Spectra

In the following function `soil_data()`, argument `soil_spec` is a `colorSpec` object with reflectance spectra of the soil, and `CCT` is the desired Correlated Color Temperature of the illuminant.

    soil_data <- function( soil_spec, CCT )
        {
        wave    = colorSpec::wavelength(soil_spec)

        #   make illuminant from the CCT. This is the reference illuminant in IES Standard TM-30.
        illum   = colorSpec::referenceSpectraTM30( CCT, wavelength=wave )

        #   make scanner from this illuminant and the standard x,y,z responsivities of the human eye
        scanner = colorSpec::product( illum, "soil", colorSpec::xyz1931.1nm, wavelength=wave )

        #   scale so the Perfect Reflecting Diffuser has Y=100; which is conventional for Munsell work
        scanner = colorSpec::calibrate( scanner, response=c(NA,100,NA), method='scaling' )

        #   compute white point of illum
        white   = colorSpec::product( colorSpec::neutralMaterial(1,wavelength=wave), scanner )

        #   compute XYZ for all soil samples; this is XYZ under illum
        XYZ     = colorSpec::product( soil_spec, scanner )

        #   compute Lab for all soil samples, under illum
        Lab     = spacesXYZ::LabfromXYZ( XYZ, white )

        #   convert Lab to Munsell HVC, which automatically does a chromatic adaptation to Illuminant C
        HVC     = LabtoMunsell( Lab, white=white )   #  the default is to use the original NBS variant of C
        rownames(HVC)   = colorSpec::specnames(soil_spec)

        # create output data.frame and add 4 columns: HVC, Munsell notation, ISCC-NBS color name, and Lab
        out = data.frame( row.names=rownames(HVC) )
        out$HVC     = HVC
        out$Munsell = MunsellNameFromHVC( HVC )
        out[[ "ISCC-NBS Name" ]] = ColorBlockFromMunsell( HVC )$Name
        out$Lab     = Lab

        return( out )
        }

This one is fairly long, but it does a lot of work.

# Pick an Illuminant and Compute the Munsell HVC for It

When viewing soil samples, these are the official recommendations:

> The visual impression of color from the standard color chips is accurate only under standard conditions of light intensity and quality. Comparison of soil color to the standard Munsell chips should be done without sunglasses and in normal sunlight. … If artificial light is used … the light source used must be as near to the white light of midday as possible. — from Conditions for measuring color in [11]

Instead of “normal sunlight” I believe the authors actually meant “normal daylight”, as in the next sentence “light of midday”. The American Cinematographer Manual, p 199 of [6], says that a reasonable Correlated Color Temperature (CCT) for “Average Summer Daylight” is 6500 K. So we’ll choose the CIE-standardized daylight illuminant with CCT=6500 for this calculation. Note that the white point of this illuminant is also the white point for the color space sRGB.

Some teams of soil scientists may use viewing conditions that are different, and also want agreement between human and instrumental Munsell colors. Here are some possible alternatives to the daylight illuminant with CCT=6500 (D65). An unofficial page [15], recommends that viewing is done under “Natural light conditions (preferably on a cloudy day or in shaded area)”. Reference [6] states that a reasonable CCT for “Average Summer Shade” is 8000K. So if viewing is done in a shaded area, CCT=8000 might be more appropriate. For viewing indoors under incandescent lamps, warm light with CCT=3000 K might be more appropriate. For viewing indoors under LEDs, an actual LED spectrum might be more appropriate. There are many such LED spectra in packages photobiologyLEDs [2] and photobiologyLamps [3]. These can be converted to `colorSpec` objects using `photobiologyInOut::as.colorSpec()` [1].

    dat_soil = soil_data( soil_spec, CCT=6500 )
    dat_soil

    ##                  HVC.H      HVC.V      HVC.C        Munsell                 ISCC-NBS Name     Lab.L
    ## a4          18.5080040  5.3137162  0.9324907 8.5YR 5.3/0.93           light brownish gray 54.186009
    ## a6          13.4661351  5.7955799  4.1294108  3.5YR 5.8/4.1                   light brown 59.009553
    ## a8          12.9220689  5.4686500  3.9175170  2.9YR 5.5/3.9           light reddish brown 55.734273
    ## a11         19.2796151  6.2947929  1.6599596  9.3YR 6.3/1.7 light grayish yellowish brown 63.973658
    ## a14         17.1148841  6.8192429  3.1407503  7.1YR 6.8/3.1         light yellowish brown 69.112885
    ## a15         13.4261954  5.0039322  3.3763618    3.4YR 5/3.4                   light brown 51.039279
    ## Neutral0.18  0.0000000  4.8519274  0.0000000         N 4.9/                   medium gray 49.496108
    ##                 Lab.a     Lab.b
    ## a4           2.018138  5.414906
    ## a6          13.256621 18.519024
    ## a8          13.181574 16.807799
    ## a11          2.786787 10.440434
    ## a14          7.290566 17.315536
    ## a15         11.337843 14.982868
    ## Neutral0.18  0.000000  0.000000

Note that all 6 pre-selected soil samples are within the YR (Yellow-Red) Hues. Actually all 23 soil samples are YR, and not just these selected 6 ! Note that for the `Neutral0.18` test sample (not soil), `C=a=b=0` as they should be.

# Round to the Nearest Chip in the Munsell Soil Charts

The above samples have high precision, but unfortunately that precision makes it inconvenient to report in a way that researchers familiar with the Munsell Soil Color Charts can relate to. For the sake of consistency with the traditional way of communicating colors, it is sometimes better to provide the Munsell notation for the closest color chip in the official Soil Charts.

    dat_rnd     = roundHVC( dat_soil$HVC, books="soil" )
    dat_rnd$HVC = NULL     # delete matrix HVC, because it is redundant in this context
    dat_rnd

    ##                             ISCC-NBS Name MunsellRounded        FergusonName
    ## a4                    light brownish gray      7.5YR 5/1                Gray
    ## a6                            light brown      2.5YR 6/4 Light reddish brown
    ## a8                    light reddish brown      2.5YR 5/4       Reddish brown
    ## a11         light grayish yellowish brown       10YR 6/2 Light brownish gray
    ## a14                 light yellowish brown      7.5YR 7/3                Pink
    ## a15                           light brown      2.5YR 5/3       Reddish brown
    ## Neutral0.18                   medium gray           N 5/                Gray

The Munsell notations in the `MunsellRounded` column are guaranteed to be in the Soil Color Charts; note that all 6 are distinct. The `ISCC-NBS Name`s are computed from the original and precise HVC, and the `FergusonName`s are computed from `MunsellRounded`. Note that these names are all different.

Note that 3 of the samples are on the same Hue chart 2.5YR. Plot that Hue chart and label those 3 samples:

    par( omi=c(0,0,0,0), mai=c(0.4,0.4,0.25,0) )
    plotPatchesH( "2.5YR",   value=c(2.5,3:8), chroma=c(1,2,3,4,6,8) )
    text( c(4,4,3), c(5,4,4), c("a6","a8","a15"), adj=c(0.5,0.5), col="white" )

The Value and Chroma vectors match the actual soil color chart, but the chips here are a superset of those in the chart. The top-right chip, 2.5YR 8/8, is inside the object color gamut, but outside the sRGB gamut. These colors are best viewed on a display that is calibrated for sRGB.

# Comparisons

Finally, we make a table with side-by-side color comparisons of the precisely computed color in Munsell notation, and the Munsell notation after rounding to the closest color chip in the Munsell Soil Color Charts.

    tbl = data.frame( row.names=1:(2*nrow(dat_soil)) )
    tbl$Sample  = as.character( rbind(rownames(dat_soil),'') )
    tbl$Precise = as.character( rbind('',MunsellNameFromHVC( dat_soil$HVC, digits=3 )) )
    tbl$Rounded = as.character( rbind('',dat_rnd$MunsellRounded ) )

    library( flextable )
    myrt <- regulartable( tbl )
    myrt <- width( myrt, j=c(2,3), width=2 )
    myrt <- align( myrt, j=1:3, align='center', part='all' )
    myrt <- hline( myrt, i=seq( 2, nrow(tbl)-2, by=2 ), border=fp_border_default( color="black", width=2 ) )
    myrt <- hrule( myrt, rule = "exact" )
    idxcolor <- seq(1,nrow(tbl)-1,by=2)
    myrt <- height( myrt, i=idxcolor, height=1 )
    myrt <- bg( myrt, i=idxcolor, j=2, bg=rgb( round(MunsellTosRGB(dat_soil$HVC)$RGB), max=255 ) )
    myrt <- bg( myrt, i=idxcolor, j=3, bg=rgb( round(MunsellTosRGB(dat_rnd$MunsellRounded)$RGB), max=255 ) )

Sample | Precise | Rounded
--- | --- | ---
a4 |  |
| 8.51YR 5.31/0.932 | 7.5YR 5/1
a6 |  |
| 3.47YR 5.8/4.13 | 2.5YR 6/4
a8 |  |
| 2.92YR 5.47/3.92 | 2.5YR 5/4
a11 |  |
| 9.28YR 6.29/1.66 | 10YR 6/2
a14 |  |
| 7.11YR 6.82/3.14 | 7.5YR 7/3
a15 |  |
| 3.43YR 5/3.38 | 2.5YR 5/3
Neutral0.18 |  |
| N 4.85/ | N 5/

These colors are best viewed on a display that is calibrated for sRGB.

# References

[1]
APHALO, Pedro J. photobiologyInOut: Read spectral and logged data from foreign files. UV4Plants Bulletin [online]. 2015, 2015(1), 21–29. Available at: doi:10.19232/uv4pb.2015.1.14
[2]
APHALO, Pedro J. photobiologyLEDs: Spectral data for light-emitting-diodes. UV4Plants Bulletin [online]. 2015, 2015(1), 21–29. Available at: doi:10.19232/uv4pb.2015.1.14
[3]
APHALO, Pedro J. photobiologyLamps: Spectral irradiance data for lamps. UV4Plants Bulletin [online]. 2015, 2015(1), 21–29. Available at: doi:10.19232/uv4pb.2015.1.14
[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. colorSpec: Color Calculations with Emphasis on Spectral Data [online]. 2018. Available at: https://CRAN.R-project.org/package=colorSpec
[6]
DETMERS, F. H. and CINEMATOGRAPHERS, American Society of. American Cinematographer Manual: Sixth Edition. B.m.: The ASC Press, 1986. ISBN 9780935578072.
[7]
FERGUSON, Jonathan. Munsell notations and color names: Recommendations for Archaeological Practice. Journal of Field Archaeology [online]. 2014, 39(4), 327–335. Available at: doi:10.1179/0093469014Z.00000000097
[8]
FROSI, Gustavo, BARRÓN, Vidal, INDA, Alberto and BASTIANI, Kayn. OxSR: Soil iron oxides via diffuse reflectance [online]. 2025. Available at: https://github.com/FGu5tav0/OxSR/
[9]
GOHEL, David and SKINTZOS, Panagiotis. flextable: Functions for Tabular Reporting [online]. 2024. Available at: doi:10.32614/CRAN.package.flextable
[10]
LONG, J. The New Munsell Student Color Set 3rd Edition. B.m.: Bloomsbury Academic, 2011. ISBN 9781609011567.
[11]
MUNSELL, Color. Munsell Soil-Color Charts. B.m.: Munsell Color, 2009.
[12]
MUNSELL, Color. Munsell Rock-Color Charts. B.m.: Munsell Color, 2009.
[13]
MUNSELL, Color. Munsell Bead Color Book. B.m.: Munsell Color, 2012.
[14]
MUNSELL, Color. Munsell Color Charts for Plant Tissues. B.m.: Munsell Color, 1977.
[15]
SEELINGER, Marc. Mastering Soil Analysis: How to Use the Munsell Soil Color Chart. https://swampschool.org/mastering-soil-analysis-how-to-use-the-munsell-soil-color-chart/. 2024.
[16]
WIKIPEDIA CONTRIBUTORS. Gray card — Wikipedia, the free encyclopedia [online]. 2024. Available at: https://en.wikipedia.org/w/index.php?title=Gray_card&oldid=1244084939. [Online; accessed 16-June-2025]

# Session Information

    R version 4.5.2 (2025-10-31 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.7       spacesXYZ_1.6-0       spacesRGB_1.7-0       munsellinterpol_3.3-2

    loaded via a namespace (and not attached):
     [1] katex_1.5.0             jsonlite_2.0.0          compiler_4.5.2          equatags_0.2.1
     [5] Rcpp_1.0.14             zip_2.3.3               xml2_1.3.8              jquerylib_0.1.4
     [9] fontquiver_0.2.1        systemfonts_1.2.3       textshaping_1.0.1       uuid_1.2-1
    [13] yaml_2.3.10             fastmap_1.2.0           R6_2.6.1                gdtools_0.4.2
    [17] microbenchmark_1.5.0    curl_6.2.2              knitr_1.50              colorSpec_1.8-0
    [21] logger_0.4.0            openssl_2.3.2           bslib_0.9.0             rlang_1.1.6
    [25] V8_6.0.3                cachem_1.1.0            xfun_0.52               sass_0.4.10
    [29] cli_3.6.5               OxSR_1.0.1              digest_0.6.37           grid_4.5.2
    [33] rootSolve_1.8.2.4       askpass_1.2.1           lifecycle_1.0.4         evaluate_1.0.3
    [37] glue_1.8.0              data.table_1.17.2       fontLiberation_0.1.0    officer_0.6.8
    [41] ragg_1.4.0              xslt_1.5.1              fontBitstreamVera_0.1.1 rmarkdown_2.29
    [45] tools_4.5.2             htmltools_0.5.8.1