Title: | Color Calculations with Emphasis on Spectral Data |
---|---|
Description: | Calculate with spectral properties of light sources, materials, cameras, eyes, and scanners. Build complex systems from simpler parts using a spectral product algebra. For light sources, compute CCT, CRI, and SSI. For object colors, compute optimal colors and Logvinenko coordinates. Work with the standard CIE illuminants and color matching functions, and read spectra from text files, including CGATS files. Estimate a spectrum from its response. A user guide and 9 vignettes are included. |
Authors: | Glenn Davis [aut, cre] |
Maintainer: | Glenn Davis <[email protected]> |
License: | GPL (>= 3) |
Version: | 1.5-0 |
Built: | 2024-12-19 06:41:43 UTC |
Source: | CRAN |
Package colorSpec is for working with spectral color data in R.
Features:
a clear classification of the commonly seen spectra into 4 types - depending on the vector space to which they belong
flexible organization for the spectra in memory, using an S3 class - colorSpec
a product algebra for the colorSpec objects
uniform handling of biological eyes, electronic cameras, and general action spectra
a few advanced calculations, such as computing optimal colors (aka Macadam Limits)
inverse colorimetry, e.g. reflectance recovery from response
built-in essential tables, such as the CIE illuminants and color matching functions
a package logging system with log levels taken from the popular Log4J
support for reading a few spectrum file types, including CGATS
bonus files containing some other interesting spectra
minimal dependencies on other R packages
Non-features:
there is no support for many common 3D color spaces, such as CIELAB, HSL, etc.. For these spaces see packages colorspace, colorscience, spacesRGB, and spacesXYZ.
there are few non-linear operations
there is little support for scientific units; for these see package colorscience
photons are parameterized by wavelength in nanometers; other wavelength units (such as Angstrom and micron) and alternative parameterizations (such as wavenumber and electronvolt) are not available
Regarding the non-linear operations in 2,
the only such operations are conversion of linear RGB to display RGB,
conversion of absorbance to transmittance,
and the reparameterized wavelength in computeADL
.
The electronic camera model is purely linear
with no dark current offset or other deviations.
Many ideas are taken from packages hyperSpec, hsdar, pavo, and zoo.
Glenn Davis <[email protected]>
colorSpec
for the S3 class provided by this package.
colorSpec User Guide
A.1nm |
standard Illuminant A, 360 to 780 nm at 1 nm intervals |
B.5nm |
standard Illuminant B, 320 to 780 nm at 5 nm intervals |
C.5nm |
standard Illuminant C, 320 to 780 nm at 5 nm intervals |
Each is a colorSpec object organized as a vector, with quantity
equal to "energy".
All of these have been divided by 100, to make the values at 560nm near 1 instead of 100.
Günther Wyszecki and W. S. Stiles. Color Science : Concepts and Methods, Quantitative Data and Formulae. Second Edition. Wiley-Interscience. 1982.
A | Table I(3.3.4) pp. 754-758. |
B | Table II(3.3.4) pp. 759. |
C | Table II(3.3.4) pp. 759. |
summary(xyz1931.1nm) white.point = product( D65.1nm, xyz1931.1nm, wave='auto' )
summary(xyz1931.1nm) white.point = product( D65.1nm, xyz1931.1nm, wave='auto' )
Convert a radiometric colorSpec object to have quantity that is actinometric (number of photons). Test an object for whether it is actinometric.
## S3 method for class 'colorSpec' actinometric( x, multiplier=1, warn=FALSE ) ## S3 method for class 'colorSpec' is.actinometric( x )
## S3 method for class 'colorSpec' actinometric( x, multiplier=1, warn=FALSE ) ## S3 method for class 'colorSpec' is.actinometric( x )
x |
a colorSpec object |
multiplier |
a scalar which is multiplied by the output, and intended for unit conversion |
warn |
if |
If the quantity
of x
does not start with 'energy'
then the quantity is not radiometric and so x
is returned unchanged.
Otherwise x
is radiometric (energy-based), and must be converted.
If type(x)
is 'light'
then
the most common radiometric energy unit is joule.
The conversion equation is:
wher is the photon count,
is the energy of the photons,
is Avogadro's constant,
is Planck's constant,
is the speed of light,
and
is the wavelength.
The output unit of photon count is
(
mole of photons) = (
photons).
If a different unit for
Q
is desired,
then the output should be scaled appropriately.
For example, if the desired unit of photon count is exaphotons,
then set multiplier=0.602214
.
If the quantity(x)
is 'energy->electrical'
,
then the most common radiometric unit of responsivity to light
is coulombs/joule (C/J) or amps/watt (A/W).
The conversion equation is:
where is the quantum efficiency,
is the energy-based responsivity,
and
is the charge of an electron (in C).
If the unit of x
is not C/J,
then multiplier
should be set appropriately.
If the quantity(x)
is
'energy->neural'
or 'energy->action'
,
the most common radiometric unit of energy is joule (J).
The conversion equation is:
where is the photon-based responsivity,
and
is the energy-based responsivity,
The output unit of photon count is
(
mole of photons) = (
photons).
This essentially the reciprocal of the first conversion equation.
The argument multiplier
is applied to the right side of all the above
conversion equations.
actinometric()
returns a colorSpec object
with quantity
that is
actinometric (photon-based) and not radiometric (energy-based).
If type(x)
is a material type
('material'
or 'responsivity.material'
)
then x
is returned unchanged.
If quantity(x)
starts with 'photons'
,
then is.actinometric()
returns TRUE
, and otherwise FALSE
.
To log the executed conversion equation,
execute cs.options(loglevel='INFO')
.
Wikipedia. Photon counting. https://en.wikipedia.org/wiki/Photon_counting
quantity
,
type
,
cs.options
,
radiometric
colSums( solar.irradiance ) # step size is 1nm, from 280 to 1000 nm. organized as a matrix # AirMass.0 GlobalTilt AirMass.1.5 # 944.5458 740.3220 649.7749 # irradiance, watts*m^{-2} colSums( actinometric(solar.irradiance) ) # AirMass.0 GlobalTilt AirMass.1.5 # 4886.920 3947.761 3522.149 # photon irradiance, (umoles of photons)*sec^{-1}*m^{-2} colSums( actinometric(solar.irradiance,multiplier=0.602214) ) # AirMass.0 GlobalTilt AirMass.1.5 # 2942.972 2377.397 2121.088 # photon irradiance, exaphotons*sec^{-1}*m^{-2}
colSums( solar.irradiance ) # step size is 1nm, from 280 to 1000 nm. organized as a matrix # AirMass.0 GlobalTilt AirMass.1.5 # 944.5458 740.3220 649.7749 # irradiance, watts*m^{-2} colSums( actinometric(solar.irradiance) ) # AirMass.0 GlobalTilt AirMass.1.5 # 4886.920 3947.761 3522.149 # photon irradiance, (umoles of photons)*sec^{-1}*m^{-2} colSums( actinometric(solar.irradiance,multiplier=0.602214) ) # AirMass.0 GlobalTilt AirMass.1.5 # 2942.972 2377.397 2121.088 # photon irradiance, exaphotons*sec^{-1}*m^{-2}
apply a function to each spectrum in a colorSpec object
## S3 method for class 'colorSpec' applyspec( x, FUN, ... )
## S3 method for class 'colorSpec' applyspec( x, FUN, ... )
x |
a colorSpec object with N wavelengths |
FUN |
a function that takes an N-vector as argument and returns an N-vector |
... |
additional arguments passed to |
applyspec()
simply calls apply()
with the right MARGIN
.
a colorSpec object with the same dimensions,
wavelength
, quantity
, and organization
.
If FUN
does not return an N-vector, it is an ERROR and
applyspec()
returns NULL
.
quantity
,
wavelength
,
linearize
,
organization
,
apply
# convert absorbance to transmittance path = system.file( "extdata/stains/Hematoxylin.txt", package='colorSpec' ) x = readSpectra( path ) x = applyspec( x, function(y) {10^(-y)} ) # this is what linearize(x) does
# convert absorbance to transmittance path = system.file( "extdata/stains/Hematoxylin.txt", package='colorSpec' ) x = readSpectra( path ) x = applyspec( x, function(y) {10^(-y)} ) # this is what linearize(x) does
convert a colorSpec object to a data.frame
## S3 method for class 'colorSpec' as.data.frame( x, row.names=NULL, optional=FALSE, organization='auto', ... )
## S3 method for class 'colorSpec' as.data.frame( x, row.names=NULL, optional=FALSE, organization='auto', ... )
x |
a colorSpec object |
organization |
The organization of the returned |
row.names |
ignored |
optional |
ignored |
... |
extra arguments ignored |
If organization
is 'auto'
,
and the organization of x
is 'df.row'
,
then organization
is set to 'row'
and the returned data.frame
has the spectra in the rows.
Otherwise the returned data.frame
has the spectra in the columns.
If the returned data.frame
has the spectra in the rows,
then the spectra are in a matrix in the last column (with name spectra
),
and any existing extradata
are also returned in the other columns.
The wavelengths are only present in character form,
as the colnames
of the matrix.
If the returned data.frame
has the spectra in the columns,
then the wavelengths are in the first column, and the spectra are in the other columns.
Calculate transmittance along a horizontal optical path in the atmosphere, as a function of length (distance) and the molecular and aerosol properties. Because the path is horizontal, the atmospheric properties are assumed to be constant on the path. Only molecular scattering is considered. There is no modeling of molecular absorption; for visible wavelengths this is reasonable.
atmosTransmittance( distance, wavelength=380:720, molecules=list(N=2.547305e25,n0=1.000293), aerosols=list(metrange=25000,alpha=0.8,beta=0.0001) )
atmosTransmittance( distance, wavelength=380:720, molecules=list(N=2.547305e25,n0=1.000293), aerosols=list(metrange=25000,alpha=0.8,beta=0.0001) )
distance |
the length of the optical path, in meters. It can also be a numeric vector of lengths. |
wavelength |
a vector of wavelengths, in nm, for the transmittance calculations |
molecules |
a list of molecular properties, see Details.
If this is |
aerosols |
a list of aerosol properties, see Details.
If this is |
The list molecules
has 2 parameters that describe the molecules in the atmosphere.
N
is the molecular density of the atmosphere at sea level,
in .
The given default is the density at sea level.
n0
is the refractive index of pure molecular air (with no aerosols).
For the molecular attenuation,
the standard model for Rayleigh scattering is used,
and there is no modeling of molecular absorption.
The list aerosols
has 3 parameters that describe the aerosols in the atmosphere.
The standard Angstrom aerosol attenuation model is:
is the Angstrom exponent, and is dimensionless.
and
have unit
.
And
=550nm.
metrange
is the Meteorological Range of the atmosphere in meters,
as defined by Koschmieder.
This is the distance at which the transmittance=0.02 at .
If
metrange
is not NULL
(the default is 25000)
then both and
are calculated to achieve
this desired
metrange
, and the supplied and
are ignored.
is calculated from
metrange
using the Kruse model,
see Note.
is calculated so that the product of
molecular and aerosol transmittance yields the desired
metrange
.
In fact:
where is the molecular attenuation at
,
and
is the meteorological range.
For a log message with the calculated values,
execute
cs.options(loglevel='INFO')
before calling atmosTransmittance()
.
atmosTransmittance()
returns a
colorSpec object with quantity
equal to 'transmittance'
.
There is a spectrum in the object for each value in the vector distance
.
The specnames
are set to sprintf("dist=%gm",distance)
.
The final transmittance is the product of the molecular transmittance
and the aerosol transmittance.
If both molecules
and aerosols
are NULL
,
then the final transmittance is identically 1;
the atmosphere has become a vacuum.
The Kruse model for as a function of
is defined in 3 pieces.
For
,
.
For
,
.
And for
50000,
.
So
is increasing, but not strictly, and not continuously.
is in meters.
See Kruse and Kaushal.
The built-in object atmosphere2003
is transmittance along
an optical path that is NOT horizontal,
and extends to outer space.
This is much more complicated to calculate.
Angstrom, Anders. On the atmospheric transmission of sun radiation and on dust in the air. Geogr. Ann., no. 2. 1929.
Kaushal, H. and Jain, V.K. and Kar, S. Free Space Optical Communication. Springer. 2017.
Koschmieder, Harald. Theorie der horizontalen Sichtweite. Beitrage zur Physik der Atmosphare. 1924. 12. pages 33-53.
P. W. Kruse, L. D. McGlauchlin, and R. B. McQuistan. Elements of Infrared Technology: Generation, Transmission, and Detection. J. Wiley & Sons, New York, 1962.
trans = atmosTransmittance( c(5,10,15,20,25)*1000 ) # 5 distances with atmospheric defaults # verify that transmittance[550]=0.02 at dist=25000 plot( trans, legend='bottomright', log='y' ) # repeat, but this time assign alpha and beta explicitly trans = atmosTransmittance( c(5,10,15,20,25)*1000, aero=list(alpha=1,beta=0.0001) )
trans = atmosTransmittance( c(5,10,15,20,25)*1000 ) # 5 distances with atmospheric defaults # verify that transmittance[550]=0.02 at dist=25000 plot( trans, legend='bottomright', log='y' ) # repeat, but this time assign alpha and beta explicitly trans = atmosTransmittance( c(5,10,15,20,25)*1000, aero=list(alpha=1,beta=0.0001) )
A band-based material spectrum is a superimposition of bandpass filters, and (optionally) a bandstop filter. The 2 functions in this topic convert a vector of numbers between 0 and 1 to a band representation, and back again.
bandMaterial( lambda, wavelength=380:780 ) ## S3 method for class 'colorSpec' bandRepresentation( x )
bandMaterial( lambda, wavelength=380:780 ) ## S3 method for class 'colorSpec' bandRepresentation( x )
lambda |
a numeric Mx2 matrix with wavelength pairs in the rows,
or a vector that can be converted to such a matrix, by row.
The two wavelengths in a row (the transition wavelengths) define either a bandpass or bandstop filter,
and all the rows are superimposed to define the transmittance spectrum of the final material.
If the 2 wavelengths are denoted by |
wavelength |
a vector of wavelengths for the returned object |
x |
a colorSpec object with |
bandRepresentation()
is a right-inverse of bandMaterial()
,
see Examples and the test script test-bands.R
.
For more mathematical details, see the vignette
Convexity and Transitions.
bandMaterial()
returns a
colorSpec object with quantity
equal to 'transmitance'
.
If lambda
is a matrix, then the object has 1 spectrum.
If lambda
is a list of matrices with length N, then the object has N spectra.
bandRepresentation()
returns a list of matrices with 2 columns.
There is a matrix in the list for each spectrum in x
.
rectangularMaterial()
,
vignette Convexity and Transitions
# make a vector superimposing a bandpass and a bandstop filter, and of the proper length 401 vec = c( rep(1,100), 0.5, rep(0,40), .25, rep(1,50), 0.9, rep(0,100), 0.4, rep(1,107) ) # convert that vector to a colorSpec object, with a single spectrum spec = colorSpec( vec, wavelength=380:780, quantity='transmittance', specnames='sample' ) # extract and print the 2 bands lambda = bandRepresentation( spec ) ; print(lambda) ## $sample ## lambda1 lambda2 ## BS 673.10 480.0 ## BP1 521.25 572.4 # convert the 2 bands (the transition wavelengths) back to a vector of length 401 # and compare with the original vector delta = vec - coredata( bandMaterial(lambda) ) range(delta) ## [1] -9.092727e-14 2.275957e-14
# make a vector superimposing a bandpass and a bandstop filter, and of the proper length 401 vec = c( rep(1,100), 0.5, rep(0,40), .25, rep(1,50), 0.9, rep(0,100), 0.4, rep(1,107) ) # convert that vector to a colorSpec object, with a single spectrum spec = colorSpec( vec, wavelength=380:780, quantity='transmittance', specnames='sample' ) # extract and print the 2 bands lambda = bandRepresentation( spec ) ; print(lambda) ## $sample ## lambda1 lambda2 ## BS 673.10 480.0 ## BP1 521.25 572.4 # convert the 2 bands (the transition wavelengths) back to a vector of length 401 # and compare with the original vector delta = vec - coredata( bandMaterial(lambda) ) range(delta) ## [1] -9.092727e-14 2.275957e-14
Take a sequence of colorSpec objects and combine their spectra
## S3 method for class 'colorSpec' bind( ... )
## S3 method for class 'colorSpec' bind( ... )
... |
colorSpec objects with the same |
The organization
of the returned object is the most complex of those in the inputs,
where the order of complexity is:
'matrix'
< 'df.col'
< 'df.row'
If the selected organization
is 'df.row'
, the extradata
is combined in a way that preserves all the columns.
Missing data is filled with NA
s, analogous to
rbind.fill()
.
The metadata
of the returned object is copied from the first object in the input list.
bind()
returns a colorSpec object, or NULL
in case of ERROR. If the bind is
successful, the number of spectra in the output object is the sum of the number of spectra
in the input objects.
wavelength
,
quantity
,
specnames
,
organization
,
extradata
,
metadata
,
rbind.fill()
Rosco = readSpectra( system.file( 'extdata/objects/Rosco.txt', package='colorSpec' ) ) Rosco = resample( Rosco, wave=wavelength(Hoya) ) numSpectra(Hoya) # prints 4 numSpectra(Rosco) # prints 42 filters = bind( Hoya, Rosco ) numSpectra(filters) # prints 46 colnames( extradata(Hoya) ) ## [1] "SAMPLE_NAME" "FresnelReflectance" "Thickness" colnames( extradata(Rosco) ) ## [1] "Model" "SampleID" "SAMPLE_NAME" "Substrate" "RefractiveIndex" "Thickness" ## The columns in common are "SAMPLE_NAME" and "Thickness" colnames( extradata(filters) ) ## [1] "FresnelReflectance" "Model" "RefractiveIndex" "SAMPLE_NAME" ## [5] "SampleID" "Substrate" "Thickness" ## ## "SAMPLE_NAME" and "Thickness" are combined in the usual way ## The other columns are present, and missing data is filled with NAs
Rosco = readSpectra( system.file( 'extdata/objects/Rosco.txt', package='colorSpec' ) ) Rosco = resample( Rosco, wave=wavelength(Hoya) ) numSpectra(Hoya) # prints 4 numSpectra(Rosco) # prints 42 filters = bind( Hoya, Rosco ) numSpectra(filters) # prints 46 colnames( extradata(Hoya) ) ## [1] "SAMPLE_NAME" "FresnelReflectance" "Thickness" colnames( extradata(Rosco) ) ## [1] "Model" "SampleID" "SAMPLE_NAME" "Substrate" "RefractiveIndex" "Thickness" ## The columns in common are "SAMPLE_NAME" and "Thickness" colnames( extradata(filters) ) ## [1] "FresnelReflectance" "Model" "RefractiveIndex" "SAMPLE_NAME" ## [5] "SampleID" "Substrate" "Thickness" ## ## "SAMPLE_NAME" and "Thickness" are combined in the usual way ## The other columns are present, and missing data is filled with NAs
make a linear modification to a colorSpec responder with M spectra,
so a specific stimulus (a single spectrum) creates a specific response (an M-vector).
It is generalized form of white balance.
The options are complicated, but in all cases the returned object is
multiply(x,gmat)
where gmat
is an internally calculated MxM matrix - called the gain matrix.
Stated another way, the spectra in the output are linear combinations of spectra in
the input x
.
In case of ERROR, a message is logged and the original x
is returned.
## S3 method for class 'colorSpec' calibrate( x, stimulus=NULL, response=NULL, method=NULL )
## S3 method for class 'colorSpec' calibrate( x, stimulus=NULL, response=NULL, method=NULL )
x |
a colorSpec responder with M spectra.
The |
stimulus |
a colorSpec object with a single spectrum, with |
response |
an M-vector, or a scalar which is then replicated to length M.
Normally all entries are not |
method |
an MxM adaptation matrix.
|
If stimulus
is NULL
, it is set to
illuminantE()
or neutralMaterial()
to match x
.
If response
is NULL
and the response of x
is electrical
or action
,
then response
is set to an M-vector of all 1s.
If response
is NULL
and the response of x
is neural
,
then this is an ERROR and the user is prompted to supply a specific response
.
If method
is NULL
, its assignment is complicated.
If M=3 and the response of x
is neural
,
and the specnames
of x
partially match c('x','y','z')
(case-insensitive),
and none of the components of response
are NA
,
then the neural response is assumed to be human, and the method
is set to 'Bradford'
.
Otherwise method
is set to 'scaling'
.
a colorSpec object equal to multiply(x,gmat)
where gmat
is an internally calculated MxM matrix.
The quantity()
and wavelength()
are preserved.
Note that gmat
is not the same as the the MxM adaptation matrix.
To inspect gmat
execute summary()
on the returned object.
If method
is 'scaling'
then gmat
is diagonal and the
diagonal entries are the M gain factors needed to achieve the calibration.
Useful data is attached as attribute "calibrate"
.
Chromatic adaptation transforms, such as 'Bradford'
,
do not belong in the realm of spectra,
for this is not really a spectral calculation.
For more about this subject see the explanation in Digital Color Management,
Chapter 15 - Myths and Misconceptions.
These sophisticated adaptation transforms are provided in calibrate()
because it is possible and convenient.
ASTM E308-01. Standard Practice for Computing the Colors of Objects by Using the CIE System. 2001.
CIE 15: Technical Report: Colorimetry, 3rd edition. CIE 15:2004.
Edward J. Giorgianni and Thomas E. Madden. Digital Color Management: Encoding Solutions. 2nd Edition John Wiley. 2009. Chapter 15 - Myths and Misconceptions.
is.regular()
,
multiply()
,
quantity()
,
wavelength()
,
colorSpec
,
summary()
,
illuminantE()
,
neutralMaterial()
,
product()
wave = 380:780 # make an art gallery illuminated by illuminant A, and with tristimulus XYZ as output gallery = product( A.1nm, 'artwork', xyz1931.1nm, wave=wave ) # calibrate simplistically, # so the perfect reflecting diffuser has the standard XYZ coordinates for Illuminant A # using the convention that Y=100 (instead of Y=1) A = 100 * spacesXYZ::standardXYZ('A') A ## X Y Z ## A 109.85 100 35.585 gallery.cal1 = calibrate( gallery, response=A, method='scaling' ) # calibrate following the ASTM and CIE recommendation gallery.cal2 = calibrate( gallery, response=c(NA,100,NA), method='scaling' ) # make the Perfect Reflecting Diffuser for testing prd = neutralMaterial( 1, wave=wave ) ; specnames(prd) = 'PRD' # compare responses to the PRD for gallery.cal1 and gallery.cal2 white.1 = product( prd, gallery.cal1 ) white.2 = product( prd, gallery.cal2 ) white.1 ; white.2 ; white.1 - white.2 ## X Y Z ## PRD 109.85 100 35.585 ## X Y Z ## PRD 109.8488 100 35.58151 ## X Y Z ## PRD 0.001210456 -2.842171e-14 0.003489313 # make an RGB flatbead scanner from illuminant F11 and a Flea2 camera scanner = product( subset(Fs.5nm,'F11'), 'paper', Flea2.RGB, wave='auto') # adjust RGB gain factors (white balance) so the perfect reflecting diffuser yields RGB=(1,1,1) scanner = calibrate( scanner ) # same flatbead scanner, but this time with some "white headroom" scanner = product( subset(Fs.5nm,'F11'), 'paper', Flea2.RGB, wave='auto' ) scanner = calibrate( scanner, response=0.95 ) scanner
wave = 380:780 # make an art gallery illuminated by illuminant A, and with tristimulus XYZ as output gallery = product( A.1nm, 'artwork', xyz1931.1nm, wave=wave ) # calibrate simplistically, # so the perfect reflecting diffuser has the standard XYZ coordinates for Illuminant A # using the convention that Y=100 (instead of Y=1) A = 100 * spacesXYZ::standardXYZ('A') A ## X Y Z ## A 109.85 100 35.585 gallery.cal1 = calibrate( gallery, response=A, method='scaling' ) # calibrate following the ASTM and CIE recommendation gallery.cal2 = calibrate( gallery, response=c(NA,100,NA), method='scaling' ) # make the Perfect Reflecting Diffuser for testing prd = neutralMaterial( 1, wave=wave ) ; specnames(prd) = 'PRD' # compare responses to the PRD for gallery.cal1 and gallery.cal2 white.1 = product( prd, gallery.cal1 ) white.2 = product( prd, gallery.cal2 ) white.1 ; white.2 ; white.1 - white.2 ## X Y Z ## PRD 109.85 100 35.585 ## X Y Z ## PRD 109.8488 100 35.58151 ## X Y Z ## PRD 0.001210456 -2.842171e-14 0.003489313 # make an RGB flatbead scanner from illuminant F11 and a Flea2 camera scanner = product( subset(Fs.5nm,'F11'), 'paper', Flea2.RGB, wave='auto') # adjust RGB gain factors (white balance) so the perfect reflecting diffuser yields RGB=(1,1,1) scanner = calibrate( scanner ) # same flatbead scanner, but this time with some "white headroom" scanner = product( subset(Fs.5nm,'F11'), 'paper', Flea2.RGB, wave='auto' ) scanner = calibrate( scanner, response=0.95 ) scanner
Consider a colorSpec object x
with type
equal to 'responsivity.material'
.
The set of all possible material reflectance functions (or transmittance functions)
is convex, closed, and bounded (in any reasonable function space),
and this implies that the set of all possible output responses
from x
is also convex, closed, and bounded.
The latter set is called the object-color solid or Rösch Farbkörper for x
.
A color on the boundary of the object-color solid is called an optimal color for x
.
The corresponding transmittance spectrum is called an optimal spectrum for x
.
The special points W (the response to the perfect reflecting diffuser)
and 0 (the response to the perfect absorbing diffuser) are optimal.
Currently the function only works if the number of spectra in
x
is 3 (e.g. RGB or XYZ).
In this case the object-color solid is a zonohedron whose boundary
is the union of parallelograms, which may be coplanar.
These parallelograms are indexed by distinct pairs of the wavelengths of x
;
if x
has N wavelengths, then there are N*(N-1) parallelograms.
The center of each parallelogram is called a canonical optimal color.
Interestingly, the special points W and 0 are not canonical.
## S3 method for class 'colorSpec' canonicalOptimalColors( x, lambda, spectral=FALSE )
## S3 method for class 'colorSpec' canonicalOptimalColors( x, lambda, spectral=FALSE )
x |
a colorSpec object with |
lambda |
a numeric Mx2 matrix whose rows contain distinct pairs of wavelengths of |
spectral |
if |
The 3 responsivities are regarded not as continuous functions,
but as step functions.
This implies that the color solid is a zonohedron.
In the preprocessing phase the zonohedral representation is calculated.
The faces of the zonohedron are either parallelograms,
or compound faces that can be partitioned into parallelograms.
The centers of all these parallelograms are the canonical optimal colors.
The optimal spectra take value 1/2 at the 2 given wavelengths, and 0 or 1 elsewhere.
If the 2 wavelengths are and
,
and
then the spectrum is approximately a bandpass filter.
If the 2 wavelengths are swapped, then the spectrum is "flipped"
and is approximately a bandstop filter.
If argument spectral=FALSE
,
canonicalOptimalColors()
returns a data.frame
with a row for each row in lambda
.
The columns in the output are:
lambda |
the given matrix argument |
optimal |
the computed optimal colors - an Mx3 matrix |
transitions |
the number of transitions in the optimal spectrum, this is a positive even number |
If rownames(lambda)
is not NULL
, they are copied to the row names of the output.
If argument spectral=TRUE
,
it returns a colorSpec object with quantity
'transmittance'
.
This object contains the optimal spectra,
and the above-mentioned data.frame
can then be obtained by applying
extradata()
to the returned object.
In case of global error, the function returns NULL
.
Centore, Paul. A zonohedral approach to optimal colours. Color Research & Application. Vol. 38. No. 2. pp. 110-119. April 2013.
Logvinenko, A. D.
An object-color space.
Journal of Vision.
9(11):5, 1-23, (2009).https://jov.arvojournals.org/article.aspx?articleid=2203976
doi:10.1167/9.11.5.
Schrödinger, E. (1920). Theorie der Pigmente von grösster Leuchtkraft. Annalen der Physik. 62, 603-622.
West, G. and M. H. Brill. Conditions under which Schrödinger object colors are optimal. Journal of the Optical Society of America. 73. pp. 1223-1225. 1983.
probeOptimalColors()
,
bandRepresentation()
,
scanner.ACES
,
extradata()
,
type
,
vignette Convexity and Transitions
wave = seq(400,700,by=5) D50.eye = product( D50.5nm, 'material', xyz1931.1nm, wavelength=wave ) canonicalOptimalColors( D50.eye, c(500,600, 550,560, 580,585) ) ## lambda.1 lambda.2 optimal.x optimal.y optimal.z transitions ## 1 500 600 47.02281830 80.07281030 4.33181530 2 ## 2 550 560 5.18490614 10.09045773 0.06121505 2 ## 3 580 585 26.91247649 21.49031008 0.03457904 6
wave = seq(400,700,by=5) D50.eye = product( D50.5nm, 'material', xyz1931.1nm, wavelength=wave ) canonicalOptimalColors( D50.eye, c(500,600, 550,560, 580,585) ) ## lambda.1 lambda.2 optimal.x optimal.y optimal.z transitions ## 1 500 600 47.02281830 80.07281030 4.33181530 2 ## 2 550 560 5.18490614 10.09045773 0.06121505 2 ## 3 580 585 26.91247649 21.49031008 0.03457904 6
chop all spectra in a colorSpec object into low and high parts at a blending interval
## S3 method for class 'colorSpec' chop( x, interval, adj=0.5 )
## S3 method for class 'colorSpec' chop( x, interval, adj=0.5 )
x |
a colorSpec object |
interval |
a numeric vector with length 2 giving the endpoints of the interval, in nm |
adj |
a number in [0,1] defining weights of low and high parts over the interval |
For each spectrum, the low and high parts sum to the original spectrum. The low part vanishes on the right of the interval, and the high part vanishes on the left.
chop(x)
returns a colorSpec object with twice the number of spectra in x
and with organization
equal to 'matrix'
.
The names of the new spectra are formed by appending ".lo" and ".hi" to the original spectrum names.
# chop blue butane flame into diatomic carbon and hydrocarbon parts path = system.file( "extdata/sources/BlueFlame.txt", package="colorSpec" ) blueflame = readSpectra( path, seq(375,650,0.5) ) plot( chop( blueflame, interval=c(432,435), adj=0.8 ) ) # chop 'white' LED into blue and yellow parts path = system.file( "extdata/sources/Gepe-G-2001-LED.sp", package="colorSpec" ) LED = readSpectra( path ) plot( chop( LED, c(470,495) ) )
# chop blue butane flame into diatomic carbon and hydrocarbon parts path = system.file( "extdata/sources/BlueFlame.txt", package="colorSpec" ) blueflame = readSpectra( path, seq(375,650,0.5) ) plot( chop( blueflame, interval=c(432,435), adj=0.8 ) ) # chop 'white' LED into blue and yellow parts path = system.file( "extdata/sources/Gepe-G-2001-LED.sp", package="colorSpec" ) LED = readSpectra( path ) plot( chop( LED, c(470,495) ) )
The function colorSpec()
is the constructor for colorSpec objects.
is.colorSpec()
tests whether an object is a valid colorSpec object.as.colorSpec()
converts other variables to a colorSpec object,
and is designed to be overridden by other packages.
colorSpec( data, wavelength, quantity='auto', organization='auto', specnames=NULL ) is.colorSpec(x) ## Default S3 method: as.colorSpec( ... )
colorSpec( data, wavelength, quantity='auto', organization='auto', specnames=NULL ) is.colorSpec(x) ## Default S3 method: as.colorSpec( ... )
data |
a vector or matrix of the spectrum values.
In case |
wavelength |
a numeric vector of wavelengths for all the spectra, in nm.
The length of this vector must be equal to |
quantity |
a character string giving the |
organization |
a character string giving the desired organization
of the returned colorSpec object.
In case of |
specnames |
a character vector with length equal to the number of spectra,
and with no duplicates.
If |
x |
an R object to test for being a valid colorSpec object. |
... |
arguments for use in other packages. |
Under the hood, a colorSpec object is either a vector
, matrix
, or data.frame
.
It is of S3 class 'colorSpec'
with these extra attributes:
wavelength
a numeric vector of wavelengths for all the spectra. If the organization of the object is 'df.col'
, then this is absent.
quantity
a character string that gives the physical quantity of all spectra, see quantity()
for a list of possible values.
metadata
a named list for user-defined data. The names 'path'
, 'header'
and 'date'
are already reserved; see metadata()
.
step.wl
step between adjacent wavelengths in nm. This is assigned only when the wavelengths are regular; see is.regular()
.
specname
only assigned when the organization
is 'vector'
, in which case it is equal to the single character string name of the single spectrum.
Note that the word specname
is singular. Also see specnames()
.
And there are a few more attributes that exist only in special cases; see the colorSpec User Guide.
colorSpec()
returns a colorSpec object, or NULL
in case of ERROR.
Compare this function with stats::ts()
.
is.colorSpec()
returns TRUE
or FALSE
.
It does more than check the class
,
and also checks wavelength
, quantity
, and organization
.
If FALSE
, it logs (at loglevel='DEBUG'
)
the reason that x
is invalid.
as.colorSpec.default()
issues an ERROR message and returns NULL
wavelength
,
quantity
,
organization
,
metadata
,
step.wl
,
specnames
,
is.regular
,
coredata
# make a synthetic Gaussian bandpass filter center = 600 wave = 400:700 trans = exp( -(wave-center)^2 / 20^2 ) filter.bp = colorSpec( trans, wave, quantity='transmittance', specnames='myfilter' ) organization( filter.bp ) # returns: "vector" # and now plot it plot( filter.bp )
# make a synthetic Gaussian bandpass filter center = 600 wave = 400:700 trans = exp( -(wave-center)^2 / 20^2 ) filter.bp = colorSpec( trans, wave, quantity='transmittance', specnames='myfilter' ) organization( filter.bp ) # returns: "vector" # and now plot it plot( filter.bp )
Consider a colorSpec object x
with type
equal to responsivity.material
.
The set of all possible material reflectance functions (or transmittance functions)
is convex, closed, and bounded (in any reasonable function space),
and this implies that the set of all possible output responses
from x
is also convex, closed, and bounded.
The latter set is called the object-color solid or Rösch Farbkörper for x
.
A color on the boundary of the object-color solid is called an optimal color.
The special points W (the response to the perfect reflecting diffuser)
and 0 are on the boundary of this set.
The interior of the line segment of neutrals joining 0 to W is in the interior of the
object-color solid.
It is natural to parameterize this segment from 0 to 1 (from 0 to W).
The solid is symmetrical about the neutral gray midpoint G=W/2.
Now suppose that x
has 3 spectra (3 responses)
and consider a color response R not equal to G.
There is a ray based at G and passing through R
that intersects the boundary of the
object-color solid at an optimal color B on the boundary
with Logvinenko coordinates .
If these 2 coordinates are combined with
, where
R =
G +
B,
it yields the Logvinenko coordinates
of R.
These coordinates are also denoted by ADL; see References.
A response is in the object-color solid iff
.
A response is optimal iff
.
The coordinates of 0 are =(1,0,0).
The coordinates of W are
=(1,1,0).
The coordinates of G are undefined.
## S3 method for class 'colorSpec' computeADL( x, response )
## S3 method for class 'colorSpec' computeADL( x, response )
x |
a colorSpec object with |
response |
a numeric Nx3 matrix with responses in the rows, or a numeric vector that can be converted to such a matrix, by row. |
For each response, a ray is computed and the ray tracing is
done by probeOptimalColors()
.
computeADL()
returns a data.frame
with a row for each response.
The columns in the data frame are:
response |
the input response vector |
ADL |
the computed ADL coordinates of the response vector |
omega |
the reparameterized |
lambda |
lambda.1 and lambda.2 at the 2 transitions, in nm. lambda.1 < lambda.2 => bandpass, and lambda.1 > lambda.2 => bandstop. |
If an individual ray could not be traced,
or if the optimal spectrum has more than 2 transitions,
the row contains NA
in appropriate columns.
In case of global error, the function returns NULL
.
Since this function is really a simple wrapper around
probeOptimalColors()
,
please see the performance warnings there.
Logvinenko, A. D.
An object-color space.
Journal of Vision.
9(11):5, 1-23, (2009).https://jov.arvojournals.org/article.aspx?articleid=2203976
.
doi:10.1167/9.11.5.
Godau, Christoph and Brian Funt. XYZ to ADL: Calculating Logvinenko's Object Color Coordinates. Proceedings Eighteenth IS&T Color Imaging Conference. San Antonio. Nov 2009.
type()
,
probeOptimalColors()
,
vignette Plotting Chromaticity Loci of Optimal Colors
D50.eye = product( D50.5nm, 'varmat', xyz1931.1nm, wave=seq(360,830,by=5) ) computeADL( D50.eye, c(30,50,70) ) ## response.X response.Y response.Z ADL.alpha ADL.delta ADL.lambda omega ## 1 30 50 70 0.7371475 0.5384104 473.3594572 0.3008817 ## lambda.1 lambda.2 ## 427.2011 555.5261 ## since alpha < 1, XYZ=c(30,50,70) is *inside* the object-color solid of D50.eye
D50.eye = product( D50.5nm, 'varmat', xyz1931.1nm, wave=seq(360,830,by=5) ) computeADL( D50.eye, c(30,50,70) ) ## response.X response.Y response.Z ADL.alpha ADL.delta ADL.lambda omega ## 1 30 50 70 0.7371475 0.5384104 473.3594572 0.3008817 ## lambda.1 lambda.2 ## 427.2011 555.5261 ## since alpha < 1, XYZ=c(30,50,70) is *inside* the object-color solid of D50.eye
Compute the CCT, in K,
of a colorSpec object with type
equal to 'light'
## S3 method for class 'colorSpec' computeCCT( x, isotherms='robertson', locus='robertson', strict=FALSE )
## S3 method for class 'colorSpec' computeCCT( x, isotherms='robertson', locus='robertson', strict=FALSE )
x |
an colorSpec R object with |
isotherms |
A character vector whose elements match one
of the available isotherm families:
|
locus |
valid values are |
strict |
The CIE considers the CCT of a chromaticity |
In computeCCT()
, for each spectrum,
XYZ is computed using xyz1931.1nm
,
and the result passed to spacesXYZ::CCTfromXYZ()
.
If the quantity
of x
is 'photons'
(actinometric) each spectrum is
converted to 'energy'
(radiometric) on the fly.
computeCCT()
returns a numeric vector of length M,
where M is the number of spectra in x
.
The vector's names
is set to specnames(x)
.
If the type
of x
is not 'light'
,
then a warning is issued and all values are NA_real_
.
McCamy, C. S. Correlated color temperature as an explicit function of chromaticity coordinates. Color Research & Application. Volume 17. Issue 2. pages 142-144. April 1992.
Robertson, A. R. Computation of correlated color temperature and distribution temperature. Journal of the Optical Society of America. 58. pp. 1528-1535 (1968).
Wyszecki, Günther and W. S. Stiles. Color Science: Concepts and Methods, Quantitative Data and Formulae, Second Edition. John Wiley & Sons, 1982. Table 1(3.11). pp. 227-228.
type()
,
quantity()
,
xyz1931
,
planckSpectra()
,
specnames()
,
spacesXYZ::CCTfromXYZ()
computeCCT( D65.1nm ) # returns 6502.068 computeCCT( D65.1nm, isotherms='native' ) # returns 6503.323 computeCCT( A.1nm ) # returns 2855.656 computeCCT( A.1nm, isotherms='native' ) # returns 2855.662 computeCCT( A.1nm, isotherms='mccamy' ) # returns 2857.188 moon = readSpectra( system.file( "extdata/sources/moonlight.txt", package='colorSpec' ) ) computeCCT( moon ) # returns 4482.371
computeCCT( D65.1nm ) # returns 6502.068 computeCCT( D65.1nm, isotherms='native' ) # returns 6503.323 computeCCT( A.1nm ) # returns 2855.656 computeCCT( A.1nm, isotherms='native' ) # returns 2855.662 computeCCT( A.1nm, isotherms='mccamy' ) # returns 2857.188 moon = readSpectra( system.file( "extdata/sources/moonlight.txt", package='colorSpec' ) ) computeCCT( moon ) # returns 4482.371
Compute the CIE 1974 color rendering index (CRI) of a light spectrum,
called the the test illuminant.
From the given spectrum a reference illuminant is selected with the same CCT
(Correlated Color Temperature).
A selected set of 8 color samples is rendered in XYZ (1931) with both illuminants
and 8 color differences are computed in a special CIEUVW color space.
For each color difference a CRI is computed, where 100 is a perfect color match.
The final CRI is the average of these 8 CRI values.
## S3 method for class 'colorSpec' computeCRI( x, adapt=TRUE, attach=FALSE, tol=5.4e-3 )
## S3 method for class 'colorSpec' computeCRI( x, adapt=TRUE, attach=FALSE, tol=5.4e-3 )
x |
an colorSpec R object with |
adapt |
if |
attach |
if |
tol |
for the CRI to be meaningful the chromaticities of the test and reference illuminants must be sufficiently close in the CIE
1960 uniform chromaticity space.
If the tolerance is exceeded, the function returns |
The CCT of x
is computed by computeCCT()
with default options.
If adapt
is TRUE
the 8 test uv
points
are chromatically adapted from the test illuminant to the reference illuminant
using a special von Kries type transformation; see Oleari and
Wikipedia.
The test UVW values are computed relative to the reference illuminant.
If adapt
is FALSE
the 8 test uv
points
are not chromatically adapted,
and the test UVW values are computed relative to the test illuminant.
computeCRI()
returns a single number 100.
In case of ERROR it returns
NA
.
If attach
is TRUE
a large list
of intermediate calculations is attached to the returned number.
The test color reflectance spectra are taken from:
http://www.lrc.rpi.edu/programs/nlpip/lightinganswers/lightsources/scripts/NLPIP_LightSourceColor_Script.m
Oleari, Claudio, Gabriele Simone. Standard Colorimetry: Definitions, Algorithms and Software. John Wiley. 2016. pp. 465-470.
Günther Wyszecki and W. S. Stiles. Color Science: Concepts and Methods, Quantitative Data and Formulae, Second Edition. John Wiley & Sons, 1982. Table 1(3.11). p. 828.
Wikipedia. Color rendering index. https://en.wikipedia.org/wiki/Color_rendering_index
Hunt, R. W. G. and M. R. Pointer. Measuring Colour. 4th edition. John Wiley & Sons. 2011. Appendix 7.
computeCRI( subset(Fs.5nm,'F2') ) # returns 64.15195 computeCRI( subset(Fs.5nm,'F4') ) # returns 51.36348
computeCRI( subset(Fs.5nm,'F2') ) # returns 64.15195 computeCRI( subset(Fs.5nm,'F4') ) # returns 51.36348
Compute the Spectrum Similarity Index (SSI), an index between 0 and 100,
of a colorSpec object with type
equal to 'light'
.
It compares a test spectrum with a reference spectrum (an ideal).
The value 100 means a perfect match to the reference,
and a smaller value mean a poorer match (similar to CRI).
Only values in the interval [375,675] nm are used; for details see Holm.
## S3 method for class 'colorSpec' computeSSI( x, reference=NULL, digits=0, isotherms='mccamy', locus='robertson' )
## S3 method for class 'colorSpec' computeSSI( x, reference=NULL, digits=0, isotherms='mccamy', locus='robertson' )
x |
a colorSpec object with |
reference |
a colorSpec object with |
digits |
the number of digits after the decimal point in the returned vector.
According to Holm the output should be rounded to the nearest integer,
which corresponds to |
isotherms |
this is only used when |
locus |
this is only used when |
If reference
contains a single spectrum,
then each test spectrum is compared to that one reference spectrum.
If reference
contains M spectra,
then the i'th test spectrum is compared to the i'th reference spectrum.
If reference=NULL
then for each test spectrum
the CCT is computed and used to compute a reference spectrum with the same CCT.
It is either a Planckian (black-body) or daylight illuminant, see Holm for details.
The test spectrum and auto-computed reference spectrum are then compared.
computeSSI()
returns a numeric vector of length M,
where M is the number of spectra in x
.
The vector's names
is set from specnames(x)
and a compact code for the corresponding reference spectrum.
If the type
of x
is not 'light'
,
or reference
is not valid,
then the function returns NULL.
J. Holm and T. Maier and P. Debevec and C. LeGendre and J. Pines and J. Erland and G. Joblove and S. Dyer and B. Sloan and J. di Gennaro and D. Sherlock. A Cinematographic Spectral Similarity Index. SMPTE 2016 Annual Technical Conference and Exhibition. pp. 1-36. (2016).
type()
,
computeCCT()
,
planckSpectra()
,
daylightSpectra()
,
specnames()
computeSSI( planckSpectra( 1000*(2:6) ) ) ## P2000_SSI[2027K] P3000_SSI[3057K] P4000_SSI[D4063] P5000_SSI[D5061] P6000_SSI[D6063] ## 99 98 93 92 92
computeSSI( planckSpectra( 1000*(2:6) ) ) ## P2000_SSI[2027K] P3000_SSI[3057K] P4000_SSI[D4063] P5000_SSI[D5061] P6000_SSI[D6063] ## 99 98 93 92 92
This function convolves each spectrum in a colorSpec object with a kernel of odd length. Its primary purpose is to correct raw spectrometer data (with positive instrumental bandwidth) to have bandwidth=0. Two popular correction kernels for this, with lengths 3 and 5, are built-in options, see Details.
## S3 method for class 'colorSpec' convolvewith( x, coeff )
## S3 method for class 'colorSpec' convolvewith( x, coeff )
x |
a colorSpec object with N wavelengths |
coeff |
a convolution kernel of odd length. The center entry of this vector is taken
as index 0 in the convolution. |
The built-in kernels, 'SS3'
and 'SS5'
, were derived
by Stearns & Stearns under specific hypotheses on the
spectrometer profile, bandpass, and pitch; see References.
Missing values at both ends are filled by copying from the nearest valid value.
The function creates a function calling stats::filter()
and passes that function to applyspec()
.
a colorSpec object with the same dimensions,
wavelength
, quantity
, and organization
.
If coeff
is invalid it is an ERROR and
convolvewith()
returns NULL
.
Stearns, E.I., Stearns R.E. An example of a method for correction radiance data for bandpass error. Color Research and Application. 13-4. 257-259. 1988.
Schanda, Janos. CIE Colorimetry, in Colorimetry: Understanding the CIE System. Wiley Interscience. 2007. p. 124.
Oleari, Claudio, Gabriele Simone. Standard Colorimetry: Definitions, Algorithms and Software. John Wiley. 2016. p. 309.
quantity
,
wavelength
,
linearize
,
applyspec
,
organization
functions for extracting the core data contained in a colorSpec object.
## S3 method for class 'colorSpec' coredata( x, forcemat=FALSE ) ## S3 method for class 'colorSpec' as.matrix( x, ... )
## S3 method for class 'colorSpec' coredata( x, forcemat=FALSE ) ## S3 method for class 'colorSpec' as.matrix( x, ... )
x |
a colorSpec object |
forcemat |
if |
... |
extra arguments ignored |
coredata |
If |
as.matrix |
a wrapper for |
colorSpec has a few options. The options are stored in the R global list and they are:
colorSpec.loglevel
,
colorSpec.logformat
, and
colorSpec.stoponerror
For details on what they do see logging
.
They can be set using the built-in function options()
.
When R starts up,
an option can be set using a call to options()
in the file Rprofile.site.
If colorSpec is later loaded, the value of the option will not be changed.
If an option has not been assigned, then it is created with a default value.
The function cs.options()
makes setting the options a little easier
in a few ways:
it automatically prepends the string 'colorSpec.'
partial matching of the option name is enabled
a warning is issued when the option value has the wrong type
cs.options( ... )
cs.options( ... )
... |
named arguments are set; unnamed arguments are ignored with a warning. See Examples. |
returns a list with all the colorSpec options.
cs.options( loglevel="DEBUG", stop=FALSE ) # 'stop' partially matches 'stoponerror' cs.options( stop='TRUE' ) # warns that value has the wrong type cs.options( stop=FALSE, "DEBUG" ) # warns that the 2nd argument has no name cs.options( loglevel="WARN" ) # back to default
cs.options( loglevel="DEBUG", stop=FALSE ) # 'stop' partially matches 'stoponerror' cs.options( stop='TRUE' ) # warns that value has the wrong type cs.options( stop=FALSE, "DEBUG" ) # warns that the 2nd argument has no name cs.options( loglevel="WARN" ) # back to default
D50.5nm |
standard Illuminant D50, from 300 to 830 nm at 5 nm intervals. |
A colorSpec object organized as a vector, with 107 data points
and specnames
equal to 'D50'
.
This spectrum is not copied from a table from a CIE publication, though it does match such a table.
It is computed using the function daylightSpectra()
by following
the special CIE recipe given in the References.
The temperature is set to (14388/14380) * 5000 = 5002.781 Kelvin.
The coefficients of the daylight components , and
are
rounded to 3 decimal places.
This linear combination is computed at 10nm intervals and then linearly interpolated to 5nm intervals.
The result is normalized to value 1 at 560nm (instead of the usual 100),
and finally rounded to 5 decimal places. See Examples.
Günther Wyszecki and W.S. Stiles. Color Science : Concepts and Methods, Quantitative Data and Formulae. Second Edition. Wiley-Interscience. 1982. Table I(3.3.4) pp. 754-758
CIE 15: Technical Report: Colorimetry, 3rd edition. CIE 15:2004. Table T.1, pp 30-32, and Note 5 on page 69.
Schanda, Janos. CIE Colorimetry, in Colorimetry: Understanding the CIE System. Wiley Interscience. 2007. p. 42.
ABC
,
D65
,
daylightSpectra
# the CIE recipe for computing D50.5nm correction = 14388 / 14380 # note 5, page 69 in CIE 15:2004 D50.10nm = daylightSpectra( correction*5000, wavelength=seq(300,830,by=10), roundMs=TRUE ) D50.5nm = resample( D50.10nm, seq(300,830,by=5), method='linear' ) D50.5nm = round( D50.5nm, 5 ) summary( D50.5nm ) white.point = product( D50.5nm, xyz1931.1nm, wave='auto' )
# the CIE recipe for computing D50.5nm correction = 14388 / 14380 # note 5, page 69 in CIE 15:2004 D50.10nm = daylightSpectra( correction*5000, wavelength=seq(300,830,by=10), roundMs=TRUE ) D50.5nm = resample( D50.10nm, seq(300,830,by=5), method='linear' ) D50.5nm = round( D50.5nm, 5 ) summary( D50.5nm ) white.point = product( D50.5nm, xyz1931.1nm, wave='auto' )
D65.1nm |
standard Illuminant D65, 300 to 830 nm at 1 nm intervals |
D65.5nm |
standard Illuminant D65, 380 to 780 nm at 5 nm intervals |
Each is a colorSpec object organized as a vector,
with specnames
equal to 'D65'
.
Both of these have been divided by 100, to make the values at 560nm equal to 1 instead of 100.
Günther Wyszecki and W.S. Stiles. Color Science : Concepts and Methods, Quantitative Data and Formulae. Second Edition. Wiley-Interscience. 1982. Table I(3.3.4) pp. 754-758
ASTM E 308-01. Standard Practice for Computing the Colors of Objects by Using the CIE System. Table 3. pages 3-4.
ABC
, D50
, daylightSpectra
, daylight
summary( D65.1nm ) white.point = product( D65.1nm, xyz1931.1nm, wave='auto' )
summary( D65.1nm ) white.point = product( D65.1nm, xyz1931.1nm, wave='auto' )
daylight1964 |
spectral components ; from 300 to 830 nm at 5 nm intervals |
daylight2013 |
smoothed spectral components ; from 300 to 830 nm at 1 nm intervals |
Each is a colorSpec object organized as a matrix with 3 columns
S0 |
component 0, the mean power spectrum |
S1 |
component 1, the 1st characteristic spectrum |
S2 |
component 2, the 2nd characteristic spectrum |
http://www.cie.co.at/publ/abst/datatables15_2004/CIE_sel_colorimetric_tables.xls
http://vision.vein.hu/~schanda/CIE%20TC1-74/
Günther Wyszecki and W.S. Stiles. Color Science : Concepts and Methods, Quantitative Data and Formulae. Second Edition. Wiley-Interscience. 1982. Table V(3.3.4) p. 762.
Smoothing spectral power distribution of daylights. Zsolt Kosztyan and Janos Schanda. Color Research & Application. Volume 38, Issue 5, pages 316-321, October 2013.
CIE 15: Technical Report: Colorimetry, 3rd edition. CIE 15:2004. Table T.2, pp 33-34
JUDD, D.B., MACADAM, D.L. and WYSZECKI, G., with the collaboration of BUDDE, H.W, CONDIT, H.R, HENDERSON, S.T, and SIMONDS, J.L. Spectral distribution of typical daylight as a function of correlated color temperature. J Opt. Soc. Am. 54, 1031-1040, 1964.
Zsolt Kosztyan and Janos Schanda. Smoothing spectral power distribution of daylights. Color Research & Application. Volume 38, Issue 5, pages 316-321, October 2013.
summary( daylight1964 ) day1964 = daylightSpectra( c(5000,6500), comp=daylight1964 ) day2013 = daylightSpectra( c(5000,6500), comp=daylight2013 ) plot( day1964, col='black' ) plot( day2013, col='black', add=TRUE )
summary( daylight1964 ) day1964 = daylightSpectra( c(5000,6500), comp=daylight1964 ) day2013 = daylightSpectra( c(5000,6500), comp=daylight2013 ) plot( day1964, col='black' ) plot( day2013, col='black', add=TRUE )
All RGB displays have a non-linear "gamma function" of some sort. This function converts from linear RGB to an RGB appropriate for the gamma function of the display; which is also called the electro-optical conversion function (EOCF).
DisplayRGBfromLinearRGB( RGB, gamma='sRGB' )
DisplayRGBfromLinearRGB( RGB, gamma='sRGB' )
RGB |
linear RGB values organized as a vector or matrix of any size; all 3 channels are treated the same way so size does not matter |
gamma |
either the string |
The function first clamps the input RGB
to the interval [0,1].
If gamma='sRGB'
(not case-sensitive) it then maps [0,1] to [0,1] using the special
piecewise-defined sRGB function, see Wikipedia.
In case gamma
is a positive number, the function raises all values to the power 1/gamma
.
The dimensions and names of the input are copied to the output.
In case of error, the function returns the clamped input values.
This function is deprecated.
New software should use spacesRGB::SignalRGBfromLinearRGB()
instead.
Wikipedia. sRGB. https://en.wikipedia.org/wiki/SRGB
DisplayRGBfromLinearRGB( c(0.2, 0.5) ) # [1] 0.4845292 0.7353570 # this is display sRGB, in [0,1] DisplayRGBfromLinearRGB( c(-0.1, 0.2, 0.5, 1), 2.2 ) # [1] 0.0000000 0.4811565 0.7297401 1.0000000 # gamma=2.2 x = seq( 0, 1, len=101) plot( x, DisplayRGBfromLinearRGB(x), type='l' )
DisplayRGBfromLinearRGB( c(0.2, 0.5) ) # [1] 0.4845292 0.7353570 # this is display sRGB, in [0,1] DisplayRGBfromLinearRGB( c(-0.1, 0.2, 0.5, 1), 2.2 ) # [1] 0.0000000 0.4811565 0.7297401 1.0000000 # gamma=2.2 x = seq( 0, 1, len=101) plot( x, DisplayRGBfromLinearRGB(x), type='l' )
The two possible modifications are:
pre-multiplication by a transmitting filter
post-multiplication by a matrix
Both of these are optional.
If neither of these modifications is enabled, the original x
is returned.
## S3 method for class 'colorSpec' emulate( x, y, filter=FALSE, matrix=TRUE )
## S3 method for class 'colorSpec' emulate( x, y, filter=FALSE, matrix=TRUE )
x |
a colorSpec responder with M spectra, to be modified.
The |
y |
a colorSpec responder with N spectra, to be emulated by a modified |
filter |
enable filter pre-multiplication. |
matrix |
enable matrix post-multiplication.
If |
If filter=FALSE
and matrix=TRUE
then
the returned value is multiply(x,A)
,
where the matrix A
is computed to minimize the difference with
y
, in the least squares sense (Frobenius matrix norm).
The function ginv()
is used here.
If filter=TRUE
and matrix=FALSE
then
the returned value is product(filter,x)
,
where the object filter
is computed to minimize the difference with
y
, in the least squares sense (Frobenius matrix norm).
This calculation is fairly straightforward,
but requires that the responsivity of x
does not vanish at any wavelength.
It also requires that M=N
.
The computed filter may be unrealistic, i.e. the transmittance may be > 1.
If this happens a WARN
message is issued.
If filter=TRUE
and matrix=TRUE
then
the returned value is product(filter,multiply(x,A))
,
where (filter,A)
are chosen with the above minimization criterion.
If N=1
then we must have M=1
as well;
the calculation is trivial and the emulation is exact.
If N
2
,
the calculation is iterative - solving alternatively for
filter
and A
until convergence.
The function ginv()
is used on each iteration.
This is a bilinear optimization.
If convergence fails, it is an error and the function returns NULL
.
If convergence succeeds, there is 1 degree of freedom in the (filter,A)
pair.
If one is scaled by a positive constant, the other can be scaled by the inverse,
and the returned object is the same.
The filter is scaled so the maximum transmittance is 1.
If filter=FALSE
and matrix=FALSE
then
the original x
is returned, with a WARN
message.
a colorSpec object close to y
, as in Details.
The quantity
is the same as y
.
The specnames()
are the same as those of y
,
except that ".em"
is appended to each one.
The function attaches attribute "emulate"
,
whose value is a list containing filter
and/or A
as appropriate.
see the vignette Emulation of one Camera by another Camera
wavelength
,
type
,
quantity
,
multiply
,
product
,
specnames
Retrieve or set the extradata of a colorSpec object.
## S3 method for class 'colorSpec' extradata(x) ## S3 replacement method for class 'colorSpec' extradata(x,add=FALSE) <- value
## S3 method for class 'colorSpec' extradata(x) ## S3 replacement method for class 'colorSpec' extradata(x,add=FALSE) <- value
x |
a colorSpec object with M spectra |
value |
a |
add |
If |
If the organization
of x
is not 'df.row'
,
then extradata
cannot be stored in x
and the assignment is ignored, with a warning.
First change the organization
to 'df.row'
,
and then assign the extradata
.
If the organization
of x
is 'df.row'
, but value
does not have
the right number of rows, the assignment is ignored, with a warning.
extradata(x)
returns a data.frame
with M rows,
where M is the number of spectra in x
.
The rownames
are set to the specnames
of x
.
If there is no extra data then the number of columns in this data.frame
is 0.
Do not confuse extradata
and metadata
.metadata
is unstructured data that is attached to the entire colorSpec object.
extradata
is structured data, with a row of data for each spectrum in the object.
F96T12
Sylvania F96T12 CW/VHO 215-Watt fluorescent bulb photon irradiance,
measured with a LI-COR LI-1800 spectroradiometer,
from 300 to 900 nm at 1 nm intervals.
A colorSpec object organized as a vector, with 601 data points
and specnames
equal to 'F96T12'
.
The unit is (mole of photons)
.
Pedro J. Aphalo. https://www.mv.helsinki.fi/home/aphalo/photobio/lamps.html
ABC
,
D65
,
daylightSpectra
sum( F96T12 ) # [1] 320.1132 photon irradiance, (micromoles of photons)*m^{-2} sum( radiometric(F96T12) ) # [1] 68.91819 irradiance, watts*m^{-2}
sum( F96T12 ) # [1] 320.1132 photon irradiance, (micromoles of photons)*m^{-2} sum( radiometric(F96T12) ) # [1] 68.91819 irradiance, watts*m^{-2}
Flea2.RGB |
an RGB responder to light, from 360 to 800 nm at 10 nm intervals |
A colorSpec object with quantity
equal to 'energy->electrical'
and 3 spectra:
Red
, Green
, and Blue
.
This data is read from the file Flea2-spectral.txt which was digitized from the
plot in Flea2-spectral.png.
https://ptgreycamera.com/product/camera/flir/flea2/firewireb-flea2/fl2-14s3c-c/
quantity
,
vignette Blue Flame and Green Comet
# Make a scanner from a tungsten source and a Flea2 camera Flea2.scanner = product( A.1nm, "VARMATERIAL", Flea2.RGB, wavelength=420:680 ) Flea2.scanner = calibrate( Flea2.scanner )
# Make a scanner from a tungsten source and a Flea2 camera Flea2.scanner = product( A.1nm, "VARMATERIAL", Flea2.RGB, wavelength=420:680 ) Flea2.scanner = calibrate( Flea2.scanner )
Fs.5nm
contains 12 CIE Fluorescent Illuminants, from 380 to 780 nm, at 5nm intervals.
Fs.5nm
is a colorSpec object with 12 spectra.
It is organized as a data frame with quantity
equal to "energy".
The series F illuminants do not seem to be normalized in a consistent way.
http://www.rit-mcsl.org/UsefulData/Fluorescents.htm
# plot only F4 plot( subset(Fs.5nm,"F4") )
# plot only F4 plot( subset(Fs.5nm,"F4") )
HigherPasserines |
Tetrachromatic Cone Fundamentals of Higher Passerine Birds |
A colorSpec object organized as a matrix with the 4 spectra:
UV |
the UV wavelength responsivity |
Short |
the short wavelength responsivity |
Medium |
the medium wavelength responsivity |
Long |
the long wavelength responsivity |
The wavelength
is from 300 to 700 nm, at 1nm intervals.
https://onlinelibrary.wiley.com/doi/full/10.1111/j.1095-8312.2005.00540.x
Endler & Mielke. Comparing entire colour patterns as birds see them. Biological Journal of the Linnean Society. Volume 86, Issue 4, pages 405-431, December 2005. Original Name of File: BIJ_540_Endler_Mielke_OnlineAppendix.txt.
summary(HigherPasserines)
summary(HigherPasserines)
Hoya |
4 standard Hoya filters; from 300 to 750 nm at 10nm intervals. |
A colorSpec object with quantity
equal to 'transmittance'
and 4 spectra:
R-60 |
long-pass red filter with cutoff about 600nm |
G-533 |
band-pass green filter with peak about 533nm |
B-440 |
band-pass blue filter with peak about 440nm |
LB-120 |
Light-balancing Blue filter with mired shift equal to -120 |
# compute response of ACES scanner to the Hoya filters product( Hoya, scanner.ACES, wave='auto' )
# compute response of ACES scanner to the Hoya filters product( Hoya, scanner.ACES, wave='auto' )
interpolate along a 1-parameter path of spectra
## S3 method for class 'colorSpec' interpolate( x, p, pout, pname=deparse(substitute(p)) )
## S3 method for class 'colorSpec' interpolate( x, p, pout, pname=deparse(substitute(p)) )
x |
a colorSpec object, typically with multiple spectra |
p |
a numeric vector with |
pout |
a numeric vector of parameter values at which interpolation of the spectra in |
pname |
the name of the parameter |
Each spectrum in x
can be thought of as a point in a high-dimensional space,
and each point has a real-valued parameter associated with it.
The function performs natural spline interpolation on these points,
one coordinate at a time.
For each wavelength value it calls spline
with method='natural'
.
interpolate(x)
returns a colorSpec object y
with a spectrum for each value in pout
.
The organization of y
is 'df.row'
,
and extradata(y)
has a single column which is a copy of pout
.
The name of the column is pname
.
The names in specnames(y)
are <pname>=<pout>
.
Other properties of y
,
e.g. wavelength
, quantity
, ...,
are the same as x
.
In case of ERROR, the function returns NULL
.
organization
,
wavelength
,
extradata
,
spline
path = system.file( "extdata/stains/PhenolRed-Fig7.txt", package="colorSpec" ) wave = 350:650 phenolred = readSpectra( path, wavelength=wave ) pH = as.numeric( sub( '[^0-9]+([0-9]+)$', '\\1', specnames(phenolred) ) ) pHvec = seq(min(pH),max(pH),by=0.05) phenolinterp = interpolate( phenolred, pH, pHvec )
path = system.file( "extdata/stains/PhenolRed-Fig7.txt", package="colorSpec" ) wave = 350:650 phenolred = readSpectra( path, wavelength=wave ) pH = as.numeric( sub( '[^0-9]+([0-9]+)$', '\\1', specnames(phenolred) ) ) pHvec = seq(min(pH),max(pH),by=0.05) phenolinterp = interpolate( phenolred, pH, pHvec )
Given a light responder (e.g. an eye or a camera), two light spectra that produce the same response from the responder are called metamers for that responder. Similarly, given a material responder (e.g. a scanner), two reflectance spectra that produce the same response from the responder are called metamers for that responder.
For a given responder and response,
there are typically infinitely many metamers.
The set of all of them is often called the metameric suite.
The goal of the function invert()
is to calculate a "good" metamer
in the "suite".
Koenderink calls this topic inverse colorimetry.
In the case that the estimated spectrum is a reflectance spectrum,
the topic is often called reflectance estimation or reflectance recovery,
see Bianco.
The centroid method, which is the default and the featured method in this package, computes the centroid of the set of all the metamers (if any). The centroid is computed in an infinite-dimensional context and is expounded further in Davis.
The Hawkyard method, see Hawkyard and Bianco, has been around a long time. The centroid and Hawkyard methods have similarities, e.g. both are low-dimensional with the number of variables equal to the number of responses (usually 3). The Hawkyard method is very fast, but has a key problem, see below.
The Transformed Least Slope Squared (TLSS) method was developed
by Scott Burns, see References.
This is my name for it, not his.
What I call TLLS is actually is a combination of Burns' LHTSS and LLSS methods;
the one that invert()
chooses depends on type(x)
, see below.
Both of these are high-dimensional,
with the number of variables equal to #(responses) + #(wavelengths).
The first argument to invert()
is the responder x
,
and the second is the matrix response
of responses (e.g. XYZs or RGBs).
The goal is to return a "good" spectrum for each response so that:
product( invert(x,response), x ) response
|
The error is returned as column estim.precis
, see below.
First consider the case where x
has type type='responsivity.material'
.
The goal is to compute a reflectance spectra.
All the methods will fail if the response is on the object-color boundary
(an optimal color) or outside the boundary.
They may also fail if the response is inside the object-color
solid (the Rösch Farbkörper) and very close to the boundary.
The centroid method solves a non-linear system that contains a
Langevin-function-based squashing function, see Davis for details.
When successful it always returns a feasible spectrum
with small estim.precis
.
The Hawkyard method is linear and very fast,
but in raw form it may return a non-feasible reflectance spectrum.
In this case invert()
simply clamps to the interval [0,1] and so
estim.precis
can be large.
The TLSS method solves a non-linear system that contains
the squashing function , see Burns for details.
When successful it always returns a feasible spectrum
with small
estim.precis
.
Now consider the case where x
has type='responsivity.light'
.
The goal is to compute the spectrum of a light source.
All the methods will fail if chromaticity of the response is on the boundary
of the inverted-U (assuming x
models the human eye) or outside the boundary.
They may also fail if the response is inside the inverted-U
and very close to the boundary.
The centroid method works on a relatively small range of chromaticities;
it will fail if the response is too far from the response to Illuminant E.
See Davis for the details.
When successful it always returns an everywhere positive spectrum
with small estim.precis
.
This method has the desirable property that if the response is multiplied by
a positive number, the computed spectrum is multiplied by that same number.
The Hawkyard method does not work in this case.
The TLSS method solves a non-linear system that contains
the squashing function , see Burns for the details.
When successful it always returns an everywhere positive spectrum
with small
estim.precis
.
This method succeeds on a larger set of chromaticities than the centroid method.
It also has the desirable scaling multiplication property mentioned above.
The centroid and Hawkyard methods have an equalization option,
which is controlled by the argument alpha
and is enabled by default, see below.
When enabled, if the response comes from a constant spectrum
(a perfectly neutral gray material, or a multiple of Illuminant E),
then the computed spectrum is that same constant spectrum (up to numerical precision).
I call this the neutral-exact property.
Equalization is a complicated mechanism, for details see Davis.
For the TLSS method, the neutral-exact property is intrinsic,
and alpha
is ignored.
NOTE:
If the responder has only one output channel (e.g. a monochrome camera)
and equalization is enabled,
then all responses are inverted to a constant spectrum.
This may or may not be desirable.
## S3 method for class 'colorSpec' invert( x, response, method='centroid', alpha=1 )
## S3 method for class 'colorSpec' invert( x, response, method='centroid', alpha=1 )
x |
a colorSpec object with |
response |
a numeric NxM matrix, or a numeric vector that can be converted
to such matrix, by row. The N responses are contained in the rows.
The |
method |
either |
alpha |
a vector of M weighting coefficients,
or a single number that is replicated to length M.
When |
For method='centroid'
the function calls the non-linear root-finder
rootSolve::multiroot()
,
which is general purpose and "full Newton".
For method='Hawkyard'
the function solves a linear system by
inverting a small matrix (#[responses] x #[responses]).
The spectra are then clamped to [0,1].
For method='TLSS'
the function solves a constrained least-squares problem
using Lagrange multipliers.
A critical point is found using a "full Newton" iteration.
The original MATLAB code is posted at Burns,
and was ported from MATLAB to R with only trivial changes.
When computing a reflectance spectrum, the Hawkyard method is used for the
initial guess, after little extra clamping.
This improved guess cuts the number of iterations substantially,
and the extra computation time is negligible.
If type(x)='responsivity.material'
it returns a colorSpec object
with type
= 'material'
(quantity
= 'reflectance'
).
If type(x)='responsivity.light'
it returns a colorSpec object
with type
= 'light'
(quantity
='energy'
or quantity
='photons'
depending on quantity(x)
).
In either case, the returned object has organization
= 'df.row'
and the extradata
is a data.frame
with these columns:
response |
the input matrix of desired responses |
estim.precis |
the difference between the desired response and actual response. It is the mean of the absolute value of the differences.
See |
time.msec |
the time to compute the spectrum, in msec.
When |
iters |
the number of iterations that were required to find the relevant root.
This is not present when |
clamped |
a logical indicating whether the reflectance was clamped to [0,1]. This is present only when |
If a response could not be estimated,
the row contains NA
in appropriate columns,
and a warning is issued.
In case of global error, the function returns NULL
.
If type(x)='responsivity.light'
the centroid method may fail
(not converge) if the response is too far from that of Illuminant E.
Davis, Glenn. A Centroid for Sections of a Cube in a Function Space, with Application to Colorimetry. https://arxiv.org/abs/1811.00990. [math.FA]. 2018.
Bianco, Simone. Reflectance spectra recovery from tristimulus values by adaptive estimation with metameric shape correction. vol. 27, no 8. Journal of the Optical Society of America A. pages 1868-1877. 2010 https://opg.optica.org/josaa/abstract.cfm?uri=josaa-27-8-1868.
Burns, Scott A. Generating Reflectance Curves from sRGB Triplets. http://scottburns.us/reflectance-curves-from-srgb/.
Hawkyard, C. J. Synthetic reflectance curves by additive mixing. Journal of the Society of Dyers and Colourists. vol. 109. no. 10. Blackwell Publishing Ltd. pp. 323-329. 1993.
Koenderink, J.J. Color for the Sciences. MIT Press. 2010.
type()
,
quantity()
,
organization()
,
specnames()
,
product()
,
extradata()
,
rootSolve::multiroot()
,
vignette Estimating a Spectrum from its Response
wave = 400:700 E.eye = product( illuminantE(1,wave), "material", xyz1931.1nm, wavelength=wave ) path = system.file( 'extdata/targets/CC_Avg30_spectrum_CGATS.txt', package='colorSpec' ) MacbethCC = readSpectra( path, wavelength=wave ) XYZ = product( MacbethCC, E.eye, wavelength=wave ) est.eq = invert( E.eye, XYZ, method='centroid', alpha=1 ) extra = extradata(est.eq) range(extra$estim.precis) # prints 0.000000e+00 3.191741e-08
wave = 400:700 E.eye = product( illuminantE(1,wave), "material", xyz1931.1nm, wavelength=wave ) path = system.file( 'extdata/targets/CC_Avg30_spectrum_CGATS.txt', package='colorSpec' ) MacbethCC = readSpectra( path, wavelength=wave ) XYZ = product( MacbethCC, E.eye, wavelength=wave ) est.eq = invert( E.eye, XYZ, method='centroid', alpha=1 ) extra = extradata(est.eq) range(extra$estim.precis) # prints 0.000000e+00 3.191741e-08
Some action spectra standards are defined by simple equations; the erythemal spectrum for human sunburn is one of them.
erythemalSpectrum( wavelength=250:400 )
erythemalSpectrum( wavelength=250:400 )
wavelength |
a vector of wavelengths, in nm |
This erythemal spectrum is defined in 4 pieces:
,
,
, and
. The unit is nm.
The spectrum is used in the definition of the international standard UV Index.
For erythemalSpectrum()
A colorSpec object with quantity
equal to 'energy->action'
.
The responsivity is 0 for > 400 nm, so this putting this spectrum in the category
of human vision is a bit of a stretch.
https://en.wikipedia.org/wiki/Ultraviolet_index
McKinlay, A.F., and B.L. Diffey. A reference action spectrum for ultraviolet induced erythema in human skin. CIE Res. Note, 6(1), 17-22. (1987)
daylight,
quantity
,
materialSpectra,
lightSpectra
Two families of standard illuminants that are parameterized by temperature are the Planckian spectra (black-body spectra), and daylight spectra. For the daylight spectra, a smoothed version is available. Illuminant E, a third and trivial spectrum, is also available.
planckSpectra( temperature, wavelength=300:830, normalize=TRUE, c2=1.4388e-2 ) daylightSpectra( temperature, wavelength=NULL, components=colorSpec::daylight1964, roundMs=FALSE ) illuminantE( energy=1, wavelength=380:780 )
planckSpectra( temperature, wavelength=300:830, normalize=TRUE, c2=1.4388e-2 ) daylightSpectra( temperature, wavelength=NULL, components=colorSpec::daylight1964, roundMs=FALSE ) illuminantE( energy=1, wavelength=380:780 )
temperature |
a vector of temperatures, in Kelvin |
wavelength |
a vector of wavelengths.
For |
normalize |
a logical value.
If |
c2 |
the value of |
components |
a colorSpec object with the daylight components |
roundMs |
a logical value. The original CIE method for the daylight spectra requires rounding intermediate coefficients M1 and M2 to 3 decimal places. This rounding is necessary to reproduce the tabulated values in Table T.1 of the CIE publication in References. |
energy |
a vector of energy levels |
For planckSpectra()
the valid range of temperatures is
0 to Inf
() K, but with exceptions at the endpoints.
For a negative temperature the spectrum is set to all
NA
s.
If temperature=0
and normalize=TRUE
, the spectrum is set to all NA
s.
If temperature=0
and normalize=FALSE
, the spectrum is set to all 0s.
Conversely, if temperature=Inf
and normalize=FALSE
,
the spectrum is set to all NA
s.
If temperature=Inf
and normalize=TRUE
,
the spectrum is set to the pointwise limit (which appears blue).
For daylightSpectra()
the valid range of temperatures is 4000 to 25000 K.
For a temperature outside this range the spectrum is set to all NA
s.
The equations for daylightSpectra()
and planckSpectra()
are complex
and can be found in the References.
IlluminantE()
is trivial - all constant energy.
For planckSpectra()
and daylightSpectra()
:
A colorSpec object with quantity
equal to 'energy'
,
and organization
equal to 'matrix'
or 'vector'
.
The specname
s are PNNNN
or DNNNN
for planckSpectra()
and daylightSpectra()
respectively.
The number of spectra in the object is the number of temperatures = length(temperature)
.
For illuminantE()
:
A colorSpec object with quantity
equal to 'energy'
.
The number of spectra in the object is the number of
energy levels = length(energy)
.
Günther Wyszecki and W.S. Stiles. Color Science : Concepts and Methods, Quantitative Data and Formulae. Second Edition. Wiley-Interscience. 1982. page 146.
CIE 15: Technical Report: Colorimetry, 3rd edition. CIE 15:2004. Table T.1, pp 30-32, and Note 5 on page 69.
Schanda, Janos. CIE Colorimetry, in Colorimetry: Understanding the CIE System. Wiley Interscience. 2007. p. 42.
daylight, resample
, organization
, quantity
,
materialSpectra
linearize spectra and return modified object
## S3 method for class 'colorSpec' linearize( x )
## S3 method for class 'colorSpec' linearize( x )
x |
a colorSpec object |
If the quantity(x)
is not 'absorbance'
then x
is returned unchanged.
If the quantity(x)
is 'absorbance'
then absorbance is converted to transmittance using
Surprisingly, there does not seem to be a similar logarithmic version of reflectance. Plots with log(responsivity) is somewhat common, but does not seem to have a separate name. I have not seen log(radiometric power).
linearize
returns a colorSpec object with linear quantities.
lms1971.5nm |
the Vos & Walraven (1971) 2° cone fundamentals from 380 to 780 nm, at 5nm intervals |
A colorSpec object organized as a matrix with 3 columns:
long |
the long wavelength responsivity |
medium |
the medium wavelength responsivity |
short |
the short wavelength responsivity |
http://www.cvrl.org/database/text/cones/vw.htm
Vos, J. J. & Walraven, P. L. On the derivation of the foveal receptor primaries. Vision Research. 11 (1971) pp. 799-818.
summary(lms1971.5nm) white.point = product( D65.1nm, lms1971.5nm, wave='auto' )
summary(lms1971.5nm) white.point = product( D65.1nm, lms1971.5nm, wave='auto' )
lms2000.1nm |
the Stockman and Sharpe (2000) 2° cone fundamentals from 390 to 830 nm, at 1nm intervals |
A colorSpec object organized as a matrix with 3 columns:
long |
the long wavelength responsivity |
medium |
the medium wavelength responsivity |
short |
the short wavelength responsivity |
Stockman, A., Sharpe, L. T., & Fach, C. C. (1999). The spectral sensitivity of the human short-wavelength cones. Vision Research. 39, 2901-2927.
Stockman, A., & Sharpe, L. T. (2000). Spectral sensitivities of the middle- and long-wavelength sensitive cones derived from measurements in observers of known genotype. Vision Research. 40, 1711-1737.
summary(lms2000.1nm) white.point = product( D65.1nm, lms2000.1nm, wave='auto' )
summary(lms2000.1nm) white.point = product( D65.1nm, lms2000.1nm, wave='auto' )
There is some flexibility in the colorSpec logging level and format.
Logging output goes to stderr()
, just like the message stream;
but see sink()
(and the pitfalls of using it).
colorSpec.loglevel
The levels are:
"FATAL", "ERROR", "WARN", "INFO", "DEBUG",
and "TRACE"
- the usual ones from Log4j.
The initial level is "WARN"
.
A "FATAL"
event usually means an internal package error.
When setting colorSpec.loglevel
an initial letter is sufficient.
colorSpec.logformat
The format is given by a string with standard Log4j conversion specifications:
%t |
the date/time of the logging event. | %t can be followed by standard strftime specs in braces; see example. |
%l |
the level of the logging event | |
%n |
namespace where event occurred | |
%f |
function where event occurred | |
%m |
the message itself | |
colorSpec.stoponerror
If the this option is TRUE
(the default),
a log event with level "ERROR"
stops execution;
otherwise, execution keeps going.
For interactive use, TRUE
is probably better.
For long batch jobs, FALSE
might be appropriate,
since then a single error may not force a complete repeat.
A "FATAL"
event always stops execution.
Wikipedia. Log4j. https://en.wikipedia.org/wiki/Log4j
options
,
cs.options
,
sink
,
stderr
options( colorSpec.logformat="%t{%H:%M:%OS3} %l %n::%f(). %m", colorSpec.stoponerror=TRUE ) # or equivalently cs.options( logformat="%t{%H:%M:%OS3} %l %n::%f(). %m", stop=TRUE )
options( colorSpec.logformat="%t{%H:%M:%OS3} %l %n::%f(). %m", colorSpec.stoponerror=TRUE ) # or equivalently cs.options( logformat="%t{%H:%M:%OS3} %l %n::%f(). %m", stop=TRUE )
luminsivity.1nm |
Four luminous efficiency functions, from 360 to 830 nm, at 1nm step |
A colorSpec object, with quantity 'energy->neural'
, and with 4 spectra:
photopic1924
The luminous efficiency function adopted by the CIE in 1924,
and defining the standard photopic observer.
It is only to be used when light levels are high enough
that the sensitivity of the eye is mediated by cones, and not rods.
It is the same as the y-bar function in xyz1931.1nm
.
It is used to define the candela in the International System (SI)
and is the only one of these functions to appear in the SI.
It was downloaded from
http://www.cvrl.org/database/data/lum/vl1924e_1.csv
where it is defined from 360 to 830 nm.
scotopic1951
The luminous efficiency function adopted by the CIE in 1951, and defining the standard scotopic observer. It is only to be used when light levels are low enough to exclude the activation of cones. It has no effective role in colorimetry. It was downloaded from http://www.cvrl.org/database/data/lum/scvle_1.csv where it is defined from 380 to 780 nm. It has been padded with 0s to 360 to 830 nm.
photopic1978
The luminous efficiency function for photopic vision, with adjustments in the blue region by Judd (1951) and Vos (1978). It was published by the CIE in 1988. It was downloaded from http://www.cvrl.org/database/data/lum/vme_1.csv where it is defined from 380 to 780 nm. It has been padded with 0s to 360 to 830 nm.
photopic2008
The CIE (2008) physiologically-relevant luminous efficiency function for photopic vision, by Stockman, Jagle, Pirzer, & Sharpe. It was downloaded from http://www.cvrl.org/database/data/lum/linCIE2008v2e_1.csv where it is defined from 390 to 830 nm. It has been padded with 0s to 360 to 830 nm.
Luminsivity is a self-coined portmanteau word:
luminsivity
= luminous
* responsivity
.
The word is unrelated to emissivity.
The term luminous responsivity is not common, but appears on page 15 of Grum.
The term luminous efficiency function is standard, but too long.
The term luminosity function is common, but luminosity is ambiguous and also appears in astronomy and scattering theory.
The object luminsivity.1nm
is used by the function photometric()
.
Colour & Vision Research Laboratory. Institute of Opthalmology. University College London. UK. http://www.cvrl.org/
Grum, Franc and Richard J. Becherer. Radiometry. Optical Radiation Measurements, Volume 1. Academic Press. 1979.
Stockman, A., Jagle, H., Pirzer, M., & Sharpe, L. T. (2008). The dependence of luminous efficiency on chromatic adaptation. Journal of Vision, 8, 16:1, 1-26.
summary(luminsivity.1nm) product( D65.1nm, luminsivity.1nm, wave='auto' )
summary(luminsivity.1nm) product( D65.1nm, luminsivity.1nm, wave='auto' )
Compute neutral gray material constant reflectance/transmittance, and rectangular spectra. Also compute absorbance of the human lens, as a function of age.
neutralMaterial( gray=1, wavelength=380:780 ) rectangularMaterial( lambda, alpha=1, wavelength=380:780 ) lensAbsorbance( age=32, wavelength=400:700 )
neutralMaterial( gray=1, wavelength=380:780 ) rectangularMaterial( lambda, alpha=1, wavelength=380:780 ) lensAbsorbance( age=32, wavelength=400:700 )
gray |
a numeric N-vector of gray levels, in the interval [0,1].
|
lambda |
a numeric Nx2 matrix with wavelength pairs in the rows, or a vector that can be converted to such a matrix, by row. The two wavelengths are the two transition wavelengths of the returned spectrum, see Details. |
alpha |
a numeric N-vector of chromatic amplitudes in the interval [-1,1].
N must be equal to |
age |
a numeric N-vector of ages in years; all ages must be |
wavelength |
a vector of wavelengths for the returned object |
A rectangular spectrum, or rectangular metamer, is easiest to define
when and
.
In this case it is a band-pass filter with transmittance=1 for
and transmittance=0 otherwise.
To create a long-pass filter, just set
to
Inf
,
or any large wavelength outside the spectrum range;
and similarly for a short-pass filter.
When the spectrum is a weighted mixture of this band-pass filter
with a perfect neutral gray filter with transmittance=0.5 at all
,
using
and
as the two weights.
The minimum transmittance is
and the maximum is
,
and their difference, the chromatic amplitude, is
.
It is still a band-pass filter.
If the spectrum is a perfect neutral with transmittance=0.5.
To "flip" the spectrum to its complement (change band-pass to band-stop, etc.),
change to a negative number, or swap
and
.
If
then the spectrum is undefined and a warning is issued
(unless
).
neutralMaterial()
returns a
colorSpec object with quantity
equal to 'reflectance'
.
The reflectance of each spectrum is constant and taken from gray
.
There are N spectra in the object - one for each gray level.
rectangularMaterial()
returns a
colorSpec object with quantity
equal to 'transmitance'
.
The transmitance of each spectrum is a step function with 0, 1 or 2 transitions (jumps)
defined by the corresponding row in lambda
.
If rownames(lambda)
is not NULL
, they are copied to specnames
of the output.
Otherwise the specnames
are computed from the shape of the spectrum
using these acronyms:
LP (long-pass), SP (short-pass), BP (band-pass), BS (band-stop),
and N (neutral, in case alpha==0
).
lensAbsorbance()
returns a
colorSpec object with quantity
equal to 'absorbance'
.
The absorbance model for the human lens is taken from Pokorny.
There are N spectra in the object - one for each age (N=length(age)
).
It is clear that there are 3 degrees-of-freedom in the
spectra returned by rectangularMaterial()
.
Logvinenko shows that these spectra in fact form a 3D ball,
which he calls the rectangle color atlas.
He also shows that if a material responder satisfies the 2-transition condition,
then these spectra uniquely generate all
colors in the corresponding object color solid.
For more on this, see the vignette Estimating a Spectrum from its Response.
Every spectrum returned by rectangularMaterial()
is an Ostwald ideal spectrum.
In Ostwald's terminology,
the color content = chromatic amplitude = .
And the black content = white content =
.
Note that the sum of these 3 contents is 1.
However, Ostwald allows black content and white content to be unequal,
as long as the sum of the 3 contents is 1, and all are non-negative.
Thus there is one extra degree-of-freedom for Ostwald's ideal spectra,
for a total of 4 degrees-of-freedom.
If an additional argument (or arguments) were added to
rectangularMaterial()
,
then it could return all Ostwald ideal spectra.
Foss, Carl E. and Dorothy Nickerson and Walter C. Granville. Analysis of the Ostwald Color System. J. Opt. Soc. Am.. vol. 34. no. 7. pp. 361-381. July, 1944.
Logvinenko, A. D.
An object-color space.
Journal of Vision.
9(11):5, 1-23, (2009).https://jov.arvojournals.org/article.aspx?articleid=2203976
.
doi:10.1167/9.11.5.
Pokorny, Joel, Vivianne C. Smith, and Margaret Lutze. Aging of the Human Lens. Applied Optics. Vol. 26, No. 8. 15 April 1987. Table I. Page 1439.
lightSpectra,
quantity()
,
specnames()
,
computeADL()
,
vignette Estimating a Spectrum from its Response
# make a perfect reflecting diffuser (PRD) prd = neutralMaterial( 1 ) # make a perfect transmitting filter (PTF) ptf = prd quantity(ptf) = 'transmittance' # make a band-stop filter (for interval [500,550]) # with 1% transmittance in the band, and 99% outside the band bs = rectangularMaterial( c(500,550), -0.98, 400:700 ) bs = rectangularMaterial( c(550,500), 0.98, 400:700 ) # equivalent to previous line # compare transmittance at 3 ages: 20, 32, and 80 years plot( linearize(lensAbsorbance( c(20,32,80) )), col='black', lty=1:3 )
# make a perfect reflecting diffuser (PRD) prd = neutralMaterial( 1 ) # make a perfect transmitting filter (PTF) ptf = prd quantity(ptf) = 'transmittance' # make a band-stop filter (for interval [500,550]) # with 1% transmittance in the band, and 99% outside the band bs = rectangularMaterial( c(500,550), -0.98, 400:700 ) bs = rectangularMaterial( c(550,500), 0.98, 400:700 ) # equivalent to previous line # compare transmittance at 3 ages: 20, 32, and 80 years plot( linearize(lensAbsorbance( c(20,32,80) )), col='black', lty=1:3 )
compute mean of all spectra in a colorSpec object
## S3 method for class 'colorSpec' mean( x, ... )
## S3 method for class 'colorSpec' mean( x, ... )
x |
a colorSpec object |
... |
further arguments ignored |
This function might be useful when capturing many spectra on a spectrometer and averaging them to reduce noise.
a colorSpec object with single spectrum = average of all spectra in colorSpec.
Retrieve or set the metadata of a colorSpec object.
## S3 method for class 'colorSpec' metadata(x, ...) ## S3 replacement method for class 'colorSpec' metadata(x, add=FALSE ) <- value
## S3 method for class 'colorSpec' metadata(x, ...) ## S3 replacement method for class 'colorSpec' metadata(x, add=FALSE ) <- value
x |
a colorSpec R object |
... |
optional names of metadata to return |
value |
a named |
add |
if |
The metadata list is stored as attr(x,'metadata')
.
After construction this list is empty.
metadata(x)
with no additional arguments returns the complete named list of metadata.
If arguments are present, then only those metadata
items are returned.
Do not confuse extradata
and metadata
.metadata
is unstructured data that is attached to the entire colorSpec object.
extradata
is structured data, with a row of data for each spectrum in the object.
## Not run: # get list of *all* metadata metadata(x) # get just the file 'path' metadata( x, 'path' ) # set the 'date' metadata( x ) = list( date="2016-04-01" ) ## End(Not run)
## Not run: # get list of *all* metadata metadata(x) # get just the file 'path' metadata( x, 'path' ) # set the 'date' metadata( x ) = list( date="2016-04-01" ) ## End(Not run)
multiply spectra by coefficients and return modified object
## S3 method for class 'colorSpec' multiply( x, s ) ## S3 method for class 'colorSpec' normalize( x, norm='L1' )
## S3 method for class 'colorSpec' multiply( x, s ) ## S3 method for class 'colorSpec' normalize( x, norm='L1' )
x |
a colorSpec object with M spectra |
s |
a scalar, an M-vector, or an MxP matrix.
In the case of a matrix, assigning |
norm |
one of |
For multiply()
:
If s
is an MxP matrix, say S, and one thinks of the spectra as
organized in an NxM matrix X,
then the new spectra are defined by the matrix XS, which is NxP.
If the P column names of s
are set, then they are copied to the
specnames
of the output.
Otherwise, default spectrum names are assigned as in
colorSpec()
, with a warning.
If s
is an M-vector, then S=diag(s)
is computed and used in the previous sentence.
This has the effect of multiplying spectrum i by s
[i].
If s
is a scalar then every spectrum is multiplied by s
.
The multiplication may produce negative entries, but no check is made for this.
WARNING: An M-vector and an Mx1 matrix may yield quite different results.
For normalize()
:normalize()
calls multiply()
with s
= an M-vector.
If the norm of a spectrum is 0, then it is left unchanged.
multiply
returns a colorSpec object with the matrix of spectra
of x
multiplied by s
.
normalize
returns a colorSpec object with each spectrum
of x
scaled to have given norm equal to 1.
In both functions, the quantity
and wavelength
are preserved.
If x
is organized as a matrix, and s
is a scalar,
the one can use the simpler and equivalent s*x
.
product()
,
quantity()
,
wavelength()
,
specnames()
,
colorSpec()
In careful calcuations with standard illuminants, it is often helpful to have the 'official' values of XYZ, i.e. with the right number of decimal places.
officialXYZ( name )
officialXYZ( name )
name |
a subvector of
|
All XYZ values are taken from the ASTM publication in References,
except B
which is taken from Wyszecki & Stiles
and D50.ICC
which is taken from ICC publications.
The latter is different than that of ASTM.
An Mx3 matrix where M is the length of name
.
Each row filled with the official XYZ, but if the illuminant name is not recognized the row is all NA
s.
The matrix rownames
are set to name
, and colnames
to c('X','Y','Z')
.
This function is deprecated.
New software should use spacesRGB::standardXYZ()
instead.
The input names are case-sensitive.
The output XYZ is normalized so that Y=1
.
ASTM E 308 - 01. Standard Practice for Computing the Colors of Objects by Using the CIE System. (2001).
Günther Wyszecki and W. S. Stiles. Color Science: Concepts and Methods, Quantitative Data and Formulae, Second Edition. John Wiley & Sons, 1982. Table I(3.3.8) p. 769.
ABC
,
D50
,
D65
,
Fluorescents
,
illuminantE
officialXYZ( c('A','D50','D50.ICC','D65') ) # X Y Z # A 1.0985000 1 0.3558500 # D50 0.9642200 1 0.8252100 # D50.ICC 0.9642029 1 0.8249054 # D65 0.9504700 1 1.0888300
officialXYZ( c('A','D50','D50.ICC','D65') ) # X Y Z # A 1.0985000 1 0.3558500 # D50 0.9642200 1 0.8252100 # D50.ICC 0.9642029 1 0.8249054 # D65 0.9504700 1 1.0888300
Retrieve or set the organization of a colorSpec object.
## S3 method for class 'colorSpec' organization(x) ## S3 replacement method for class 'colorSpec' organization(x) <- value
## S3 method for class 'colorSpec' organization(x) ## S3 replacement method for class 'colorSpec' organization(x) <- value
x |
a colorSpec R object |
value |
a valid organization: |
If organization(x)
is "vector"
, then x
is a vector
representing a single spectrum. Compare this with stats::ts()
.
If organization(x)
is "matrix"
, then x
is a matrix
and the spectra are stored in the columns.
If organization(x)
is "df.col"
, then x
is a data.frame
with M+1 columns, where M is the number of spectra.
The wavelengths are stored in column 1, and the spectra in columns 2:(M+1).
This organization is good for printing to the console, and writing to files.
If the organization
of x
is "df.row"
, then x
is a data.frame
with N rows, where N is the number of spectra.
The spectra are stored in the last column, which is a matrix with the name "spectra"
.
The other columns preceding spectra
(if present) contain extra data associated with
the spectra; see extradata
.
organization(x)
returns a valid organization:
'vector'
, 'matrix'
, 'df.col'
, or 'df.row'
.
In organization(x) <- value
if x
has more than 1 spectrum,
then value
equal to 'vector'
is invalid and ignored.
If organization(x)
is equal to 'df.row'
and also has extradata
,
then changing the organization
silently discards the extradata
.
organization(Hoya) # returns 'df.row' organization(Hoya) = 'matrix' # extradata in Hoya is silently discarded
organization(Hoya) # returns 'df.row' organization(Hoya) = 'matrix' # extradata in Hoya is silently discarded
Convert radiometric units of power or energy to photometric units, using 4 standard photometric weighting curves. Actinometric units (number of photons) are converted to radiometric units (energy of photons) on-the-fly.
## S3 method for class 'colorSpec' photometric( x, photopic=683, scotopic=1700, multiplier=1 )
## S3 method for class 'colorSpec' photometric( x, photopic=683, scotopic=1700, multiplier=1 )
x |
a colorSpec object with |
photopic |
the conversion factor for photopic vision, in lumen/watt. The CIE standard is 683, and another common value is 683.002. |
scotopic |
the conversion factor for scotopic vision, in lumen/watt. The CIE standard is 1700, and another common value is 1700.06. |
multiplier |
an conversion factor intended for conversion of units,
and applied to both photopic and scotopic vision.
For example if the input unit of |
The function computes the product of x
with luminsivity.1nm
.
This product is an Mx4 matrix, where M is the number of spectra in x
.
There are 3 columns for photopic vision, and 1 column for scotopic vision.
These columns are multiplied by the appropriate conversion factors
and the resulting Mx4 matrix is returned.
The 5 power-based input quantities and corresponding photometric outputs are:
radiant power [ ] |
---> | luminous flux [ ] |
irradiance [ ] |
---> | illuminance [ ] |
radiant exitance [ ] |
---> | luminous exitance [ ] |
radiant intensity [ ] |
---> | luminous intensity [ ] |
radiance [ ] |
---> | luminance [ ] |
The 2 common energy-based input quantities and corresponding photometric outputs are:
radiant energy [ ] |
---> | luminous energy [ ] |
radiant exposure [ ] |
---> | luminous exposure [ ] |
and there are 3 more obtained by integrating over time. For example "time-integrated radiance" —> "time integrated luminance". But I have not been able to find names for these 3. The talbot is the unofficial name for a lumen-second.
photometric()
returns an Mx4 matrix, where M is the number of spectra in x
.
The rownames
are specnames(x)
,
and the colnames
are specnames(luminsivity.1nm)
.
In case of ERROR it returns NULL
.
To get the right output quantity and units,
the user must know the input quantity and units.
If the units are different than those in the above list,
then set multiplier
appropriately.
It is up to the user to determine whether
photopic or scotopic vision (or neither) is appropriate.
The intermediate mesopic vision is currently a subject of research by the CIE,
and might be added to this function in the future.
Poynton, Charles. Digital Video and HD - Algorithms and Interfaces. Morgan Kaufmann. Second Edition. 2012. Appendix B, pp. 573-580.
quantity
,
type
,
luminsivity.1nm
,
radiometric
photometric( solar.irradiance ) # unit is watt*m^{-2} # photopic1924 scotopic1951 photopic1978 photopic2008 # units are lux # AirMass.0 133100.41 313883.2 133843.65 140740.3 # GlobalTilt 109494.88 250051.5 110030.31 115650.0 # AirMass.1.5 97142.25 215837.1 97571.57 102513.7
photometric( solar.irradiance ) # unit is watt*m^{-2} # photopic1924 scotopic1951 photopic1978 photopic2008 # units are lux # AirMass.0 133100.41 313883.2 133843.65 140740.3 # GlobalTilt 109494.88 250051.5 110030.31 115650.0 # AirMass.1.5 97142.25 215837.1 97571.57 102513.7
plot the spectra in a colorSpec object as lines or points
## S3 method for class 'colorSpec' plot( x, color=NULL, subset=NULL, main=TRUE, legend=TRUE, CCT=FALSE, add=FALSE, ... )
## S3 method for class 'colorSpec' plot( x, color=NULL, subset=NULL, main=TRUE, legend=TRUE, CCT=FALSE, add=FALSE, ... )
x |
a colorSpec object |
color |
If |
subset |
specifies a subset of |
main |
If |
legend |
If |
CCT |
If |
add |
If |
... |
other graphical parameters, see Details |
Commonly used graphical parameters are:
type
passed to lines.default()
,
with default type='l'
.
Other valid values are 'p'
(points), 'b'
, 'c'
, 'o'
,
'h'
, 'S'
, 's'
, and 'n'
,
see plot()
for their meanings.
An additional type='step'
is available.
This option draws each spectrum as a step function,
similar to 'S'
and 's'
,
except that the jumps are between the wavelengths
(with appropriate extensions at min and max wavelengths).
The function segments()
is used for the drawing.
For type='step'
, lwd
and lty
should be vectors of length 1 or 2.
If the length of lwd
is 1, then horizontal segments are draw with that width,
but vertical segments are not drawn.
If the length of lwd
is 2,
then vertical segments are draw with width lwd[2]
.
If the length of lty
is 2, then the styles are applied to the
horizontal and vertical segments in that order.
If the length of lty
is 1, then that style is applied to both
horizontal and vertical segments.
For examples of this plotting option, see the vignette
Convexity and Transitions.
lwd, lty
passed to lines.default()
,
except when type='step'
when they are passed to segments()
.
In the former case these can be vectors, and components are passed
sequentially to each spectrum, similar to matplot()
.
In the latter case, see the description in type
.
The default value for both is 1.
pch
passed to lines.default()
,
but it only has meaning when type='p'
, 'b'
, or 'o'
.
This can be a vector, and components are passed sequentially to each spectrum.
ylab
If ylab
is a string
then it is passed on to plot.default()
,
otherwise suitable default string is generated.
xlim, ylim
If xlim
and ylim
are 2-vectors, they are passed
to plot.default
.
If one of the components is NA
then a suitable default is supplied.
log
passed on to plot.default()
.
Care must be taken for y
because
many spectra are 0 at some wavelengths, and even negative. Use ylim
in such cases.
TRUE
or FALSE
computeCCT()
,
subset()
,
lines()
,
segments()
,
plot()
,
matplot()
,
colorRamp()
plot( 100 * BT.709.RGB ) plot( xyz1931.1nm, add=TRUE, lty=2, legend=FALSE )
plot( 100 * BT.709.RGB ) plot( xyz1931.1nm, add=TRUE, lty=2, legend=FALSE )
Consider a colorSpec object x
with type
equal to 'responsivity.material'
and 3 responsivity spectra.
The function plotOptimals3D()
makes a plot of the object-color solid for x
.
This solid is a zonohedron in 3D.
The 3D drawing package rgl is required.
Consider a colorSpec object x
with type
equal to 'responsivity.material'
and 2 responsivity spectra.
The function plotOptimals2D()
makes a plot of the object-color solid for x
.
This solid is a zonogon in 2D.
The 3D drawing package rgl is not required.
The set of all possible material reflectance functions (or transmittance functions)
is convex, closed, and bounded (in any reasonable function space),
and this implies that the set of all possible output responses
from x
is also convex, closed, and bounded.
The latter set is called the object-color solid,
or Rösch Farbkörper, for x
.
A color on the boundary of the object-color solid is called an optimal color.
For more discussion see sectionOptimalColors()
.
## S3 method for class 'colorSpec' plotOptimals3D( x, size=50, type='w', both=TRUE ) ## S3 method for class 'colorSpec' plotOptimals2D( x )
## S3 method for class 'colorSpec' plotOptimals3D( x, size=50, type='w', both=TRUE ) ## S3 method for class 'colorSpec' plotOptimals2D( x )
x |
a colorSpec object with |
size |
an integer giving the number of wavelengths at which to resample |
type |
|
both |
the color solid is symmetric about its center, so only half of it must
be computed.
If |
The functions return TRUE
or FALSE
.
If n
is the number of wavelengths,
the number of parallelogram faces of the zonohedron is n*(n-1)
.
The time to compute these faces increase with n
even faster,
so that is why the default size=50
is a fairly small number.
It was chosen to be a reasonable compromise between detail and performance.
In addition to the wireframe or points,
it draws the box with opposite vertices at the "poles" 0 and W
and the diagonal segment of neutral grays that connects 0 and W.
If n
is the number of wavelengths,
the number of edges in the zonogon is 2*n
.
Computing these edges is fast and visualization is easy,
so there are no plotting options at this time.
If all responsivity functions of x
are non-negative,
the object-color solid of x
is inside the box.
If the responsivity functions of x
have negative lobes,
the object-color solid of x
extends outside the box.
Indeed, the box may actually be inside the optimals.
Centore, Paul. A Zonohedral Approach to Optimal Colours. Color Research & Application. Vol. 38. No. 2. pp. 110-119. April 2013.
Logvinenko, A. D.
An object-color space.
Journal of Vision.
9(11):5, 1-23, (2009).https://jov.arvojournals.org/article.aspx?articleid=2203976
.
doi:10.1167/9.11.5.
West, G. and M. H. Brill. Conditions under which Schrödinger object colors are optimal. Journal of the Optical Society of America. 73. pp. 1223-1225. 1983.
type()
,
probeOptimalColors()
,
sectionOptimalColors()
,
vignette Plotting Chromaticity Loci of Optimal Colors
human = product( D50.5nm, 'slot', xyz1931.5nm, wave=seq(400,770,by=5) ) plotOptimals3D( human ) plotOptimals2D( subset(human,2:3) ) # y and z only scanner = product( D50.5nm, 'slot', BT.709.RGB, wave=seq(400,770,by=5) ) plotOptimals3D( scanner )
human = product( D50.5nm, 'slot', xyz1931.5nm, wave=seq(400,770,by=5) ) plotOptimals3D( human ) plotOptimals2D( subset(human,2:3) ) # y and z only scanner = product( D50.5nm, 'slot', BT.709.RGB, wave=seq(400,770,by=5) ) plotOptimals3D( scanner )
display a colorSpec object as readable text.
Output goes to stdout()
.
## S3 method for class 'colorSpec' print( x, ...) ## S3 method for class 'colorSpec' summary( object, long=TRUE, ... )
## S3 method for class 'colorSpec' print( x, ...) ## S3 method for class 'colorSpec' summary( object, long=TRUE, ... )
x |
a colorSpec object |
object |
a colorSpec object |
long |
logical indicating whether to print |
... |
further arguments ignored |
If long=FALSE
,
summary()
prints a summary of the wavelength vector, and names of all spectra.
For each spectrum it prints the range of values, LambdaMax
, and
extradata
if any.
If long=TRUE
it also prints data listed above (if any).
The function print()
simply calls summary()
with long=FALSE
.
Both functions return (invisibly) the character vector that was just printed
to stdout()
.
extradata
,
print
,
summary
,
stdout
print( xyz1931.1nm ) xyz1931.1nm # same thing, just calls print()
print( xyz1931.1nm ) xyz1931.1nm # same thing, just calls print()
Consider a colorSpec object x
with type
equal to 'responsivity.material'
.
The set of all possible material reflectance functions (or transmittance functions)
is convex, closed, and bounded (in any reasonable function space),
and this implies that the set of all possible output responses
from x
is also convex, closed, and bounded.
The latter set is called the object-color solid or Rösch Farbkörper for x
.
A color on the boundary of the object-color solid is called an optimal color.
The special points W (the response to the perfect reflecting diffuser)
and 0 are on the boundary of this set.
The interior of the line segment of neutrals joining 0 to W is in the interior of the
object-color solid.
It is natural to parameterize this segment from 0 to 1 (from 0 to W).
A ray that is based at a point on the interior of the neutral line segment
must intersect the boundary of the object-color solid in a unique optimal color.
The purpose of the function
probeOptimalColors()
is to compute that intersection point.
Currently the function only works if the number of spectra in
x
is 3 (e.g. RGB or XYZ).
Before colorSpec v 0.8-1 this function used a 2D root-finding
method that could only find
optimal colors whose spectra contain 0, 1, or 2 transitions.
But starting with v0.8-1, we have switched to zonohedral representation
of the object-color solid, which makes it possible to discover more than 2 transitions.
The inspiration for this change is the article by Centore.
To inspect these computed spectra, the argument spectral
must be set
to TRUE
.
## S3 method for class 'colorSpec' probeOptimalColors( x, gray, direction, aux=FALSE, spectral=FALSE, tol=1.e-6 )
## S3 method for class 'colorSpec' probeOptimalColors( x, gray, direction, aux=FALSE, spectral=FALSE, tol=1.e-6 )
x |
a colorSpec object with |
gray |
vector of numbers in the open interval (0,1) that define neutral grays on the line segment from black to white; this neutral gray point is the basepoint of a probe ray |
direction |
a numeric Nx3 matrix with directions of the probe rays in the rows, or a numeric vector that can be converted to such a matrix, by row. |
aux |
a logical that specifies whether to return extra performance and diagnostic data; see Details |
spectral |
if |
tol |
error tolerance for the intersection of probe and object-color boundary |
Each gray level and each direction defines a ray.
So the total number of rays traced is length(gray)
* nrow(direction)
.
The 3 responsivities are regarded not as continuous functions,
but as step functions.
This implies that the color solid is a zonohedron.
In the preprocessing phase the zonohedral representation is calculated.
The faces of the zonohedron are either parallelograms,
or compound faces that can be partitioned into parallelograms.
The centers of all these parallelograms are computed, along with their normals
and plane constants.
This representation of the color solid is very strict regarding the
2-transition assumption.
During use, one can count on there being some spectra with more than two transitions.
Forcing the best 2-transition spectrum is a possible topic for the future.
If argument spectral=FALSE
,
probeOptimalColors()
returns a data.frame
with a row for each traced ray.
There are length(gray)
* nrow(direction)
rays.
The columns in the output are:
gray |
the graylevel defining the |
direction |
the |
s |
computed scalar so that |
optimal |
the optimal color on the boundary; |
lambda |
lambda.1 and lambda.2 at the 2 transitions, in nm.
lambda.1 < lambda.2 => bandpass,
and lambda.1 > lambda.2 => bandstop.
It will happen that the optimal spectrum has more than 2 transitions;
in this case both lambdas are set to |
dol |
|
If aux is TRUE
, these auxiliary columns related to performance and diagnostics are added:
timetrace |
time to trace the ray, in seconds |
parallelograms |
# of parallelograms in the (possibly compound) face. 1 means just a single parallelogram. |
tested |
# of parallelograms actually tested for ray intersection. This only has meaning for compound faces. |
alpha |
the 2 coordinates of the intersection point inside the parallelogram |
If argument spectral=TRUE
,
probeOptimalColors()
returns a colorSpec object with quantity
'reflectance'
.
This object contains the optimal spectra, and can be used to inspect the spectra
with more than 2 transitions, which will happen.
The above-mentioned data.frame
can then be obtained by applying
extradata()
to the returned object.
If an individual ray could not be traced (which should be rare),
the row contains NA
in appropriate columns.
In case of global error, the function returns NULL
.
The preprocessing calculation of the zonohedron dominates the total time.
And this time goes up rapidly with the number of wavelengths.
We recommend using a wavelength step of 5nm, as in the Examples.
For best results, batch a lot of rays into a single function call
and then process the output.
Moreover, the preprocessing time is dominated by the partitioning
of the compound faces into parallelograms.
This is made worse by spectral responses with little overlap,
as in scanner.ACES
.
In these cases, try a larger step size, and then reduce.
Optimizing these compound faces is a possible topic for the future.
Centore, Paul. A zonohedral approach to optimal colours. Color Research & Application. Vol. 38. No. 2. pp. 110-119. April 2013.
Logvinenko, A. D.
An object-color space.
Journal of Vision.
9(11):5, 1-23, (2009).https://jov.arvojournals.org/article.aspx?articleid=2203976
.
doi:10.1167/9.11.5.
Schrödinger, E. (1920). Theorie der Pigmente von grösster Leuchtkraft. Annalen der Physik. 62, 603-622.
West, G. and M. H. Brill. Conditions under which Schrödinger object colors are optimal. Journal of the Optical Society of America. 73. pp. 1223-1225. 1983.
type
,
vignette Plotting Chromaticity Loci of Optimal Colors,
scanner.ACES
,
extradata()
wave = seq(400,700,by=5) D50.eye = product( D50.5nm, 'material', xyz1931.1nm, wavelength=wave ) probeOptimalColors( D50.eye, c(0.2,0.5,0.9), c(1,2,1, -1,-2,-1) ) ## gray direction.1 direction.2 direction.3 s optimal.1 optimal.2 ## 1 0.2 1 2 1 32.306207 52.533143 85.612065 ## 2 0.2 -1 -2 -1 8.608798 11.618138 3.782055 ## 3 0.5 1 2 1 20.993144 71.560483 94.485416 ## 4 0.5 -1 -2 -1 20.993144 29.574196 10.512842 ## 5 0.9 1 2 1 4.333700 95.354911 103.165832 ## 6 0.9 -1 -2 -1 35.621938 55.399273 23.254556 ## optimal.3 lambda.1 lambda.2 dol.delta dol.omega dol.lambda ## 1 49.616046 451.8013 598.9589 0.63409966 0.48287469 536.97618091 ## 2 8.701041 636.3031 429.4659 0.08458527 0.99624955 674.30015903 ## 3 64.267740 441.9105 615.0822 0.78101041 0.49048222 538.73234859 ## 4 22.281453 615.0822 441.9105 0.21898959 0.99048222 662.20606601 ## 5 82.227974 422.9191 648.7404 0.95800430 0.49825407 540.49590064 ## 6 42.272337 593.2415 455.2425 0.42035428 0.97962398 650.57382749 # create a 0-1 spectrum with 2 transitions rectspec = rectangularMaterial( lambda=c(579.8697,613.7544), alpha=1, wave=wave ) # compute the corresponding color XYZ XYZ = product( rectspec, D50.eye ) XYZ ## X Y Z ## BP_[579.87,613.754] 33.42026 21.96895 0.02979764 # trace a ray from middle gray through XYZ white.XYZ = product( neutralMaterial(1,wave=wave), D50.eye ) direction = XYZ - white.XYZ/2 res = probeOptimalColors( D50.eye, 0.5, direction, aux=FALSE ) res$s ## 1.00004 the ray has gone past the original color to the boundary res$optimal ## X Y Z ## [1,] 33.41958 21.96774 0.02808178 res$lambda ## NA NA because there are more than 2 transitions in the true optimal # since s=1.00004 > 1, # XYZ is actually in the interior of the color solid, and not on the boundary. # The boundary is a little-bit further along the ray, # and the corresponding spectrum has more than 2 transitions.
wave = seq(400,700,by=5) D50.eye = product( D50.5nm, 'material', xyz1931.1nm, wavelength=wave ) probeOptimalColors( D50.eye, c(0.2,0.5,0.9), c(1,2,1, -1,-2,-1) ) ## gray direction.1 direction.2 direction.3 s optimal.1 optimal.2 ## 1 0.2 1 2 1 32.306207 52.533143 85.612065 ## 2 0.2 -1 -2 -1 8.608798 11.618138 3.782055 ## 3 0.5 1 2 1 20.993144 71.560483 94.485416 ## 4 0.5 -1 -2 -1 20.993144 29.574196 10.512842 ## 5 0.9 1 2 1 4.333700 95.354911 103.165832 ## 6 0.9 -1 -2 -1 35.621938 55.399273 23.254556 ## optimal.3 lambda.1 lambda.2 dol.delta dol.omega dol.lambda ## 1 49.616046 451.8013 598.9589 0.63409966 0.48287469 536.97618091 ## 2 8.701041 636.3031 429.4659 0.08458527 0.99624955 674.30015903 ## 3 64.267740 441.9105 615.0822 0.78101041 0.49048222 538.73234859 ## 4 22.281453 615.0822 441.9105 0.21898959 0.99048222 662.20606601 ## 5 82.227974 422.9191 648.7404 0.95800430 0.49825407 540.49590064 ## 6 42.272337 593.2415 455.2425 0.42035428 0.97962398 650.57382749 # create a 0-1 spectrum with 2 transitions rectspec = rectangularMaterial( lambda=c(579.8697,613.7544), alpha=1, wave=wave ) # compute the corresponding color XYZ XYZ = product( rectspec, D50.eye ) XYZ ## X Y Z ## BP_[579.87,613.754] 33.42026 21.96895 0.02979764 # trace a ray from middle gray through XYZ white.XYZ = product( neutralMaterial(1,wave=wave), D50.eye ) direction = XYZ - white.XYZ/2 res = probeOptimalColors( D50.eye, 0.5, direction, aux=FALSE ) res$s ## 1.00004 the ray has gone past the original color to the boundary res$optimal ## X Y Z ## [1,] 33.41958 21.96774 0.02808178 res$lambda ## NA NA because there are more than 2 transitions in the true optimal # since s=1.00004 > 1, # XYZ is actually in the interior of the color solid, and not on the boundary. # The boundary is a little-bit further along the ray, # and the corresponding spectrum has more than 2 transitions.
Take a sequence of colorSpec objects and compute their product. Only certain types of sequences are allowed. The return value can be a new colorSpec object or a matrix; see Details.
## S3 method for class 'colorSpec' product( ... )
## S3 method for class 'colorSpec' product( ... )
... |
unnamed arguments are colorSpec objects, and possibly a single character string, see Details. Possible named arguments are:
|
To explain the allowable product sequences it is helpful to introduce some simple notation for the objects:
notation | colorSpec type |
description of the object |
|
light |
a light source |
|
material |
a material |
|
responsivity.light |
a light responder (aka detector) |
|
responsivity.material |
a material responder (e.g. a scanner) |
It is also helpful to define a sequence of positive integers
to be conformable iff it has at most one value greater than 1.
For example,
a sequence of all 1s is conformable. A sequence of all 's is conformable.
The sequences
c(1,3)
and c(1,1,4,1,1,4,1)
are conformable,
but c(1,1,4,1,3,4,1)
is not.
There are 6 types of sequences for which the product is defined:
1. ↦
The product of materials is another material.
Think of a stack of
transmitting filters effectively forming a new filter.
If we think of each object as a matrix (with the spectra in the columns),
then the product is element-by-element using R's
*
- the Hadamard product.
The numbers of spectra in the terms must be conformable.
If some objects have 1 spectrum and all the others have ,
then the column-vector spectrums are repeated
times to form a
matrix with
columns.
If the numbers of spectra are not conformable,
it is an ERROR and the function returns
NULL
.
As an example, suppose has 1 spectrum and
has
spectra,
and
.
Then the product is a material with
spectra.
Think of an IR-blocking filter followed by the RGB filters in a 3-CCD camera.
2. ↦
The product of a light source followed by materials is a light source.
Think of a light source
followed by a stack of
transmitting filters, effectively forming a new light source.
The numbers of spectra in the terms must be conformable as in sequence 1,
and the matrices are multiplied element by element.
As an example, suppose has 1 spectrum and
has
spectra,
and
.
Then the product is a light source with
spectra.
Think of a light source followed by a filter wheel with
filters.
3. ↦
The product of materials followed by a light responder, is a light responder.
Think of a stack of
transmitting filters in front of a camera, effectively forming a new camera.
The numbers of spectra in the terms must be conformable as in sequence 1,
and the matrices are multiplied element by element.
As an example, suppose has 1 spectrum and
has
spectra,
and
.
Then the product is a light responder with
spectra.
Think of a 3-CCD camera in which all 3 CCDs have exactly the same responsivity
and so can be modeled with a single object
.
4. •
↦
This is the strangest product.
The bullet symbol • means that a variable material is inserted at that slot
in the sequence (or light path).
For each material spectrum inserted there is a response from .
Therefore the product of this sequence is a material responder
.
Think of a light source
going through
a transparent object • on a flatbed scanner and into a camera
.
For more about the mathematics of this product,
see the colorSpec-guide.pdf in the doc directory.
These material responder spectra are the same as the
effective spectral responsivities in Digital Color Management.
The numbers of spectra in the terms must be conformable as in sequence 1,
and the product is a material responder with
spectra.
In the function product()
the location of the • is marked
by any character string whatsoever - it's up to the user who might choose
something that describes the typical material (between the light source and camera).
For example one might choose:scanner = product( A.1nm, 'photo', Flea2.RGB, wave='auto')
to model a scanner that is most commonly used to scan photographs.
Other possible strings could be 'artwork'
, 'crystal'
, 'varmaterial'
,
or even 'slot'
.
See the vignette Viewing Object Colors in a Gallery for a worked-out example.
5. ↦
The product of a light source, followed by materials,
followed by a light responder, is a matrix!
The numbers of spectra in the terms must splittable into
a conformable left part (
from sequence 2.)
and a conformable right part (
from sequence 3.).
There is a row for each spectrum in
,
and a column for each spectrum in
.
Suppose the element-by-element product of the left part is
×
and the element-by-element product of the right part is
and
×
,
where
is the number of wavelengths.
Then the output matrix is the usual matrix product
%*%
of the transpose of the left part times and right part,
which is ×
.
As an example, think of a light source followed by a
reflective color target with 24 patches
followed by an RGB camera.
The sequence of spectra counts is c(1,24,3)
which is splittable into c(1,24)
and c(3)
.
The product matrix is 24×3.
See the vignette Viewing Object Colors in a Gallery for a worked-out example.
Note that is OK for there to be no materials in this product;
it is OK if .
See the vignette Blue Flame and Green Comet
for a worked-out example.
6. ↦
The product of materials followed by a material responder, is a matrix !
The sequence of numbers of spectra must be splittable into left and right
parts as in sequence 4, and the product matrix is formed the same way.
One reason for computing this matrix in 2 steps is that one can
calibrate
the material responder separately in a customizable way.
See the vignette Viewing Object Colors in a Gallery
for a worked-out example with a flatbed scanner.
Note that sequences 5. and 6. are the only ones that
use the usual matrix product %*%
.
They may also use the Hadamard matrix product *
, as in sequences 1 to 4.
The argument wavelength
can also be 'auto'
or NULL
.
In this case the intersection of all the wavelength ranges of the objects is computed.
If the intersection is empty, it is an ERROR and the function returns NULL
.
The wavelength step step.wl
is taken to be the smallest over all the object wavelength sequences.
If the minimum step.wl
is less than 1 nanometer,
it is rounded off to the nearest power of 2 (e.g 1, 0.5, 0.25, ...).
product()
returns either a colorSpec object or a matrix, see Details.
If product()
returns a colorSpec object, the organization
of the object is 'matrix'
or 'vector'
;
any extradata
is lost.
However, all terms in the product are saved in attr(*,'sequence')
.
One can use str()
to inspect this attribute.
If product()
returns a matrix,
this matrix can sometimes be ambiguous, see Note.
All actinometric terms are converted to radiometric on-the-fly and the returned colorSpec object is also radiometric.
In case of ERROR it returns NULL
.
The product for sequences 1, 2, and 3 is associative.
After all matrices are filled out to have columns,
the result is essentially a Hadamard product of matrices,
which is associative.
Also note that a subsequence of sequences 2 and 3 might be sequence 1.
The product for sequence 4 is never associative, since subproducts that contain the variable • are undefined. However the result is essentially a Hadamard product of matrices, and is unambiguous.
The product for sequence 5 is associative in special cases, but not in general.
The problem is that the left and right splitting point is not unique.
If all objects have only a single spectrum, then it *is* associative,
and therefore unambiguous.
If the left part has a different number of multiple spectra than the right part,
then it is not associative in general since some ways of grouping the
product may be undefined.
Moreover, in some cases the product can be ambiguous.
Suppose that the vector of spectrum counts is c(1,3,1)
;
this could come from a single light source, followed by 3 filters (e.g. RGB),
followed by a graylevel camera.
There are 2 ways to split this: "1|3,1"
and "1,3|1"
.
The first split is interpreted as the light source into a camera with 3 channels.
The second split is interpreted as 3 colored light sources into a graylevel camera.
In the first split the returned matrix is a 1x3
row vector.
In the second split the returned matrix is a 3x1
column vector.
For the vector "1,3,1"
, one can show that
the computed components in the 2 vectors are equal,
so the ambiguity is benign.
But consider the longer sequence "1,3,3,1"
.
There are 3 ways to split this, and the returned matrices are
1x3
, 3x3
, and 3x1
.
So this ambiguity is obviously a problem.
Whenever there is an ambiguity, the function chooses a splitting
in which the left part is as long as possible, and issues a warning message.
The user should inspect the result carefully.
To avoid the ambiguity, the user should break the product into smaller pieces
and call product()
multiple times.
The product for sequence 6 is essentially the same as sequence 5,
and the function issues a warning message when appropriate.
Note that a subproduct is defined only if it avoids the final multiplication with .
Edward J. Giorgianni and Thomas E. Madden. Digital Color Management: Encoding Solutions. 2nd Edition John Wiley. 2009. Figure 10.11a. page 141.
Wikipedia. Hadamard product (matrices). https://en.wikipedia.org/wiki/Hadamard_product_(matrices)
ASTM E308-01. Standard Practice for Computing the Colors of Objects by Using the CIE System. (2001).
wavelength
,
type
,
resample
,
calibrate
,
radiometric
,
step.wl
# sequence 1. path = system.file( "extdata/objects/Midwest-SP700-2014.txt", package='colorSpec' ) blocker.IR = readSpectra( path ) product( blocker.IR, Hoya, wave='auto' ) # sequence 2. product( subset(solar.irradiance,1), atmosphere2003, blocker.IR, Hoya, wave='auto' ) # sequence 3. plumbicon = readSpectra( system.file( "extdata/cameras/plumbicon30mm.txt", package='colorSpec' ) ) product( blocker.IR, subset(Hoya,1:3), plumbicon, wave='auto' ) # sequence 4. # make an RGB scanner bluebalancer = subset(Hoya,'LB') # combine tungsten light source A.1nm with blue light-balance filter # use the string 'artwork' to mark the variable material slot scanner = product( A.1nm, bluebalancer, 'artwork', Flea2.RGB, wave='auto' ) # sequence 5. product( D65.1nm, Flea2.RGB, wave='auto' ) # a 1x3 matrix, no materials product( D65.1nm, neutralMaterial(0.01), Flea2.RGB, wave='auto' ) # a 1x3 matrix, 1 material path = system.file( "extdata/sources/Lumencor-SpectraX.txt", package='colorSpec' ) lumencor = readSpectra( path, wave=340:660 ) product( lumencor, Flea2.RGB, wave='auto' ) # a 7x3 matrix, no materials # sequence 6. scanner = calibrate( scanner ) target = readSpectra( system.file( "extdata/targets/N130501.txt", package='colorSpec') ) product( target, scanner, wave='auto' ) # a 288x3 matrix
# sequence 1. path = system.file( "extdata/objects/Midwest-SP700-2014.txt", package='colorSpec' ) blocker.IR = readSpectra( path ) product( blocker.IR, Hoya, wave='auto' ) # sequence 2. product( subset(solar.irradiance,1), atmosphere2003, blocker.IR, Hoya, wave='auto' ) # sequence 3. plumbicon = readSpectra( system.file( "extdata/cameras/plumbicon30mm.txt", package='colorSpec' ) ) product( blocker.IR, subset(Hoya,1:3), plumbicon, wave='auto' ) # sequence 4. # make an RGB scanner bluebalancer = subset(Hoya,'LB') # combine tungsten light source A.1nm with blue light-balance filter # use the string 'artwork' to mark the variable material slot scanner = product( A.1nm, bluebalancer, 'artwork', Flea2.RGB, wave='auto' ) # sequence 5. product( D65.1nm, Flea2.RGB, wave='auto' ) # a 1x3 matrix, no materials product( D65.1nm, neutralMaterial(0.01), Flea2.RGB, wave='auto' ) # a 1x3 matrix, 1 material path = system.file( "extdata/sources/Lumencor-SpectraX.txt", package='colorSpec' ) lumencor = readSpectra( path, wave=340:660 ) product( lumencor, Flea2.RGB, wave='auto' ) # a 7x3 matrix, no materials # sequence 6. scanner = calibrate( scanner ) target = readSpectra( system.file( "extdata/targets/N130501.txt", package='colorSpec') ) product( target, scanner, wave='auto' ) # a 288x3 matrix
apply a linear transformation to a colorSpec responder with M spectra,
so that multiples of M given primary vectors
are transformed to the standard basis of .
And a given white vector is transformed to the M-vector of all 1s.
The returned object is always
multiply(x,A)
where A
is an internally calculated MxM matrix.
The name ptransform
is short for projective transformation.
In case of ERROR, a message is logged and NULL returned.
## S3 method for class 'colorSpec' ptransform( x, primary, white, digits=Inf )
## S3 method for class 'colorSpec' ptransform( x, primary, white, digits=Inf )
x |
a colorSpec responder with M spectra.
|
primary |
an MxM matrix whose rows define the M primary vectors
in the response space of |
white |
an M-vector in the response space of |
digits |
if a positive integer,
then |
The formal mathematical requirements for primary
and white
are:
The rows of primary
must form a basis of .
Equivalently, if
denotes the matrix
primary
,
then is invertible.
The coordinates of white
in this basis are all non-zero.
Equivalently, if is the solution of
,
then every component of x is non-zero.
Assuming both of these are true, then there is a unique matrix so that
transforms a multiple of the
'th row of
to the
'th
standard basis vector of
.
transforms
white
to the M-vector of all 1s.
This statement is essentially the fundamental theorem of (analytic) projective geometry;
see Bumcrot page 87, and Semple page 398.
The rows of plus
define a projective frame;
the former are the fundamental points and the latter is the unit point.
If digits
is a positive integer,
the chromaticity of white
is computed by dividing white
by sum(white)
.
The latter must be non-zero, or else it is an ERROR.
This chromaticity is rounded to digits
decimal digits, while preserving the sum of 1.
This rounded chromaticity is non-zero, and defines a line through 0.
The vector white
is projected onto this line to get the new and rounded white
,
with the rounded chromaticity.
See Examples.
a colorSpec object equal to multiply(x,A)
where A
is an internally calculated MxM matrix.
The quantity
and wavelength
are preserved.
The specnames
of the returned object are set to tolower( rownames(primary) )
.
The user may want to change the quantity
of the returned object;
see Examples.
Bumcrot, Robert J. Modern Projective Geometry. Holt, Rinehart, and Winston. 1969.
IEC 61966-2-1:1999. Multimedia systems and equipment - Colour measurement and management. Part 2-1: Colour management - Default RGB colour space - sRGB. https://webstore.iec.ch/publication/6169
Semple, J. G. and G. T. Kneebone. Algebraic Projective Geometry. Oxford. 1952.
quantity
,
wavelength
,
colorSpec
,
multiply
,
product
############ Example for sRGB ########### # assign the standard sRGB primaries P = matrix( c(0.64,0.33,NA, 0.3,0.6,NA, 0.15,0.06,NA ), 3, 3, byrow=TRUE ) rownames(P) = c('R','G','B') P # [,1] [,2] [,3] # R 0.64 0.33 NA # G 0.30 0.60 NA # B 0.15 0.06 NA white = product( D65.1nm, xyz1931.1nm, wave='auto' ) white # X Y Z # D65 100.437 105.6708 115.0574 white/sum(white) # X Y Z # D65 0.3127269 0.3290232 0.3582499 # But the sRGB standard D65 is xy=(0.3127,0.3290) # so when the next line is executed, # the calculated 3x3 matrix will *NOT* agree with the sRGB standard y = ptransform( xyz1931.1nm, P, white, digits=Inf ) product( D65.1nm, y, wave='auto' ) # R G B # D65 1 1 1 # this is exactly what we want, but the internal 3x3 matrix is a little off # now repeat, but this time round the white chromaticity to # xy=(0.3127,0.3290) in order to get the matrix right y = ptransform( xyz1931.1nm, P, white, digits=4 ) rgb = product( D65.1nm, y, wave='auto' ) rgb # R G B # D65 1.000238 1.000053 0.999835 # off in the 4'th digit (WARN: this is linear RGB) 255 * rgb # R G B # D65 255.0607 255.0134 254.9579 # good enough for 8-bit RGB 65535 * rgb # R G B # D65 65550.59 65538.44 65524.18 # NOT good enough for 16-bit RGB # So for 16-bit RGB, one can get the white RGB right, or the 3x3 matrix right, but not both ! ############ Example for ProPhoto RGB ########### # assign the standard ProPhoto RGB primaries P = matrix( c(0.7347,0.2653,NA, 0.1596,0.8404,NA, 0.0366,0.0001,NA ), 3, 3, byrow=TRUE ) rownames(P) = c('R','G','B') P # [,1] [,2] [,3] # R 0.7347 0.2653 NA # G 0.1596 0.8404 NA # B 0.0366 0.0001 NA white = product( D50.5nm, xyz1931.5nm, wave='auto' ) white # X Y Z # D50 101.2815 105.042 86.67237 white / sum(white) # X Y Z # D50 0.3456755 0.3585101 0.2958144 # but the ProPhoto RGB standard is xy=(0.3457,0.3585); proceed anyway y = ptransform( xyz1931.5nm, P, white, digits=Inf ) product( D50.5nm, y, wave='auto' ) # R G B # D50 1 1 1 # this is exactly what we want, but the internal 3x3 matrix is a little off # the following line is an equivalent way to compute y. # pass D50.5nm directly as the 'white' argument y = ptransform( xyz1931.5nm, P, D50.5nm )
############ Example for sRGB ########### # assign the standard sRGB primaries P = matrix( c(0.64,0.33,NA, 0.3,0.6,NA, 0.15,0.06,NA ), 3, 3, byrow=TRUE ) rownames(P) = c('R','G','B') P # [,1] [,2] [,3] # R 0.64 0.33 NA # G 0.30 0.60 NA # B 0.15 0.06 NA white = product( D65.1nm, xyz1931.1nm, wave='auto' ) white # X Y Z # D65 100.437 105.6708 115.0574 white/sum(white) # X Y Z # D65 0.3127269 0.3290232 0.3582499 # But the sRGB standard D65 is xy=(0.3127,0.3290) # so when the next line is executed, # the calculated 3x3 matrix will *NOT* agree with the sRGB standard y = ptransform( xyz1931.1nm, P, white, digits=Inf ) product( D65.1nm, y, wave='auto' ) # R G B # D65 1 1 1 # this is exactly what we want, but the internal 3x3 matrix is a little off # now repeat, but this time round the white chromaticity to # xy=(0.3127,0.3290) in order to get the matrix right y = ptransform( xyz1931.1nm, P, white, digits=4 ) rgb = product( D65.1nm, y, wave='auto' ) rgb # R G B # D65 1.000238 1.000053 0.999835 # off in the 4'th digit (WARN: this is linear RGB) 255 * rgb # R G B # D65 255.0607 255.0134 254.9579 # good enough for 8-bit RGB 65535 * rgb # R G B # D65 65550.59 65538.44 65524.18 # NOT good enough for 16-bit RGB # So for 16-bit RGB, one can get the white RGB right, or the 3x3 matrix right, but not both ! ############ Example for ProPhoto RGB ########### # assign the standard ProPhoto RGB primaries P = matrix( c(0.7347,0.2653,NA, 0.1596,0.8404,NA, 0.0366,0.0001,NA ), 3, 3, byrow=TRUE ) rownames(P) = c('R','G','B') P # [,1] [,2] [,3] # R 0.7347 0.2653 NA # G 0.1596 0.8404 NA # B 0.0366 0.0001 NA white = product( D50.5nm, xyz1931.5nm, wave='auto' ) white # X Y Z # D50 101.2815 105.042 86.67237 white / sum(white) # X Y Z # D50 0.3456755 0.3585101 0.2958144 # but the ProPhoto RGB standard is xy=(0.3457,0.3585); proceed anyway y = ptransform( xyz1931.5nm, P, white, digits=Inf ) product( D50.5nm, y, wave='auto' ) # R G B # D50 1 1 1 # this is exactly what we want, but the internal 3x3 matrix is a little off # the following line is an equivalent way to compute y. # pass D50.5nm directly as the 'white' argument y = ptransform( xyz1931.5nm, P, D50.5nm )
Retrieve or set the quantity of a colorSpec object.
## S3 method for class 'colorSpec' quantity(x) ## S3 replacement method for class 'colorSpec' quantity(x) <- value ## S3 method for class 'colorSpec' type(x)
## S3 method for class 'colorSpec' quantity(x) ## S3 replacement method for class 'colorSpec' quantity(x) <- value ## S3 method for class 'colorSpec' type(x)
x |
a colorSpec R object |
value |
a valid |
There are 4 valid types
, which are further divided into 14 valid quantities.
All of these are strings:
For type='light'
quantity =
'energy'
(radiometric), or 'photons'
(actinometric)
For type='responsivity.light'
quantity =
'energy->electrical'
, 'energy->neural'
, 'energy->action'
,'photons->electrical'
, 'photons->neural'
, or 'photons->action'
For type='material'
quantity =
'reflectance'
, 'transmittance'
, or 'absorbance'
For type='responsivity.material'
quantity =
'material->electrical'
, 'material->neural'
, or 'material->action'
Also see the colorSpec User Guide.
quantity()
returns the quantity of x
type()
returns the type of x
, which depends only on the quantity
.
The colorSpec quantity
is more general than the physical SI quantity;
for example quantity='energy'
really includes 10 distinct SI quantities
and maybe more.
The unit is left arbitrary in most cases.
Exceptions are reflectance
, transmittance
, and absorbance
which are dimensionless.
Changing the quantity
should only be done if one knows what one is doing.
It does not change the underlying numbers.
For example, changing photons
to energy
does not do numerical conversion.
For this specific conversion, see radiometric()
.
Similarly, see linearize()
for conversion
from absorbance
to transmittance
.
colorSpec
,
radiometric
,
linearize
Convert a colorSpec object to have quantity that is radiometric (energy of photons) - to prepare it for colorimetric calculations. Test an object for whether it is radiometric.
## S3 method for class 'colorSpec' radiometric( x, multiplier=1, warn=FALSE ) ## S3 method for class 'colorSpec' is.radiometric( x )
## S3 method for class 'colorSpec' radiometric( x, multiplier=1, warn=FALSE ) ## S3 method for class 'colorSpec' is.radiometric( x )
x |
a colorSpec object |
multiplier |
a scalar which is multiplied by the output, and intended for unit conversion |
warn |
if |
If the quantity
of x
does not start with 'photons'
then the quantity is not actinometric
and so x
is returned unchanged.
Otherwise x
is actinometric (photon-based).
If type(x)
is 'light'
then
the most common actinometric unit of photon count is
(mole of photons) = (
photons).
The conversion equation is:
where is the energy of the photons,
is the photon count,
is Avogadro's constant,
is Planck's constant,
is the speed of light,
and
is the wavelength in meters.
The output energy unit is joule.
If the unit of Q
is not (mole of photons),
then the output should be scaled appropriately.
For example, if the unit of photon count is exaphotons,
then set
multiplier=1/0.602214
.
If the quantity(x)
is 'photons->electrical'
,
then the most common actinometric unit of responsivity to light is quantum efficiency (QE).
The conversion equation is:
where is the energy-based responsivity,
is the quantum efficiency,
and
is the charge of an electron (in C).
The output responsivity unit is coulombs/joule (C/J) or amps/watt (A/W).
If the unit of x
is not quantum efficiency,
then multiplier
should be set appropriately.
If the quantity(x)
is
'photons->neural'
or 'photons->action'
,
the most common actinometric unit of photon count is
(mole of photons) = (
photons).
The conversion equation is:
where is the energy-based responsivity,
is the photon-based responsivity.
This essentially the reciprocal of the first conversion equation.
The argument multiplier
is applied to the right side of all the above
conversion equations.
radiometric()
returns a colorSpec object with
quantity
that is
radiometric (energy-based) and not actinometric (photon-based).
If type(x)
is a material type
('material'
or 'responsivity.material'
)
then x
is returned unchanged.
If quantity(x)
starts with 'energy'
,
then is.radiometric()
returns TRUE
, and otherwise FALSE
.
To log the executed conversion equation,
execute cs.options(loglevel='INFO')
.
Wikipedia. Photon counting. https://en.wikipedia.org/wiki/Photon_counting
quantity
,
type
,
F96T12
,
cs.options
,
actinometric
sum( F96T12 ) # the step size is 1nm, from 300 to 900nm # [1] 320.1132 photon irradiance, (micromoles of photons)*m^{-2}*sec^{-1} sum( radiometric(F96T12) ) # [1] 68.91819 irradiance, watts*m^{-2}
sum( F96T12 ) # the step size is 1nm, from 300 to 900nm # [1] 320.1132 photon irradiance, (micromoles of photons)*m^{-2}*sec^{-1} sum( radiometric(F96T12) ) # [1] 68.91819 irradiance, watts*m^{-2}
The CGATS text format supports a preamble followed by N tables, where N 1.
Each table can have a separate header.
A table may or may not contain spectral data, see Note.
The function converts each table to a
data.frame
with attributes;
see Details.
readCGATS( path, collapsesingle=FALSE )
readCGATS( path, collapsesingle=FALSE )
path |
the path name of a single file, in CGATS format |
collapsesingle |
If |
The returned list is given attributes: "path"
, "preamble"
,
and (if present) "date"
, "created"
, "originator"
, and "file_descriptor"
.
The attribute values are all character vectors.
The value of attribute "path"
is the argument path
,
and the other values are extracted from "preamble"
.
The length of "preamble"
is (typically) greater than 1, and the others have length 1.
Each line of the preamble is a keyword-value pair.
The keyword ORIGINATOR
is converted to attribute "originator"
.
The keyword FILE_DESCRIPTOR
is converted to attribute "file_descriptor"
.
The keyword CREATED
is converted to attributes "created"
and "date"
.
The list is also given names
.
If the keyword TABLE_NAME
is present in the table header, then its value is used.
Otherwise the names are "TABLE_1"
, "TABLE_2"
, ...
Each data.frame
in the list is assigned attributes: "header"
,
and (if present) "descriptor"
.
The length of "header"
is (typically) greater than 1, and "descriptor"
has length 1.
Each line of the table header is a keyword-value pair.
The keywords DESCRIPTOR
and TABLE_DESCRIPTOR
are converted to attribute "descriptor"
.
For the lines between BEGIN_DATA
and END_DATA
,
two conventions for separating the values are supported:
In the standard convention, fields are separated by contiguous spaces or tabs, and character strings (which may have embedded spaces or even tabs)
are enclosed by double-quotes.
This is is the convention in the CGATS standard.
The function scan()
is used here.
In the non-standard convention, fields are separated by a single tab,
and character strings (which may have embedded spaces but not tabs) are not enclosed by double-quotes.
This convention is often easier to work with in spreadsheet software.
The function strsplit()
is used here.
The function readCGATS()
selects the separation convention
by examining the line after BEGIN_DATA_FORMAT
.
If this line is split by a single tab and the number of fields
matches that given on the NUMBER_OF_FIELDS
line,
then the non-standard convention is selected;
otherwise, the standard convention is selected.
readCGATS()
returns a list of data.frame
s -
one data.frame
for each table found in path
.
The list and each individual data.frame
have attributes, see Details.
If path
has only a single table (the majority of files have only 1)
and collapsesingle
is TRUE
, then the attributes of the list
are copied to those of the data.frame
,
and the data.frame
is then returned.
The name of the table is lost.
If there is an error in any table, then the function returns NULL
.
In the BEGIN_DATA_FORMAT
line(s),
field names may not be quoted and may not have embedded spaces.
The CGATS standard allows duplicated field names,
and readCGATS()
returns them as they appear, with no attempt to append numbers
in order to make them unique.
Examples of field names which may be duplicated are:
SPECTRAL_NM
, SPECTRAL_DEC
, and SPECTRAL_PCT
;
for more on these see readSpectraCGATS()
.
No attempt is made to recognize those tables that contain spectral data.
For conversion of spectral data to colorSpec objects, see readSpectraCGATS()
.
ANSI/CGATS.17. Graphic technology - Exchange format for colour and process control data using XML or ASCII text. https://webstore.ansi.org/ 2009.
ISO/28178. Graphic technology - Exchange format for colour and process control data using XML or ASCII text. https://www.iso.org/standard/44527.html. 2009.
CGATS.17 Text File Format. http://www.colorwiki.com/wiki/CGATS.17_Text_File_Format.
readSpectraCGATS
,
scan
,
strsplit
,
names
# read file with 2 tables of non-spectral data A70 = readSpectra( system.file( "tests/A70.ti3", package='colorSpec' ) ) length(A70) # [1] 2 # the file has 2 tables ncol( A70[[1]] ) # [1] 7 # the 1st table has 7 columns ncol( A70[[2]] ) # [1] 4 # the 2nd table has 4 columns
# read file with 2 tables of non-spectral data A70 = readSpectra( system.file( "tests/A70.ti3", package='colorSpec' ) ) length(A70) # [1] 2 # the file has 2 tables ncol( A70[[1]] ) # [1] 7 # the 1st table has 7 columns ncol( A70[[2]] ) # [1] 4 # the 2nd table has 4 columns
These functions read colorSpec objects from files.
In case of ERROR
, they return NULL
.
There are 5 different file formats supported; see Details.
readSpectra( pathvec, ... ) readSpectraXYY( path ) readSpectraSpreadsheet( path ) readSpectrumScope( path ) readSpectraCGATS( path ) readSpectraControl( path )
readSpectra( pathvec, ... ) readSpectraXYY( path ) readSpectraSpreadsheet( path ) readSpectrumScope( path ) readSpectraCGATS( path ) readSpectraControl( path )
pathvec |
a character vector to (possibly) multiple files. The file extension and a few lines from each file are read and a guess is made regarding the file format. |
... |
optional arguments passed on to |
path |
a path to a single file with the corresponding format:
|
readSpectra()
reads the first few lines of the file in order
to determine the format,
and then calls the corresponding format-specific function.
If readSpectra()
cannot determine the format, it returns NULL.
The 5 file formats are:
XYY
There is a column header line matching ^(wave|wv?l)
(not case sensitive)
followed by the the names of the spectra.
All lines above this one are taken to be metadata
.
The separarator on this header line can be space, tab, or comma;
the line is examined and the separator found there is used in the lines with data below.
The organization of the returned object is 'df.col'
.
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, using a spectrometer, of many spectra.
See the sample file N130501.txt
from Wolf Faust.
Scope
This is a file format used by Ocean Optics spectrometer software.
There is a line >>>>>Begin Processed Spectral Data<<<<<
followed by 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 complex format that is best understood by looking at some samples, such asextdata/objects/Rosco.txt
;
see also the References.
The function readCGATS()
is first called to get all the tables,
and then for each table the column names are examined.
There are 2 conventions for presenting the spectral data:
In the standard convention the fields SPECTRAL_DEC
or SPECTRAL_PCT
have the spectral values.
The former is the true value, and the latter is the true value x 100.
Each value column is preceded a corresponding wavelength column,
which has the field name SPECTRAL_NM
.
Note that these field names are highly duplicated.
In principle, this convention allows each record in a CGATS table to have
a different wavelength vector.
However, this complication is rejected by readSpectraCGATS()
,
which treats it as an ERROR.
In the non-standard convention the field names that match the pattern"^(nm|SPEC_|SPECTRAL_)[_A-Z]*([0-9.]+)$"
are considered to be spectral value data, and other fields are considered extradata
.
The wavelength is the numerical value of the 2nd parenthesized expression ([0-9.]+)
in nanometers.
Note that every record in this CGATS table has the same wavelength vector.
Although this convention is non-standard, it appears in files from many companies,
including X-Rite.
If a data.frame
has spectral data, it is converted to a colorSpec object
and placed in the returned list.
The organization of the resulting colorSpec object is 'df.row'
.
If the data.frame
of extradata
contains a column
SAMPLE_NAME
, SAMPLE_ID
, SampleID
, or Name
,
(examined in that order), then that column is taken to be the specnames
of the object.
If a table has no spectral data, then it is ignored.
If the CGATS file has no tables with spectral data, then it is an ERROR
and the function returns NULL
.
Control
This is a personal format used for digitizing images of plots from manufacturer
datasheets and academic papers. It is structured like a .INI
file.
There is a [Control]
section establishing a simple linear map from pixels 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 is done on-the-fly after they are read.
Extrapolation can be a problem, especially when the value is near 0.
To force constant extrapolation (see resample()
),
repeat the control point (knot) at the endpoint.
See the sample file Lumencor-SpectraX.txt
.
The organization of the returned objects is 'vector'
.
readSpectra()
returns a single colorSpec object or NULL
in case of ERROR.
If there are multiple files in pathvec
and they cannot be combined using bind()
because their wavelengths are different, it is an ERROR.
To avoid this ERROR, the wavelength
argument can be used for
resampling to common wavelengths.
If there are multiple files, the organization
of the returned object is
'df.row'
and the first column is the path
from which the spectrum was read.
The functions readSpectraXYY()
, readSpectraSpreadsheet()
,
and readSpectraScope()
, return a single colorSpec object,
or NULL
in case of ERROR.
The functions readSpectraCGATS()
and readSpectraControl()
are more complicated.
These 2 file formats can contain multiple spectra with different wavelength
sequences,
so both functions return a list of colorSpec objects,
even when that list has length 1.
If no spectral objects are found, they return NULL
.
If readSpectra()
calls readSpectraCGATS()
or readSpectraControl()
and receives a list of colorSpec objects,
readSpectra()
attempts to bind()
them into a single object.
If they all have the same wavelength vector, then the bind()
succeeds and the
single colorSpec object is returned.
Otherwise the bind()
fails, and it is an ERROR.
To avoid this error readSpectra()
can be called with a
wavelength
argument.
The multiple spectra are resampled using resample()
and
then combined using bind()
,
which makes it much more convenient to read such files.
During import, each read function tries to guess the quantity
from spectrum names or other cues.
For example the first line in N130501.txt is IT8.7/1
,
which indicates that the quantity is 'transmittance'
(a reflective target is denoted by IT8.7/2
).
If a confident guess cannot be made, it makes a wild guess and issues a warning.
If the quantity is incorrect, one can assign the correct value after import.
Alternatively one can add a line to the header part of the file with the
keyword 'quantity'
followed by some white-space and the correct value.
It is OK to put the value in quotes.
See example files under folder extdata.
CGATS.17 Text File Format. http://www.colorwiki.com/wiki/CGATS.17_Text_File_Format.
ANSI/CGATS.17. Graphic technology - Exchange format for colour and process control data using XML or ASCII text. https://webstore.ansi.org/ 2009.
ISO/28178. Graphic technology - Exchange format for colour and process control data using XML or ASCII text.
https://www.iso.org/standard/44527.html.
2009.
wavelength
,
quantity
,
metadata
,
resample
,
bind
,
readCGATS
# read file with header declaring the quantity to be "photons->neural" bird = readSpectra( system.file( "extdata/eyes/BirdEyes.txt", package='colorSpec' ) ) quantity(bird) # [1] "photons->neural"
# read file with header declaring the quantity to be "photons->neural" bird = readSpectra( system.file( "extdata/eyes/BirdEyes.txt", package='colorSpec' ) ) quantity(bird) # [1] "photons->neural"
interpolate or smooth to new wavelengths. Simple extrapolation and clamping is also performed.
## S3 method for class 'colorSpec' resample( x, wavelength, method='auto', span=0.02, extrapolation='const', clamp='auto' )
## S3 method for class 'colorSpec' resample( x, wavelength, method='auto', span=0.02, extrapolation='const', clamp='auto' )
x |
a colorSpec object |
wavelength |
vector of new wavelengths, in nanometers |
method |
interpolation methods available are
|
span |
smoothing argument passed to |
extrapolation |
extrapolation methods available are
|
clamp |
clamp methods available are
|
If method is 'sprague'
, the quintic polynomial in De Kerf is used.
Six weights are applied to nearby data values: 3 on each side.
The 'sprague'
method is only supported when x
is regular.
If method is 'spline'
, the function stats::spline()
is called with method='natural'
.
The 'spline'
method is supported even when x
is irregular.
If method is 'linear'
, the function stats::approx()
is called.
Two weights are applied to nearby data values: 1 on each side.
The 'linear'
method is supported even when x
is irregular.
If method is 'loess'
, the function stats::loess()
is called
with the given span
parameter.
Smoothing is most useful for noisy data, e.g. raw data from a spectrometer.
I have found that span=0.02
works well for Ocean Optics .scope
files,
but this may be too small in other cases, which triggers an error in stats::loess()
.
The 'loess'
method is supported even when x
is irregular.
If extrapolation is 'const'
,
the extreme values at each end are simply extended.
If extrapolation is 'linear'
,
the line defined by the 2 extreme values at each end is used for extrapolation.
If the ultimate and penultimate wavelengths are equal,
then this line is undefined and the function reverts to 'const'
.
If clamp is 'auto'
,
output values are clamped to the physically realizable interval
appropriate for x
.
This is the interval [0,1] when
quantity(x)
is 'reflectance'
or 'transmittance'
,
and the interval [0,) otherwise.
Exception: If an input spectrum in
x
violates a limit,
then clamping the output spectrum to this limit is NOT enforced.
This happens most frequenty for theoretical (or matrixed) cameras,
such as BT.709.RGB
.
If clamp is TRUE
, the result is the same as 'auto'
,
but with no exceptions.
If clamp is FALSE
, then no clamping is done.
If clamp is a numerical interval, then clamping is done to that interval,
with no exceptions.
The two standard intervals mentioned above can be expressed in R
as c(0,1)
and c(0,Inf)
respectively.
resample(x)
returns a colorSpec object
with the new wavelength
.
Other properties, e.g. organization
, quantity
, ...,
are preserved.
In case of ERROR, the function returns NULL
.
De Kerf, Joseph L. F. The interpolation method of Sprague-Karup. Journal of Computational and Applied Mathematics. volume I, no 2, 1975. equation (S).
organization()
,
quantity()
,
wavelength()
,
is.regular()
,
theoreticalRGB
,
spline()
,
approx()
,
loess
path = system.file( "extdata/sources/pos1-20x.scope", package='colorSpec' ) y = readSpectra( path ) # plot noisy data in gray plot( y, col='gray' ) # plot smoothed plot in black on top of the noisy one to check quality plot( resample( y, 200:880, meth='loess', span=0.02 ), col='black', add=TRUE )
path = system.file( "extdata/sources/pos1-20x.scope", package='colorSpec' ) y = readSpectra( path ) # plot noisy data in gray plot( y, col='gray' ) # plot smoothed plot in black on top of the noisy one to check quality plot( resample( y, 200:880, meth='loess', span=0.02 ), col='black', add=TRUE )
This function computes a few technical metrics regarding some geometric objects related to a responder: the spherical chromaticity polygon, cone, convex cone, and color-solid.
Currently the function only works if the number of spectra in x
is 3 (e.g. RGB or XYZ).
In this case the rows of as.matrix(x)
(after weighting by step size)
are called the generators; they are vectors in
and we require that they are all in some open linear halfspace (unless a generator is 0).
The 0-based rays through the generators intersect a plane inside the halfspace to form the vertices
of the chromaticity polygon
.
The 0-based rays through points of the interior of
form a cone,
and the convex hull of this cone is a convex cone.
The central projection of
onto the unit sphere is the spherical chromaticity polygon
.
If
type
is 'responsivity.material'
, then x
has an
object-color solid or Rösch Farbkörper,
which is a zonohedron .
See Centore and vignette
Convexity and Transitions
for details.
Some simplification of the generators is performed during pre-processing.
Generators that are 0 (in all channels) are removed,
and a group of generators that are all positive multiples of each other is replaced by their sum.
The 3-vectors are called the condensed generators.
These simplifications do not change any of the geometric objects defined above.
## S3 method for class 'colorSpec' responsivityMetrics( x )
## S3 method for class 'colorSpec' responsivityMetrics( x )
x |
a colorSpec object with |
responsivityMetrics()
returns a list with these items:
generators |
a pair of integers, the 1st is the number of original generators, and the 2nd is the number of condensed generators |
zeros |
vector of wavelengths at which the responsivity is 0 (in all 3 channels) |
multiples |
a list of vectors of wavelengths; the responsivities in each vector (group) are positive multiples of each other |
salient |
a logical where TRUE means that there is some open linear halfspace that contains all the non-zero generators. If all the responsivities are non-negative, which is the usual case, then |
normal |
If |
If salient=TRUE
, then the list also contains:
concavities |
a |
coneangle |
the solid angle of the cone generated by the generators. This is identical to the area of the spherical chromaticity polygon, with concavities preserved. |
cxconeangle |
the solid angle of the convex cone generated by the generators, with no concavities.
This is identical to the area of the convex hull of the spherical chromaticity polygon.
If all responsivities are non-negative, which is the usual case, then this solid angle is less than
the solid angle of an octant, which is |
If the type of x is 'responsivity.material'
then the list also contains:
area |
the surface area of the object-color solid of |
volume |
the volume of the object-color solid of |
In case of global error, the function returns NULL
.
To determine the value of salient
, the package quadprog might be required.
Centore, Paul. A zonohedral approach to optimal colours. Color Research & Application. Vol. 38. No. 2. pp. 110-119. April 2013.
type
,
vignette Convexity and Transitions
scanner.ACES
is an RGB responder to material;
an ACES/SMPTE standard for scanning RGB film.
The 3 spectra are defined from 368 to 728 nm, at 2nm intervals.
A colorSpec object with quantity
equal to 'material->electrical'
and 3 spectra:
r
, g
, and b
.
The responsivities have been scaled (by calibrate
) so the response to the perfect transmitting filter (PTF) is RGB=(1,1,1).
Technical Bulletin TB-2014-005. Informative Notes on SMPTE ST 2065-2 - Academy Printing Density (APD). Spectral Responsivities, Reference Measurement Device and Spectral Calculation.
SMPTE ST 2065-3 Academy Density Exchange Encoding (ADX). Encoding Academy Printing Density (APD) Values.
The Academy of Motion Picture Arts and Sciences. Science and Technology Council. Academy Color Encoding System (ACES) Project Committee. Version 1.0 December 19, 2014. Annex A Spectral Responsivities.
# compute response of ACES scanner to the Hoya filters product( Hoya, scanner.ACES, wave='auto' ) ## R G B ## R-60 0.902447043 2.022522e-05 0.00000000 ## G-533 0.038450857 4.900983e-01 0.05431134 ## B-440 0.008466317 1.686241e-02 0.42863320 ## LB-120 0.184408941 3.264111e-01 0.53492533
# compute response of ACES scanner to the Hoya filters product( Hoya, scanner.ACES, wave='auto' ) ## R G B ## R-60 0.902447043 2.022522e-05 0.00000000 ## G-533 0.038450857 4.900983e-01 0.05431134 ## B-440 0.008466317 1.686241e-02 0.42863320 ## LB-120 0.184408941 3.264111e-01 0.53492533
Consider a colorSpec object x
with type
equal to 'responsivity.material'
.
The set of all possible material reflectance functions (or transmittance functions)
is convex, closed, and bounded (in fact they form a cube),
and this implies that the set of all possible output responses
from x
is also convex, closed, and bounded.
The latter set is called the object-color solid or Rösch Farbkörper for x
.
If the dimension of the response of x
is 2,
this solid is a convex polygon
that is centrally symmetric - a zonogon.
If the dimension of the response of x
is 3 (e.g. RGB or XYZ),
this solid is a special type of centrally symmetric convex polyhedron
called a zonohedron, see Centore.
This function only supports dimensions 2 and 3.
Denote this object-color solid by Z.
A color on the boundary of Z is called an optimal color. Consider the intersection of a hyperplane with the boundary of Z. Let the equation of the hyperplane be given by:
where is orthogonal to the hyperplane,
and
is the plane constant, and
is a variable vector.
The purpose of the function
sectionOptimalColors()
is to compute the intersection set.
In dimension 2 this hyperplane is a line, and the intersection is generically 2 points, and 1 point if the line only intersects the boundary (we ignore the special case when the intersection is an edge of the polygon).
In dimension 3 this hyperplane is a 2D plane, and the intersection is generically a polygon, and 1 point if the line only intersects the boundary (we ignore the special case when the intersection is a face of the zonohedron).
Of course, the intersection can also be empty.
## S3 method for class 'colorSpec' sectionOptimalColors( x, normal, beta )
## S3 method for class 'colorSpec' sectionOptimalColors( x, normal, beta )
x |
a colorSpec object with |
normal |
a nonzero vector of dimension M, that is the normal to a hyperplane |
beta |
a vector of numbers of positive length.
The number |
.
Consider first the case that the dimension of x
is 3,
so that Z is a zonohedron.
In the preprocessing phase the zonohedral representation is calculated.
The faces of Z are either parallelograms,
or compound faces that are partitioned into parallelograms.
The centers of all these parallelograms are computed,
along with their extent in direction .
For a given plane
,
the parallelograms that intersect the plane are extracted.
The boundary of each parallelogram intersects the plane in 2 points (in general)
and one of those points is computed.
The set of all these points is then sorted into proper order around the boundary.
In the case that the dimension of x
is 2,
so that Z is a zonogon,
the parallelograms are replaced by line segments (edges),
and the processing is much easier.
The function returns a list with an item for each value in vector beta
.
Each item in the output is a list with these items:
beta |
the value of the plane constant |
section |
an NxM matrix, where N is the number of points in the section,
and M is the dimension of |
In case of global error, the function returns NULL
.
The preprocessing calculation of the zonohedron dominates the total time.
And this time goes up rapidly with the number of wavelengths.
We recommend using a wavelength step of 5nm, as in the Examples.
For best results, batch a lot of beta
s into a single function call
and then process the output.
Moreover, the preprocessing time is dominated by the partitioning
of the compound faces into parallelograms.
This is made worse by an x
whose spectral responses have little overlap,
as in scanner.ACES
.
In these cases, try a larger step size, and then reduce.
Optimizing these compound faces is a possible topic for the future.
Centore, Paul. A Zonohedral Approach to Optimal Colours. Color Research & Application. Vol. 38. No. 2. pp. 110-119. April 2013.
Logvinenko, A. D.
An object-color space.
Journal of Vision.
9(11):5, 1-23, (2009).https://jov.arvojournals.org/article.aspx?articleid=2203976
.
doi:10.1167/9.11.5.
vignette Plotting Chromaticity Loci of Optimal Colors,
probeOptimalColors()
wave = seq(420,680,by=5) Flea2.scanner = product( A.1nm, "material", Flea2.RGB, wavelength=wave ) seclist = sectionOptimalColors( Flea2.scanner, normal=c(0,1,0), beta=10 ) length( seclist[[1]]$section ) seclist[[1]]$section[ 1:5, ] ## [1] 207 # the polygon has 207 vertices, and the first 5 are: ## Red Green Blue ## [1,] 109.2756 10 3.5391342 ## [2,] 109.5729 10 2.5403628 ## [3,] 109.8078 10 1.7020526 ## [4,] 109.9942 10 1.0111585 ## [5,] 110.1428 10 0.4513051
wave = seq(420,680,by=5) Flea2.scanner = product( A.1nm, "material", Flea2.RGB, wavelength=wave ) seclist = sectionOptimalColors( Flea2.scanner, normal=c(0,1,0), beta=10 ) length( seclist[[1]]$section ) seclist[[1]]$section[ 1:5, ] ## [1] 207 # the polygon has 207 vertices, and the first 5 are: ## Red Green Blue ## [1,] 109.2756 10 3.5391342 ## [2,] 109.5729 10 2.5403628 ## [3,] 109.8078 10 1.7020526 ## [4,] 109.9942 10 1.0111585 ## [5,] 110.1428 10 0.4513051
solar.irradiance
Three power spectra; from 280 to 1000 nm at 1 nm intervals. The unit is .
atmosphere2003
a transmittance spectrum = the quotient of 2 spectra from solar.irradiance
solar.irradiance
is a
colorSpec object with quantity
equal to 'energy'
and with 3 spectra:
AirMass.0
Extraterrestrial Radiation (solar spectrum at top of atmosphere) at mean Earth-Sun distance
GlobalTilt
spectral radiation from solar disk plus sky diffuse and diffuse reflected from ground on south facing surface tilted 37 deg from horizontal
AirMass.1.5
the sum of Direct and Circumsolar irradiance, when the optical path is 1.5 times that of the sun at zenith, see Details
atmosphere2003
is a colorSpec object with quantity
equal to 'transmittance'
and with 1 spectrum:
AirMass.1.5
the quotient AirMass.1.5
/ AirMass.0
from solar.irradiance
Direct is Direct Normal Irradiance Nearly parallel (0.5 deg divergent cone) radiation on surface with surface normal tracking (pointing to) the sun, excluding scattered sky and reflected ground radiation.
Circumsolar is Spectral irradiance within +/- 2.5 degree (5 degree diameter) field of view centered on the 0.5 deg diameter solar disk, but excluding the radiation from the disk.
The reference spectra in ASTM G173-03 are designed for Photovoltaic Performance Evaluation.
The original wavelength sequence in ASTM G173-03 is irregular.
The interval is 0.5 nanometer from 280 to 400 nm,
1 nm from 400 to 1700 nm,
an intermediate wavelength at 1702 nm,
and 5 nm from 1705 to 4000 nm.
To create the object solar.irradiance
with a regular step size,
the original was resampled from 280 to 1000 nm at 1nm intervals.
Reference Solar Spectral Irradiance: ASTM G-173. http://rredc.nrel.gov/solar/spectra/am1.5/astmg173/astmg173.html
ASTM G173-03 Reference Spectra Derived from SMARTS v. 2.9.2.
Standard Tables for Reference Solar Spectral Irradiances: Direct Normal and Hemispherical on 37-deg Tilted Surface (2003)
D65
,
D50
,
daylightSpectra
,
resample
,
vignette Blue Flame and Green Comet
Retrieve or set the specnames of a colorSpec object. Retrieve the number of spectra.
## S3 method for class 'colorSpec' specnames(x) ## S3 replacement method for class 'colorSpec' specnames(x) <- value ## S3 method for class 'colorSpec' numSpectra(x)
## S3 method for class 'colorSpec' specnames(x) ## S3 replacement method for class 'colorSpec' specnames(x) <- value ## S3 method for class 'colorSpec' numSpectra(x)
x |
a colorSpec R object |
value |
a character vector with length equal to the number of spectra in |
If the organization
of x
is "vector"
then x
is a vector
and value
is a single string, which is stored as attr(x,'specname')
.
If the organization
of x
is "matrix"
, then x
is a matrix
and value
is stored as colnames(x)
.
If the organization
of x
is "df.col"
, then x
is a data.frame
with N+1
columns, where N
is the number of spectra.
value
is stored as colnames(x)[2:(N+1)]
.
If the organization
of x
is "df.row"
, then x
is a data.frame
and value
is stored as row.names(x)
.
specnames()
returns a character vector with the names of the spectra.
numSpectra(x)
is equal to length(specnames(x))
but much more efficient.
To display an XYZ value, it typically must be converted to a standard RGB space. This is the function to do it.
RGBfromXYZ( XYZ, space )
RGBfromXYZ( XYZ, space )
XYZ |
a 3-vector, or a matrix with 3 columns with XYZs in the rows |
space |
the name of the RGB space - either |
The input XYZ
is multiplied by the appropriate 3x3 conversion matrix
(for sRGB or Adobe RGB).
These matrices are taken from Lindbloom and not from the corresponding Wikipedia articles;
for the reason why see Note.
An Mx3 matrix where M is the number of rows in XYZ
, or M=1 if XYZ is a 3-vector.
Each row of the returned matrix is filled with linear RGB in the appropriate RGB space.
Values outside the unit cube are not clamped.
To compute non-linear display RGB, see DisplayRGBfromLinearRGB()
.
In case of error the function returns NULL
.
This function is deprecated.
New software should use spacesRGB::RGBfromXYZ()
instead.
The new function returns "signal RGB" instead of linear RGB.
An RGB space is normally defined by the xy chromaticities of the 3 primaries and the white point.
We follow Lindbloom in using the 'official' XYZ of the white point from ASTM E308.
Using this XYZ of the white point makes the color space a little more consistent
with other areas of color.
For example, from IEC 61966-2-1 we have
D65 xyY=(0.3127,0.3290,1) -> XYZ=(0.9504559,1,1.0890578).
But from ASTM E308, D65 XYZ=(0.95047,1,1.08883), which is a little different.
IEC 61966-2-1:1999. Multimedia systems and equipment - Colour measurement and management. Part 2-1: Colour management - Default RGB colour space - sRGB. https://webstore.iec.ch/publication/6169
Lindbloom, Bruce. RGB/XYZ Matrices. http://brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
Wikipedia. sRGB. https://en.wikipedia.org/wiki/SRGB
Wikipedia. Adobe RGB. https://en.wikipedia.org/wiki/Adobe_RGB_color_space
D65
,
officialXYZ
,
DisplayRGBfromLinearRGB
RGBfromXYZ( officialXYZ('D65'), 'sRGB' ) # R G B # [1,] 1 1 1 # not really 1s, but difference < 1.e-7 RGBfromXYZ( c(.3127,0.3290,0.3583)/0.3290, 'sRGB' ) # R G B # [1,] 0.9998409 1.000023 1.00024 difference > 1.e-5
RGBfromXYZ( officialXYZ('D65'), 'sRGB' ) # R G B # [1,] 1 1 1 # not really 1s, but difference < 1.e-7 RGBfromXYZ( c(.3127,0.3290,0.3583)/0.3290, 'sRGB' ) # R G B # [1,] 0.9998409 1.000023 1.00024 difference > 1.e-5
extract a subset of the spectra in a colorSpec object.
The subset can be specified by indexes, by a logical vector, or by a regular expression matching the
specnames
## S3 method for class 'colorSpec' subset( x, subset, ... )
## S3 method for class 'colorSpec' subset( x, subset, ... )
x |
a colorSpec object |
subset |
an integer vector, a logical vector, or a regular expression |
... |
additional arguments ignored |
If subset
is an integer vector, each integer must be between 1 and M,
where M the number of spectra in x
.
No duplicates are allowed.
The number of spectra returned is equal to length(subset)
.
It is OK for the length
to be 0, in which case the function returns the empty subset.
If subset
is a logical vector, its length must be equal to M.
The number of spectra returned is equal to the number of TRUE
s in subset
.
If subset
is a regular expression, the number of spectra returned is equal to
the number of specnames(x)
matched by the expression.
subset(x)
returns a colorSpec object with the same organization
as x
.
Exception: if the organization of x
is 'vector'
and the subset is empty,
then the returned object is a matrix with 0 columns.
subset()
can also be used for re-ordering the spectra;
just set argument subset
to the desired permutation vector.
tritanope = subset( lms2000.1nm, 1:2 ) # keep long and medium cone fundamentals, but drop the short sml2000.1nm = subset( lms2000.1nm, 3:1 ) # reorder from short to long
tritanope = subset( lms2000.1nm, 1:2 ) # keep long and medium cone fundamentals, but drop the short sml2000.1nm = subset( lms2000.1nm, 3:1 ) # reorder from short to long
These are 3 built-in colorSpec objects,
with quantity
equal to 'energy->electrical'
.
BT.709.RGB
a theoretical RGB responder to light. The 3 responsivity spectra are constructed so that the RGBs from this theoretical camera, when displayed on an sRGB display after correct EOTF adjustment, would emit light with the same XYZs as the captured scene (up to a constant multiple). All three responsivities have negative lobes.
Adobe.RGB
a theoretical RGB responder to light. The 3 responsivity spectra are constructed so that the RGBs from this theoretical camera, when displayed on an Adobe RGB display after correct EOTF adjustment, would emit light with the same XYZs as the captured scene (up to a constant multiple). All three responsivities have negative lobes.
ACES.RGB
a theoretical RGB responder to light. Unlike the two above cameras, the responsivities are non-negative and so this camera could be built, in principle. These are the ACES RICD (Reference Input Capture Device) spectral sensitivities.
All are colorSpec objects with quantity
equal to 'energy->electrical'
and 3 spectra:
r
, g
, and b
.
The wavelengths are 360 to 830 nm at 1 nm intervals.
All responsitivity spectra are linear combinations of the spectra in xyz1931.1nm
.
These 3 theoretical cameras satisfy the Maxwell-Ives criterion by construction.
For BT.709.RGB
and Adobe.RGB
,
the responsivities are scaled so the response to D65.1nm
is RGB=(1,1,1).
These responsivities have negative lobes.
The BT.709 primaries and white point are the same as those of sRGB (though the EOTF functions are different).
Adobe RGB and sRGB share the same Red, Blue, and White chromaticities, and only differ in the Green.
This implies that for both cameras the Green output is 0 at Red and Blue, and 1 at White.
This in turn implies that the Green output is identical for both cameras for all input spectra,
and so the Green responsivity spectra are identical for both cameras.
For ACES.RGB
the responsivities are area normalized
as in Annex C of S-2008-001.
They are scaled so that the response to Illuminant E is RGB=(1,1,1).
For an example of white-balancing, as in Annex B,
see the examples below.
Poynton, Charles. Digital Video and HD - Algorithms and Interfaces. Morgan Kaufmann. Second Edition. 2012. Figure 26.5 on page 302.
Academy Color Encoding Specification (ACES). S-2008-001. 2011. Annex B, pp. 23-25. Annex C, pp. 26-33.
quantity()
,
D65.1nm
,
xyz1931.1nm
,
ptransform()
,
calibrate()
,
vignette Blue Flame and Green Comet
####### BT.709.RGB is created using the following recipe ######## P = matrix( c(0.64,0.33,NA, 0.3,0.6,NA, 0.15,0.06,NA ), 3, 3, byrow=TRUE ) rownames(P) = c('R','G','B') BT.709.RGB = ptransform( xyz1931.1nm, P, D65.1nm ) quantity(BT.709.RGB) = "energy->electrical" ####### Adobe.RGB recipe is the same, except for the matrix P ######## P = matrix( c(0.64,0.33,NA, 0.21,0.71,NA, 0.15,0.06,NA ), 3, 3, byrow=TRUE ) rownames(P) = c('R','G','B') Adobe.RGB = ptransform( xyz1931.1nm, P, D65.1nm ) quantity(Adobe.RGB) = "energy->electrical" ####### white-balancing ACES.RGB for CIE Standard Illuminant D60 ######## # in a scene illuminated by daylight illuminant D6003, # and with a perfect-reflecting-diffuser in that scene, # object 'camera1' would have response RGB=(1,1,1) for that diffuser. D6003 = daylightSpectra( 6000*1.4388/1.4380, wavelength=wavelength(ACES.RGB) ) camera1 = calibrate( ACES.RGB, D6003, 1, method='scaling' )
####### BT.709.RGB is created using the following recipe ######## P = matrix( c(0.64,0.33,NA, 0.3,0.6,NA, 0.15,0.06,NA ), 3, 3, byrow=TRUE ) rownames(P) = c('R','G','B') BT.709.RGB = ptransform( xyz1931.1nm, P, D65.1nm ) quantity(BT.709.RGB) = "energy->electrical" ####### Adobe.RGB recipe is the same, except for the matrix P ######## P = matrix( c(0.64,0.33,NA, 0.21,0.71,NA, 0.15,0.06,NA ), 3, 3, byrow=TRUE ) rownames(P) = c('R','G','B') Adobe.RGB = ptransform( xyz1931.1nm, P, D65.1nm ) quantity(Adobe.RGB) = "energy->electrical" ####### white-balancing ACES.RGB for CIE Standard Illuminant D60 ######## # in a scene illuminated by daylight illuminant D6003, # and with a perfect-reflecting-diffuser in that scene, # object 'camera1' would have response RGB=(1,1,1) for that diffuser. D6003 = daylightSpectra( 6000*1.4388/1.4380, wavelength=wavelength(ACES.RGB) ) camera1 = calibrate( ACES.RGB, D6003, 1, method='scaling' )
Retrieve or set the wavelengths of a colorSpec object. Retrieve the number of wavelengths, and whether the wavelength sequence is regular.
## S3 method for class 'colorSpec' wavelength(x) ## S3 replacement method for class 'colorSpec' wavelength(x) <- value ## S3 method for class 'colorSpec' numWavelengths(x) ## S3 method for class 'colorSpec' is.regular(x) step.wl(x)
## S3 method for class 'colorSpec' wavelength(x) ## S3 replacement method for class 'colorSpec' wavelength(x) <- value ## S3 method for class 'colorSpec' numWavelengths(x) ## S3 method for class 'colorSpec' is.regular(x) step.wl(x)
x |
a colorSpec R object |
value |
a numeric vector with length equal to the number of wavelengths in |
If the organization
of x
is 'df.col'
, then x
is a data.frame
and the wavelength vector is stored in the first column of x
.
Otherwise, the wavelength vector is stored as attr(x,'wavelength')
.
wavelength()
returns a numeric vector with the wavelengths of the spectra, in nanometers.
numWavelengths(x)
is equal to length(wavelength(x))
but much more efficient.
is.regular()
returns TRUE
or FALSE
,
depending on whether the step between consecutive wavelengths is a constant.
A truncation error of 1.e-6 nm is tolerated here.
For example, the X-Rite ColorMunki spectrometer in hi-res mode has a step of 3.33333nm,
and it is considered regular.
step.wl()
returns the mean step in nm,
whether the wavelengths are regular or not.
xyz1931.1nm |
the 1931 2° functions from 360 to 830 nm, at 1nm intervals |
xyz1931.5nm |
the 1931 2° functions from 380 to 780 nm, at 5nm intervals |
Each is a colorSpec object organized as a matrix with 3 variables.
x |
the x-bar responsivity function |
y |
the y-bar responsivity function |
z |
the z-bar responsivity function |
Günther Wyszecki and W.S. Stiles. Color Science : Concepts and Methods, Quantitative Data and Formulae. Second Edition. Wiley-Interscience. 1982. Table I(3.3.1). pp. 723-735.
ASTM E 308 - 01. Standard Practice for Computing the Colors of Objects by Using the CIE System. Table 1
summary(xyz1931.1nm) white.point = product( D65.1nm, xyz1931.1nm, wave='auto' )
summary(xyz1931.1nm) white.point = product( D65.1nm, xyz1931.1nm, wave='auto' )
xyz1964.1nm |
the 10° 1964 functions from 360 to 830 nm, at 1nm intervals |
xyz1964.5nm |
the 10° 1964 functions from 380 to 780 nm, at 5nm intervals |
Each is a colorSpec object organized as a matrix with 3 columns.
x |
the x-bar responsivity function |
y |
the y-bar responsivity function |
z |
the z-bar responsivity function |
Günther Wyszecki and W.S. Stiles. Color Science : Concepts and Methods, Quantitative Data and Formulae. Second Edition. Wiley-Interscience. 1982. Table I(3.3.1). pp. 723-735.
ASTM E 308 - 01. Standard Practice for Computing the Colors of Objects by Using the CIE System. Table 1
summary(xyz1964.1nm) white.point = product( D65.1nm, xyz1964.1nm, wave='auto' )
summary(xyz1964.1nm) white.point = product( D65.1nm, xyz1964.1nm, wave='auto' )