Skip to content

Commit ccf8f42

Browse files
bug fix in spectral library preparation and subsequent interpolation of spectra
1 parent f5726b7 commit ccf8f42

File tree

6 files changed

+92
-18
lines changed

6 files changed

+92
-18
lines changed

data/munsell.spectra.rda

-15.9 KB
Binary file not shown.

misc/utils/Munsell/interpolate-spectra.R

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
##
44

55

6+
## TODO: test extrapolation of "1-chroma" spectra
67

78
## TODO: clamp to original range of Munsell chroma and value
89
## TODO: coordinate with `prepare-munsell-LUT.R`
@@ -36,6 +37,19 @@ xyplot(reflectance ~ chroma | factor(wavelength), data=s,
3637
par.settings = tactile.theme()
3738
)
3839

40+
idx <- which(m.rel$hue %in% c('7.5YR') & m.rel$value == 4)
41+
s <- m.rel[idx, ]
42+
43+
xyplot(reflectance ~ chroma | factor(wavelength), data=s,
44+
type='b', as.table=TRUE,
45+
scales = list(y = list(tick.number = 10)),
46+
auto.key=list(lines=TRUE, points=FALSE, cex=1, space='right'),
47+
par.settings = tactile.theme()
48+
)
49+
50+
51+
52+
3953
# split by hue/value/wavelength
4054
m <- split(m.rel, list(m.rel$hue, m.rel$value, m.rel$wavelength))
4155

@@ -69,6 +83,16 @@ xyplot(reflectance ~ chroma | factor(wavelength), data=s,
6983
par.settings = tactile.theme()
7084
)
7185

86+
# 8.5 values
87+
idx <- which(m.final$hue %in% c('7.5Y') & m.final$value == 8.5)
88+
s <- m.final[idx, ]
89+
90+
xyplot(reflectance ~ chroma | factor(wavelength), data=s,
91+
type='b', as.table=TRUE,
92+
scales = list(y = list(tick.number = 10)),
93+
par.settings = tactile.theme()
94+
)
95+
7296

7397

7498
# check for reflectance <= 0
@@ -125,8 +149,20 @@ xyplot(reflectance ~ wavelength, data = s,
125149
)
126150

127151

152+
# 2025-06-03: fixed long-standing bug in REGEX-parsing of reflectance database
153+
s <- subset(m.final, subset = hue == '2.5Y' & value == 8.5 & chroma %in% 2:4)
154+
155+
xyplot(reflectance ~ wavelength, data = s,
156+
groups = munsell, type='b',
157+
scales = list(y = list(tick.number = 10)),
158+
auto.key=list(lines=TRUE, points=FALSE, cex=1, space='top', columns = 3),
159+
par.settings = tactile.theme()
160+
)
161+
162+
128163

129164
## interpolate spectra for select half-chip Munsell values
165+
# note: there are some 8.5 value spectra in the source data (hues: "10Y" "2.5Y" "5Y" "7.5Y")
130166

131167
# split by hue/chroma/wavelength
132168
m <- split(m.final, list(m.final$hue, m.final$chroma, m.final$wavelength))
@@ -148,7 +184,7 @@ str(m.final)
148184

149185

150186
# check for reflectance <= 0
151-
# 3403 rows, all very close to 0
187+
# 3368 rows, all very close to 0
152188
# most of these are very low value + low chroma | low value + high chroma
153189
nrow(m.final[m.final$reflectance <= 0, ])
154190

@@ -181,12 +217,13 @@ xyplot(reflectance ~ wavelength, data = s,
181217
par.settings = tactile.theme()
182218
)
183219

220+
# there should be no duplication of the few "8.5 value" spectra
184221
s <- subset(m.final, subset = hue == '2.5Y' & chroma == 4 & value %in% c(7, 8, 8.5, 9, 9.5, 10))
185222

186223
xyplot(reflectance ~ wavelength, data = s,
187224
groups = munsell, type='b',
188225
scales = list(y = list(tick.number = 10)),
189-
auto.key=list(lines=TRUE, points=FALSE, cex=1, space='top', columns = 3),
226+
auto.key=list(lines=TRUE, points=FALSE, cex=1, space='top', columns = 5),
190227
par.settings = tactile.theme()
191228
)
192229

misc/utils/Munsell/investigate-spectral-interpolation-errors.R

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
library(purrr)
2+
devtools::load_all()
23

34
data("munsell.spectra.wide")
45

