colorSpec is an R package providing an S3 class with methods for color spectra. It supports the standard calculations with spectral properties of light sources, materials, cameras, eyes, scanners, etc.. And it works well with the more general action spectra. Many ideas are taken from packages hsdar [17], hyperSpec [4], pavo [18], photobiology [3], and zoo [31].
Some features:MASS::ginv()
responsivityMetrics()
to determine whether the
generators lie in an open linear halfspace
computeADL()
. The
electronic camera model is purely linear with no dark current offset or
other deviations.
Pick up any book on color physics (e.g. [29], [21], [20], or [13]) or color management (e.g. [10]) and you will see plots of many spectra. Let’s start with a simple division of these spectra into 4 basic types:
For the infinite-dimensional spaces, the interval [380,780] is used for illustration; in specific calculations it can vary. Note that of the 4 vector spaces, only \(L^*\) and \(M\) are isomorphic, but we take the mathematical point of view that although they are isomorphic, they are not the same. For a proof of this isomorphism, see Appendix D. Multiplication operators are the infinite-dimensional generalization of diagonal matrices. For more background on this functional analysis, see [28] and [15].
For the finite-dimensional spaces, it takes the full sequence of wavelengths and not just the endpoints. The wavelength sequence is typically regular not always. In this case all 4 vector spaces are isomorphic (since they are the same dimension), but we still take the mathematical point of view that they are not the same space.
The last type='responsivity.material'
is the least
common. There is an example in [10]
(Figure 10.11a, page 141) of a scanner, where the 3 spectra are called
the effective spectral responsivities of that scanner. They are
the pointwise product of the scanner light source and the responsitivies
of the scanner’s RGB sensor; see also pages 146-147. On page 335, in
equation (B.4), there is a more general product. In
colorSpec, both of these products and more are peformed
in the function product()
. Another example of effective
spectral responsivities are those of a reference scanner from SMPTE, see
[23].
Every colorSpec object has one of these types, but
it is not stored with the object. The object stores a
quantity
which then determines the type
; see
the next section for more discussion. A synonym for type
might be space
, but this could be confused with color
space.
colorSpec does not actually use the finite-dimensional representations in Table 1.1; the organization is flexible. And it would not be efficient memory use to store a diagonal matrix as such. For discussion of the organization, see section 4.
Given 2 finite-dimensional spectra of types 'light'
and
'responsivity.light'
the response (a real number) is their
dot product multiplied by the step between wavelengths.
All materials in this document are non-fluorescent; i.e. the outgoing photons reflected (or transmitted) only come from incoming photons of the same wavelength. A transparent material transmits an incoming light spectrum and a new spectrum emerges on the other side. If the material is not fluorescent, the outgoing spectrum is the same as the incoming, except there is a reduction of power that depends only on the wavelength (and the material). If the light power were divided into N bins, the transmitted power spectrum would be a diagonal NxN matrix times the incoming spectrum.
A reflectance spectrum is mathematically the same as a transmittance spectrum, except we compare the outgoing light spectrum to that of a perfect reflecting diffuser. Such a material does not exist, like many concepts in physics, but it is a very useful idealization.
Unfortunately there are two common metrics for quantifying spectra
with type='light'
- energy of photons and
number of photons. The former - radiometric - is the
oldest, being used in the 19th century. The latter -
actinometric - was not used until the 20th century (after the
modern concept of photons was proposed in 1905). So colorimetry uses
radiometric quantities by convention and actinometric ones are converted
to radiometric automatically for calculations. The conversion is easy;
see the function radiometric()
, [21] pp. 93-94, and [20] p. 12.
Similarly, 'responsivity.light'
can be radiometric
(e.g. the CIE color matching functions) or actinometric (e.g. the
quantum efficiency of a CMOS sensor). These actinometric spectra are
also converted to radiometric on the fly.
plot()
calibrate()
radiometric()
and
actinometric()
Note that the action
response is really a grab-bag for
responses that are neither electrical
(a modern solid-state
photosensor) nor neural
(a biological eye).
Here are the valid types and their quantities:
The colorSpec quantities are typically not the same as the SI quantities; they are more general.
First consider light sources (type='light'
).
The colorSpec quantity='energy'
includes all 5 of these power-based SI quantities: radiant
power (radiant flux), irradiance, radiant exitance, radiant intensity,
and radiance. And it also includes these energy-based
quantities: radiant energy, radiant exposure, and the time integrals of
radiant exitance, radiant intensity, and radiance. Thus
quantity='energy'
includes 10 true physical SI quantities,
which all include energy and optionally include area, solid angle, and
time.
Similary, the colorSpec
quantity='photons'
includes all 5 of these SI quantities:
photon flux, photon irradiance, photon exitance, photon intensity, and
photon radiance. It also includes these 5 quantities integrated over
time, e.g. photon fluence.
Versions of colorSpec before 0.7-1 used
power
in place of energy
. But now we have
switched to energy
; see
Appendix E for the reasons
why. power
and power->*
are still
supported, but deprecated, and will eventually be phased out.
For type='light' and type='responsivity.light'
, each
radiometric quantity has a corresponding actinometric quantity. The
following table shows the correspondences:
The colorSpec functions radiometric()
and actinometric()
convert back and forth between the two
metrics. For energy
and energy->electrical
and energy->action
the functions actually do assume the
example units. For energy->electrical
the example units
in the table are in common use for electronic cameras. Note that for
energy->action
, the action we have in mind is
photosynthesis. A quick internet search shows that the maximum
theoretical photosynthesis response is between 1/16 and 1/8 of an \(\text{O}_2\) molecule per photon. For
energy->neural
see the functions man pages for more
discussion.
Since these are spectra parameterized by nm, the
example units should all add \(\text{nm}^{-1}\) at the end, but this is
suppressed for simplicity.
Now consider materials (type='material'
). The
situation here is simpler. The colorSpec
quantity='reflectance'
, 'transmittance'
, and
'absorbance'
correspond directly to the SI quantities. All
reflecting materials are Lambertian and opaque, and all transmitting
materials have only direct transmission with no scatter.
The user constructs a colorSpec
object x
using the function colorSpec()
:
x <- colorSpec( data, wavelength, quantity='auto', organization='auto', specnames=NULL )
The arguments are:
data
a vector or matrix of the spectrum values. In case data
is
a vector, x
has a single spectrum and the number of points
in that spectrum is the length of data
. In case
data
is a matrix, the spectra are stored in the columns, so
the number of points in each spectrum is the number of rows in
data
. It is OK for the matrix to have only 0 or 1
column.
wavelength
a numeric vector of wavelengths for all the spectra in x
.
The length of this vector must be equal to NROW(data)
, and
the unit must be nanometers. The sequence must be increasing. The
wavelength
of x
can be changed after
construction.
quantity
a character string giving the quantity of all spectra; see Table 2.1 for
a list of valid values. In case quantity='auto'
, a guess is
made from the specnames
. The quantity
of
x
can be changed later.
organization
a character string giving the desired organization of the returned
colorSpec object. In case organization='auto'
, the
organization is 'vector'
or 'matrix'
depending
on data
. The organization
of x
can be changed after construction. See the next section for
discussion of all 4 possible organizations.
specnames
a character vector with length equal to the number of spectra in
data
, and with no duplicates. If
specnames=NULL
and data
is a vector, then
specnames
is set to deparse(substitute(data))
.
If specnames=NULL
and data
is a matrix, then
specnames
is set to colnames(data)
. If
specnames
is still not a character vector with the
right length, or if there are duplicate names, then
specnames
is set to 'S1', 'S2', ...
with a
warning message. Names can be changed after construction.
Compare colorSpec()
with the function
stats::ts()
.
A spectrum is similar to a time-series (with time replaced by
wavelength), and so the organization of a colorSpec
object
is similar to that of the time-series objects in package
stats. A single time-series is organized as a vector
with class ts
, and a multiple time series is organized as a
matrix (with the series in the columns) with class mts
. We
decided to use a single class name colorSpec
, continue the
idea of different organizations, and allow 2 more
organizations. Here are the 4 possible organizations, in order of
increasing complexity:
'vector'
The object is a numeric vector with attributes but no dimensions, like a
time-series ts
. This organization works for a single
spectrum only, which is very common. The common arithmetic operations
work well with this organization. The length of the vector is the number
of wavelengths. The class of the object is
c('colorSpec','numeric')
.
'matrix'
The object is a matrix with attributes, like a multiple time-series
mts
. This is probably the most suitable organization in
most cases, but it does not support extra data (see
'df.row'
below). The common arithmetic and subsetting
operations work well; and even round()
works. The number of
columns is the number of spectra, and the spectrum names are stored as
the column names. This organization can be used for any number of
spectra, including 0 or 1. The class of the object is
c('colorSpec', 'matrix')
.
'df.col'
The object is a data frame with attributes. The spectra are stored in
the columns. But the first column is always the wavelength sequence, so
the spectra are in columns 2:(M+1), where M is the number of spectra.
This organization mirrors the most common organization in text files and
spreadsheets. The common arithmetic operations do not work, and the
initial wavelength column is awkward to handle. The spectrum names are
stored as the column names of the data frame. This organization can be
used for any number of spectra, including 0 or 1. This organization
imitates the “long” format in package hyperSpec. The
class of the object is c('colorSpec', 'data.frame')
.
'df.row'
The object is a data frame with attributes. The last (right-most) column
is a matrix with spectra in the rows. This matrix is the transpose of
the matrix used when the organization is 'matrix'
. The
common arithmetic operations do not work. The spectrum names are stored
as the row names of the data frame. This organization can be used for
any number of spectra, including 0 or 1. This organization imitates the
“tall” format in package hyperSpec. This is the only
organization that supports extra data associated with each spectrum,
such as physical parameters, time parameters, descriptive strings, or
whatever. This extra data occupies the initial columns of the data frame
that come before the spectra, and can be any data frame with the right
number of rows. This extra data can be assigned to any spectrum with the
'df.row'
organization. The class of the object is
c('colorSpec', 'data.frame')
.
The attribute list is kept as small as possible. Here it is:
The user should never have to modify these using the function
attr()
.
There are 5 text file formats that can be imported; no binary formats
are supported yet. The function readSpectra()
reads a few
lines from the top of the file to try and determine the type. If
successful, it then calls the appropriate read function; see the
colorSpec reference guide for details. The file formats
are:
XYY
There is a line matching '^(wave|wv?l)'
(not case
sensitive) followed by the the names of the spectra. This is the column
header line. All lines above this one are taken to be metadata. This is
probably the most common file format; see the sample file
ciexyz31_1.csv
.
spreadsheet
There is a line matching '^(ID|SAMPLE|Time)'
. This line and
lines below must be tab-separated. Fields matching
'^[A-Z]+([0-9.]+)nm$'
are taken to be spectral data and
other fields are taken to be extradata. All lines above this one are
taken to be metadata. The organization of the returned object is
'df.row'
. This is a good format for automated acquisition
of many spectra, using a spectrometer. See the sample file
E131102.txt
.
scope
This is a file format used by Ocean Optics spectrometer software. There
is a line
>>>>>Begin Processed Spectral Data<<<<<
.
The following lines contain wavelength and energy separated by a tab.
There is only 1 spectrum per file. The organization of the returned
object is 'vector'
. See the sample file
pos1-20x.scope
.
CGATS
This is a standardized format for exchange of color data, covered by
both ANSI and ISO standards, see [2] and
[12]. It might be best understood by
looking at some samples, such as
inst/extdata/objects/Rosco.txt
. Unfortunately these
standards do not give a standard way to name the spectral data. The
function readSpectra()
considers field names that match the
pattern "^(nm|SPEC_|SPECTRAL_)[_A-Z]*([0-9.]+)$"
to be
spectral data and other fields are considered extra data. The
organization of the returned object is 'df.row'
.
Control
This is a personal format used for digitizing images of plots from
manufacturer datasheets and academic papers. It is structured like a
Microsoft .INI
file. There is a [Control]
section establishing a simple linear map from the image pixels in the
file to the wavelength and spectrum quantities. Only 3 points are really
necessary. It is OK for there to be a little rotation of the plot axes
relative to the image. This is followed by a section for each spectrum,
in XY pixel units only. Conversion to wavelength and spectral quantities
happens during on-the-fly after read. The organization of the returned
object is 'vector'
.
There is a function cs.options()
for setting options
private to the package. Currently there is only one option (before v
1.6-0 there were three).
By default, when a logging ERROR
event occurs, execution
stops. But if the option colorSpec.stoponerror
is
FALSE
, execution continues. The logging level
FATAL
is reserved for internal errors, and after a
FATAL
event, execution always stops.
Here are a few possible improvements and additions.
wavelength
handling the wavelength sequence, e.g. for product()
and
resample()
, is an annoyance. We might consider adding a
global wavelength option that all spectra are automatically resampled
to.
fluorescent materials
Recall that a non-fluorescent material corresponds to a diagonal matrix,
which operates in a trivial way on light spectra. A diagonal matrix can
be stored much more compactly as a plain vector, and multiplication of a
diagonal matrix by a vector simplifies to entrywise (Hadamard)
multiplication. A fluorescent material corresponds to a non-diagonal
matrix – called the Excitation Emission Matrix or Donaldson
Matrix. The product in Appendix C is still
multilinear, but the material product in the middle is no longer
symmetric, so enhancements to the product computations must be made.
This is a new level of complexity and memory usage, and may require a
new type of memory organization.
comparisons
There should a metric of some kind that compares two material spectra.
There should be a way to compare 2 colorSpec objects of the same type,
especially 'responsivity.light'
. For example, there would
then be a way to evaluate how close an electronic camera comes to
satisying the Maxwell-Ives Criterion. Possible metrics would be
the principal angles between subspaces.
plot()
the product()
function saves the terms with the product
object, but the plot()
function ignores them. It may be
useful to have an option to plot the individual terms too.
The following are built-in colorSpec objects that are commonly used. They are global objects that are automatically available when colorSpec is loaded. For more details on each see the corresponding help topic.
Each built-in colorSpec object in Appendix
A takes time to fully document in .Rd
help files.
Here are some bonus spectra files under folder extdata
that
users may find interesting and useful. Use the function
readSpectra()
to construct a colorSpec
object from the file, for example:
sunlight = readSpectra( system.file( 'extdata/illuminants/sunlight.txt', package='colorSpec' ) )
sunlight
##
## colorSpec object. The organization is 'df.col'. Object size is 5008 bytes.
## the object describes a single source of light, and the quantity is 'energy' (energy of photons, which is radiometric).
## Wavelength range: 300 to 830 nm. Step size is 10 nm.
##
## 1 spectra
## 54 data points / spectrum
##
## Source Min Max LambdaMax Integral
## 1 sunlight.Energy 0 1167 480 472390
See the top of each file for sources, attribution, and other
information. Alternatively, one can run summary()
on the
imported object. Some of the files in Control
format have
associated JPG
or PNG
images of plots.
This Appendix is a very formal mathematical treatment of spectra. In infinite dimensions we use the terminology of functional analysis. In finite dimensions we use the terminology of linear algebra. For easier reference here is a repeat of Table 1.1:
There are 5 natural binary products on these spaces:
An equivalent way to handle these material diagonal matrices is to represent them instead as simple vectors – the entries along the diagonal. The above products with diagonal matrices then become the much simpler entrywise or Hadamard product. This is how it is done in colorSpec, using R’s built-in entrywise product operation.
The first 4 products can be strung together to get a product: \[L \times M_1 \times ... \times M_m \times L^* \to
R\] It is not hard to show that this product is
multilinear. This means that if one fixes all terms except the
\(i^{th}\) material location, then the
composition: \[M \to L \times M_1 \times ...
\times \bullet \times ... \times M_m \times L^* \to R\] is
linear, see [16]. The first inclusion map
means to place the material spectrum in \(M\) at the ith variable slot \(\bullet\) in the product. The composition
map is a functional on \(M\) which is
an element of \(M^*\), i.e. a material
responder. This special method of creating a material responder - a
spectrum in \(M^*\) - plus all the
products in the above table, are available in the function
product()
in colorSpec. See that help page
for examples. Compare the previous equation with equation (B.4) (page
335) in [10].
The right-hand term \(R\) can be
thought of as standing for Response or Real numbers.
In colorSpec the light responders can have
multiple channels, e.g. R, G, and B,
and so there are conventions on the admissible numbers of spectra for
each term in these products. See the help page for
colorSpec::product()
for details.
This appendix gives some proofs of some earlier statements about infinite dimensional function spaces. It is not relevant to the software in any way, and is likely of interest only to mathematicians and physicists. This proof is not original and is largely an expanded version of a discussion on math.stackexchange.com, see [26].
Throughout this appendix, \(L^1\) denotes \(L^1[0,1]\), which is isomorphic to \(L^1[ \lambda_{min}, \lambda_{max}]\) where \([ \lambda_{min}, \lambda_{max}]\) is an arbitrary interval of wavelengths. Furthermore, \(L^\infty\) denotes \(L^\infty[0,1]\), and \(\mu\) denotes Lebesgue measure on \([0,1]\).
Proposition: Suppose \(\phi :[0,1] \to \mathbb{R}\) is a measurable function, and that \(\phi f \in L^1\) whenever \(f \in L^1\). Define the multiplication operator \(M_{\phi} : L^1 \to L^1\) by \(M_{\phi}(f) = \phi f\). ThenLemma: Given \(f,g \in L^1\) and a sequence \({f_n} \in L^1\) and \(\phi\) as above. Suppose \[a) ~ f_n \to f ~~~~~~~\text{and} ~~~~~~~~ b) ~ \phi f_n \to g\]
where both convergences are in \(L^1\). Then \(\phi f = g\) almost everywhere.
Proof: From \(a)\), and Theorem 3.12 in [22], p. 70, \(f_n\) has a subsequence that converges to \(f\) a.e. Replace \(f_n\) by this subsequence and \(a)\) and \(b)\) are still true. From \(b)\), \(\phi f_n\) has a subsequence that converges to \(g\) a.e. Replace \(\phi f_n\) and \(f_n\) by this subsequence and \(a)\) and \(b)\) are still true. So we have \[a') ~ f_n \to f ~~~~~~~\text{and}~~~~~~~~ a'') ~ \phi f_n \to \phi f ~~~~~~~\text{and}~~~~~~~~ b') ~ \phi f_n \to g\] where all convergences are almost everywhere. From \(a'')\) and \(b')\) we conclude that \(\phi f = g\) a.e. \(\square\).
Proof of Proposition: Parts \(a)\) and \(b)\) of the Lemma state that \((f_n,\phi f_n) \to (f,g)\) in \(L^1 \times L^1\). Define the graph of \(M_{\phi}\) in \(L^1 \times L^1\) to be the set of all pairs \((f,\phi f)\), \(f \in L^1\). The conclusion of the Lemma states that this graph is closed. So by the closed graph theorem ([22] p. 122), \(M_\phi\) is continuous. This shows part \(1.\)
Consider the functional \(f \mapsto \int \phi f \, d\mu\) on \(L^1\). It is the composition of \(M_\phi\) and a trivially continuous functional, and is therefore continous. Since \(L^1\) is \(\sigma\)-finite, the standard duality theorem ([22] p. 136), implies that there is a unique \(g \in L^\infty\) so that \(\int \phi f \, d\mu = \int g f \, d\mu\) for all \(f \in L^1\). Therefore \(\phi = g\), and this shows part \(2.\)
If \(\left\lVert \phi \right\rVert_\infty = 0\) then \(\phi=0\) and \(\left\lVert M_\phi \right\rVert = 0\), so part \(3.\) is trivially true. Assume now that \(\left\lVert \phi \right\rVert_\infty > 0\). Let \(f \in L^1\) with \(\left\lVert f \right\rVert = 1\). Then \[\left\lVert M_\phi (f) \right\rVert_1 ~=~ \int_0^1 \left\lvert \phi f \right\rvert \, d\mu ~=~ \int_0^1 \left\lvert \phi \right\rvert \left\lvert f \right\rvert \, d\mu ~\le~ \left\lVert \phi \right\rVert_\infty \int_0^1 \left\lvert f \right\rvert \, d\mu ~=~ \left\lVert \phi \right\rVert_\infty \] This shows \(\left\lVert M_\phi \right\rVert \le \left\lVert \phi \right\rVert_\infty\). For the other direction, let \(\alpha\) be any number with \(0 < \alpha < \left\lVert \phi \right\rVert_\infty\), and let \(E_\alpha := \left\lvert \phi \right\rvert ^{-1} ( [\alpha,\infty] )\). Then by the definition of \(\left\lVert \phi \right\rVert_\infty\), \(\mu( E_\alpha) > 0\). Let \(f_\alpha := \chi_{E_\alpha} / \mu( E_\alpha)\) (the \(L^1\)-normalized indicator function of \(E_\alpha\)). Then \[ \left\lVert M_\phi (f_\alpha) \right\rVert_1 ~:=~ \left\lVert \phi f_\alpha \right\rVert_1 ~:=~ \int_0^1 \left\lvert \phi \right\rvert f_\alpha \, d\mu ~\ge~ \int_0^1 \alpha f_\alpha \, d\mu ~=~ \alpha \int_0^1 f_\alpha \, d\mu ~=~ \alpha \left\lVert f_\alpha \right\rVert_1 ~=~ \alpha \] So \(\left\lVert M_\phi \right\rVert \ge \alpha\) for every \(\alpha < \left\lVert \phi \right\rVert_\infty\), which implies \(\left\lVert M_\phi \right\rVert \ge \left\lVert \phi \right\rVert_\infty\). This shows part \(3.\) \(\square\).
Corollary: Let \(M\) be the vector space of all
multiplication operators on \(L^1\).
Then the mapping \(L^\infty \to M\)
given by \(\phi \mapsto M_\phi\) is a
norm-preserving isomorphism.
Proof:The mapping is clearly injective. The
Proposition shows that it is surjective and
norm-preserving. \(\square\)
Consider these subtle differences in the way light sources and responders (detectors) are appropriately measured:
'power->neural'
), a photovoltaic cell
('power->electrical'
), or photosynthesis
('power->action'
). All of these respond (almost)
instantaneously. 'energy->electrical'
), or erythemal exposure
('energy->action'
). For these responders there is a
well-defined integration time.
Since color science emphasizes constant light sources and biological
eyes, power has always seemed more appropriate to me than
energy. But starting with colorSpec version
0.7-1 I decided to switched to energy
for these
reasons:
energy->electrical
) what
matters to the color of the photograph is the integral of the spectrum
(the energy) of the flash bulb over the exposure interval of the camera.
This is a case when the light spectrum is not constant; it can vary over
that interval. Similarly, in photosynthesis
(energy->action
) what matters to the plant is the
integral of daylight from sunrise to sunset. Think of the daytime as a
very long pulse. For an example, see the file
solar.exposure.txt
in
Appendix B.
I also considered allowing both energy
and
power
, and both photons
and
photons/time
. But this would force the user to decide
whether a light source is constant or variable, and whether a
responder/detector is integrating or non-integrating. So things quickly
got complicated. These common radiant SI quantities - radiant power,
irradiance, radiant exitance, radiant intensity, radiance - differ only
in area and steradian. Time is now grouped with these 2 geometric
units.
In physics, wavelengths are in some interval of real numbers - an uncountable set. But in engineering, one is forced to use wavelengths taken from a finite table of values. Given a table of wavelengths and values, a software package must make some sort of choice of what the physical interpretation of this table really is. In colorSpec the choice is schizophrenic - there are multiple interpretations.
With few exceptions, a table of wavelengths and values is interpreted
as a step function. Such functions are sometimes called
piecewise-constant. This requires a lengthy explanation.
Suppose X
is a colorSpec object with \(N\) wavelengths: \(\lambda_1 < \lambda_2 < \ldots <
\lambda_N\). Define \(N\)
intervals \(I_i :=
[\beta_{i-1},\beta_i]\) where \[\begin{equation}
\beta_0 := \tfrac{3}{2}\lambda_1 - \tfrac{1}{2}\lambda_2 ~~~~~
\beta_i := (\lambda_i + \lambda_{i+1})/2, ~ i{=}1,\ldots,N-1 ~~~~~
\beta_N := \tfrac{3}{2}\lambda_N - \tfrac{1}{2}\lambda_{N-1}
\end{equation}\] The intervals \(I_i\) are a partition of \([\beta_0,\beta_N]\). Note that \([\beta_0,\beta_N]\) is slightly bigger than
\([\lambda_1,\lambda_N]\) because the
endpoints are extended. Define the \(i'th\) step \(\mu_i := \operatorname{length}(I_i), ~
i{=}1,\ldots,N\). If the sequence \(\{\lambda_i\}\) is regular (\(\lambda_{i+1}-\lambda_i\) is constant),
then \(\mu_i\) is constant with the
same value, and each \(\lambda_i\) is
the center of \(I_i\). Now suppose
X
has \(m\) spectra
(channels) with vector values \(\mathbf{y}_i
\in \mathbb{R}^m\). Then the physical function realization of
X
is a function \(\mathbf{y}(\lambda) : [\beta_0,\beta_N] \to
\mathbb{R}^m\) that takes the constant value \(\mathbf{y}_i\) on \(I_i\). If the sequence \(\{\lambda_i\}\) is regular, then all \(\mathbf{y}_i\) have the same weight,
including the first and last. This is the step function interpretation
used in product()
, interpolate()
,
bandSpectra()
, and many other places.
The exceptions are resample()
and plot()
.
In resample()
the physical functions are piecewise-linear,
piecewise-cubic, or piecewise-quintic, depending on the argument
method
(a smoothing method is also available). In
plot()
the spectra are plotted as piecewise-linear (using
lines()
) by default, but an option to plot as step
functions (using segments()
) was added in v. 1.2-0.
The function product()
often takes the product of
spectra. Note that the product of piecewise-linear functions is not
piecewise-linear, but the product of step functions is still a step
function.
In lengthy calculations using both interpretations, there are inevitable numerical errors, which are certainly larger than the usual numerical roundoff. But we do not attempt carry the error analysis any further than that.
Logging is performed using the package logger [6]. This is a powerful package that allows a
separate configuration for logging from within
colorSpec, 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="colorSpec" ) # preferred
or
library( logger ) # not preferred
log_threshold( INFO, namespace="colorSpec" )
The layout callback function is customized; it adds the name of the
calling function to the message. To change it back again, one can
execute:
log_layout( layout_simple, namespace="colorSpec" )
or to install ones own layout function, one can execute:
log_layout( <your function>, namespace="colorSpec" )
.
The appender callback functions is also customized; it comes to an
immediate stop if the log event level is FATAL
, or if the
level is ERROR
and the global option
colorSpec.stoponerror
is TRUE
. To return to
the default behavior, one can execute:
log_appender( appender_console, namespace="colorSpec" )
The formatter callback function is initialized to be
formatter_sprintf()
; this should not be changed.
R version 4.4.2 (2024-10-31 ucrt) Platform: x86_64-w64-mingw32/x64 Running under: Windows 11 x64 (build 22631) Matrix products: default locale: [1] LC_COLLATE=C [2] LC_CTYPE=English_United States.utf8 [3] LC_MONETARY=English_United States.utf8 [4] 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] colorSpec_1.6-0 loaded via a namespace (and not attached): [1] digest_0.6.37 R6_2.5.1 microbenchmark_1.5.0 [4] fastmap_1.2.0 xfun_0.49 glue_1.8.0 [7] cachem_1.1.0 knitr_1.49 htmltools_0.5.8.1 [10] logger_0.4.0 rmarkdown_2.29 lifecycle_1.0.4 [13] cli_3.6.3 sass_0.4.9 jquerylib_0.1.4 [16] compiler_4.4.2 tools_4.4.2 evaluate_1.0.1 [19] bslib_0.8.0 yaml_2.3.10 rlang_1.1.4 [22] jsonlite_1.8.9