Soil Colors

Glenn Davis

2025-06-17



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 Ferguson Name. 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

The argument CCT in the following function 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 )
    
    # chromatically adapt soil sample XYZ under illum, to sample XYZ under Illuminant C
    white.C = 100 * spacesXYZ::standardXYZ( "C.NBS" )   # use the original NBS variant of this white point
    theCAT  = spacesXYZ::CAT( white, white.C )
    XYZ.C   = spacesXYZ::adaptXYZ( theCAT, XYZ )
    
    #   convert XYZ.C (XYZ under Illuminant C) to Munsell HVC.
    HVC     = XYZtoMunsell( XYZ.C, xyC="NBS" )  #  use the original NBS variant, as in standardXYZ() above
    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
## a4           18.5080067   5.3137162   0.9324906 8.5YR 5.3/0.93           light brownish gray
## a6           13.4661350   5.7955799   4.1294108  3.5YR 5.8/4.1                   light brown
## a8           12.9220689   5.4686500   3.9175170  2.9YR 5.5/3.9           light reddish brown
## a11          19.2796152   6.2947929   1.6599596  9.3YR 6.3/1.7 light grayish yellowish brown
## a14          17.1148841   6.8192429   3.1407503  7.1YR 6.8/3.1         light yellowish brown
## a15          13.4261954   5.0039322   3.3763618    3.4YR 5/3.4                   light brown
## Neutral0.18 100.0000000   4.8519274   0.0000000         N 4.9/                   medium gray
##                 Lab.L     Lab.a     Lab.b
## a4          54.186009  2.018138  5.414906
## a6          59.009553 13.256621 18.519024
## a8          55.734273 13.181574 16.807799
## a11         63.973658  2.786787 10.440434
## a14         69.112885  7.290566 17.315536
## a15         51.039279 11.337843 14.982868
## Neutral0.18 49.496108  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       Ferguson Name
## 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 Names are computed from the original and precise HVC, and the Ferguson Names 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 ) )
myrt

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.1 (2025-06-13 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 11 x64 (build 26100)

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.2-0

loaded via a namespace (and not attached):
 [1] katex_1.5.0             jsonlite_2.0.0          compiler_4.5.1          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.1             
[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.1             htmltools_0.5.8.1