misc/utils/Munsell/local-functions.R

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,17 +69,22 @@ interpolateOddChromaSpectra <- function(i) {
6969

7070
interpolateValueSpectra <- function(i) {
7171

72+
# new Munsell values
73+
v.target <- c(2.5, 8.5, 9.5)
74+
7275
# 0 or 1 row input: no interpolation possible
73-
if(nrow(i) < 2)
76+
if(nrow(i) < 2) {
7477
return(NULL)
78+
}
79+
80+
# there are a few spectra associated with 8.5 values
81+
# if present, ignore
82+
v.target <- setdiff(v.target, i$value)
7583

7684
# setup interpolation function: natural splines
7785
# fit is exact at training points
7886
a.fun <- splinefun(i$value, i$reflectance, method = 'natural')
7987

80-
# new Munsell values
81-
v.target <- c(2.5, 8.5, 9.5)
82-
8388
# re-assemble into original format
8489
res <- data.frame(
8590
munsell = sprintf("%s %s/%s", i$hue[1], v.target, i$chroma[1]),

misc/utils/Munsell/main.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
## Code / Data related to preparation of Munsell color interpretation in aqp
2-
## 2024-10-03
2+
## 2025-06-03
33
## D.E. Beaudette, A.G. Brown
44

55
# make Munsell and related LUT

misc/utils/Munsell/prepare-simplfied-spectra-library.R

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22
##
33
##
44

5+
## From the internal description:
6+
# This file contains spectral reflectance measurements of X-Rite's 2007 Munsell Book
7+
# of Color (Glossy Finish). The measurements were made in 2012 with a ColorMunki
8+
# spectrophotometer. The first column is the Munsell name. The remaining
9+
# columns give reflectance values for 380 nm to 730 nm, in steps
10+
# of 10 nm. The reflectance is a value between 0 (indicating that no light at that
11+
# wavelength is reflected) and 1 (indicating that all the light at that wavelength
12+
# is reflected). Occasionally an entry is slightly greater than 1. The likely cause
13+
# is random variability, and those entries can be adjusted to 1 with negligible loss.
14+
# In all, 1485 colour samples were measured. Researchers are invited to analyze
15+
# the data in this file.
16+
17+
library(aqp)
518
library(reshape2)
619

720
# missing odd chroma
@@ -18,22 +31,40 @@ m$v <- as.numeric(gsub(pattern = 'X', replacement = '', x = m$variable))
1831
m <- m[, c('Name', 'v', 'value')]
1932
names(m) <- c('munsell', 'wavelength', 'reflectance')
2033

21-
# this is clever
22-
# split into Munsell pieces
34+
# reflectance values > 1 should be clamped at 1
35+
# see description above
36+
m$reflectance <- pmin(m$reflectance, 1)
37+
38+
#
39+
# 2025-06-03: more precise Munsell parsing REGEX
40+
# verified with https://regexr.com/
41+
#
42+
# the main problem here is that the munsell notation in the refelctance library
43+
# is specified without a space, and there are some 8.5 values in there
44+
# --> 10YR4/5, 5Y8.5/2, 2.5BG6/6, etc.
45+
#
2346
d <- strcapture(
24-
'([[[:digit:][:alpha:].]+)([[:digit:]]+)/([[:digit:]]+)',
47+
'([[:digit:]]+[.]?[[:digit:]]?[[:alpha:]]+)([[:digit:].]+)/([[:digit:]]+)',
2548
m$munsell,
26-
proto = data.frame(hue=character(), value=integer(), chroma=integer(), stringsAsFactors = FALSE)
49+
proto = data.frame(
50+
hue = character(),
51+
value = numeric(),
52+
chroma = integer(),
53+
stringsAsFactors = FALSE
54+
)
2755
)
2856

29-
# check: some funky ones in there:
30-
# 10Y8.
31-
# 7.5Y8.
32-
table(d$hue)
57+
# check: OK
58+
sort(tab <- table(d$hue))
59+
60+
# all hues accounted for
61+
.hp <- huePosition(returnHues = TRUE, includeNeutral = FALSE)
62+
setdiff(names(tab), .hp)
63+
setdiff(.hp, names(tab))
3364

34-
# OK
35-
table(d$value)
36-
table(d$chroma)
65+
# all standard values and
66+
table(d$value, useNA = 'always')
67+
table(d$chroma, useNA = 'always')
3768

3869

3970
# re-assemble

0 commit comments

Comments
 (0)