knitr::opts_chunk$set(echo = TRUE) library(colorSpec)  colorSpec is an R package providing an S3 class with methods for color spectra. It supports the standard calculations with spectral properties of light sources, materials, cameras, eyes, scanners, etc.. And it works well with the more general action spectra. Many ideas are taken from packages hsdar @hsdar, hyperSpec @hyperSpec, pavo @pavo, photobiology @photobiology, and zoo @zoo. Some features: • a clear classification of the common color spectra into 4 types • 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) • 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 Some non-features: • There is no support for 3D colors spaces other than XYZ and RGB; see packages colorspace @colorspace and colorscience @colorscience for more 3D spaces. • there are few non-linear operations. 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. • there is little support for scientific units; for these see packages photobiology @photobiology and colorscience @colorscience • photons are parameterized by wavelength in nanometers (nm); other wavelength units (such as Ångstrom and micron) and alternative parameterizations (such as wavenumber and electronvolt) are not available.  includetable <- function( path, height ) { tmp1 <- URLencode( paste(readLines(path,warn=FALSE), collapse="\n"), reserved = TRUE ) tmp2 <- sprintf( '", style="border: none; seamless:seamless; width: 900px; height: %s" ></iframe>', height ) cat( '<iframe src="data:text/html;charset=utf-8,', tmp1 , tmp2 ) } includeplain <- function( path ) { tmp <- readLines(path,warn=FALSE) writeLines( tmp ) }  Spectrum Types Pick up any book on color physics (e.g. @wyszecki2000color, @packer2003, @oleari2016standard, or @koenderink) or color management (e.g. @giorgianni2009digital) and you will see plots of many spectra. Let's start with a simple division of these spectra into 4 basic types:  includetable("tables/table-1.1.html", "550px" )  For the infinite-dimensional spaces, the interval [380,780] is used for illustration; in specific calculations it can vary. Note that of the 4 vector spaces, only$L^*$and$M$are isomorphic, but we take the mathematical point of view that although they are isomorphic, they are not the same. For a proof of this isomorphism, see Appendix D. Multiplication operators are the infinite-dimensional generalization of diagonal matrices. For more background on this functional analysis, see @wiki:MO and @LangReal. For the finite-dimensional spaces, it takes the full sequence of wavelengths and not just the endpoints. The wavelength sequence is typically regular not always. In this case all 4 vector spaces are isomorphic (since they are the same dimension), but we still take the mathematical point of view that they are not the same space. The last type = 'responsivity.material' is perhaps the least common. There is an example in @giorgianni2009digital (Figure 10.11a, page 141) of a scanner, where the 3 spectra are called the effective spectral responsivities. There is also a standard scanner from SMPTE, see @7292043. Every colorSpec object has one of these types, but it is not stored with the object. The object stores a quantity which then determines the type; see the next section for more discussion. A synonym for type might be space, but this could be confused with color space. colorSpec does not actually use the finite-dimensional representations in Table 1.1; the organization is flexible. And it would not be efficient memory use to store a diagonal matrix as such. For discussion of the organization, see section 4. Given 2 finite-dimensional spectra of types 'light' and 'responsivity.light' the response (a real number) is their dot product multiplied by the step between wavelengths. All materials in this document are non-fluorescent; i.e. the outgoing photons reflected (or transmitted) only come from incoming photons of the same wavelength. A transparent material transmits an incoming light spectrum and a new spectrum emerges on the other side. If the material is not fluorescent, the outgoing spectrum is the same as the incoming, except there is a reduction of power that depends only on the wavelength (and the material). If the light power were divided into N bins, the transmitted power spectrum would be a diagonal NxN matrix times the incoming spectrum. A reflectance spectrum is mathematically the same as a transmittance spectrum, except we compare the outgoing light spectrum to that of a perfect reflecting diffuser. Such a material does not exist, like many concepts in physics, but it is a very useful idealization. Spectrum Quantities Unfortunately there are two common metrics for quantifying spectra with type='light' - energy of photons and number of photons. The former - radiometric - is the oldest, being used in the 19^th^ century. The latter - actinometric - was not used until the 20th century (after the modern concept of photons was proposed in 1905). So colorimetry uses radiometric quantities by convention and actinometric ones are converted to radiometric automatically for calculations. The conversion is easy; see the function radiometric(), @packer2003 pp. 93-94, and @oleari2016standard p. 12. Similarly, 'responsivity.light' can be radiometric (e.g. the CIE color matching functions) or actinometric (e.g. the quantum efficiency of a CMOS sensor). These actinometric spectra are also converted to radiometric on the fly. For responsivity, we distinguish between 3 types of response: electrical, neural, and action. In colorSpec this 3-way distinction is only used in a few places: • for the y label of the spectrum in plot() • to determine the default adaption method in calibrate() • for the conversion equations in radiometric() and actinometric() Note that the action response is really a grab-bag for responses that are neither electrical (a modern solid-state photosensor) nor neural (a biological eye). Here are the valid types and their quantities:  includetable("tables/table-2.1.html", "640px" )  The colorSpec quantities are typically not the same as the SI quantities; they are more general. First consider light sources (type='light'). The colorSpec quantity='energy' includes all 5 of these power-based SI quantities: radiant power (radiant flux), irradiance, radiant exitance, radiant intensity, and radiance. And it also includes these energy-based quantities: radiant energy, radiant exposure, and the time integrals of radiant exitance, radiant intensity, and radiance. Thus quantity='energy' includes 10 true physical SI quantities, which all include energy and optionally include area, solid angle, and time. Similary, the colorSpec quantity='photons' includes all 5 of these SI quantities: photon flux, photon irradiance, photon exitance, photon intensity, and photon radiance. It also includes these 5 quantities integrated over time. Versions of colorSpec before 0.7-1 used power in place of energy. But now we have switched to energy; see Appendix E for the reasons why. power and power->* are still supported, but deprecated, and will eventually be phased out. For type='light' and type='responsivity.light', each radiometric quantity has a corresponding actinometric quantity. The following table shows the correspondences:  includetable("tables/table-2.2.html", "240px" )  The colorSpec functions radiometric() and actinometric() convert back and forth between the two metrics. For energy and energy->electrical and energy->action the functions actually do assume the example units. For energy->electrical the example units in the table are in common use for electronic cameras. Note that for energy->action, the action we have in mind is photosynthesis. A quick internet search shows that the maximum theoretical photosynthesis response is between 1/16 and 1/8 of an$\text{O}_2$molecule per photon. For energy->neural see the functions man pages for more discussion. Since these are spectra parameterized by nm, the example units should all add$\text{nm}^{-1}$at the end, but this is suppressed for simplicity. Now consider materials (type='material'). The situation here is simpler. The colorSpec quantity='reflectance', 'transmittance', and 'absorbance' correspond directly to the SI quantities. All reflecting materials are Lambertian and opaque, and all transmitting materials have only direct transmission with no scatter. Construction of colorSpec objects The user constructs a colorSpec object x using the function colorSpec(): x <- colorSpec( data, wavelength, quantity='auto', organization='auto', specnames=NULL )  The arguments are: data a vector or matrix of the spectrum values. In case data is a vector, x has a single spectrum and the number of points in that spectrum is the length of data. In case data is a matrix, the spectra are stored in the columns, so the number of points in each spectrum is the number of rows in data. It is OK for the matrix to have only 0 or 1 column. wavelength a numeric vector of wavelengths for all the spectra in x. The length of this vector must be equal to NROW(data), and the unit must be nanometers. The sequence must be increasing. The wavelength of x can be changed after construction. quantity a character string giving the quantity of all spectra; see Table 2.1 for a list of valid values. In case quantity='auto', a guess is made from the specnames. The quantity of x can be changed later. organization a character string giving the desired organization of the returned colorSpec object. In case organization='auto', the organization is 'vector' or 'matrix' depending on data. The organization of x can be changed after construction. See the next section for discussion of all 4 possible organizations. specnames a character vector with length equal to the number of spectra in data, and with no duplicates. If specnames=NULL and data is a vector, then specnames is set to deparse(substitute(data)). If specnames=NULL and data is a matrix, then specnames is set to colnames(data). If specnames is still not a character vector with the right length, or if there are duplicate names, then specnames is set to 'S1', 'S2', ... with a warning message. Names can be changed after construction. Compare colorSpec() with the function stats::ts(). colorSpec object organization A spectrum is similar to a time-series (with time replaced by wavelength), and so the organization of a colorSpec object is similar to that of the time-series objects in package stats. A single time-series is organized as a vector with class ts, and a multiple time series is organized as a matrix (with the series in the columns) with class mts. We decided to use a single class name colorSpec, continue the idea of different organizations, and allow 2 more organizations. Here are the 4 possible organizations, in order of increasing complexity: 'vector' The object is a numeric vector with attributes but no dimensions, like a time-series ts. This organization works for a single spectrum only, which is very common. The common arithmetic operations work well with this organization. The length of the vector is the number of wavelengths. The class of the object is c('colorSpec','numeric'). 'matrix' The object is a matrix with attributes, like a multiple time-series mts. This is probably the most suitable organization in most cases, but it does not support extra data (see 'df.row' below). The common arithmetic and subsetting operations work well; and even round() works. The number of columns is the number of spectra, and the spectrum names are stored as the column names. This organization can be used for any number of spectra, including 0 or 1. The class of the object is c('colorSpec', 'matrix'). 'df.col' The object is a data frame with attributes. The spectra are stored in the columns. But the first column is always the wavelength sequence, so the spectra are in columns 2:(M+1), where M is the number of spectra. This organization mirrors the most common organization in text files and spreadsheets. The common arithmetic operations do not work, and the initial wavelength column is awkward to handle. The spectrum names are stored as the column names of the data frame. This organization can be used for any number of spectra, including 0 or 1. This organization imitates the "long" format in package hyperSpec. The class of the object is c('colorSpec', 'data.frame'). 'df.row' The object is a data frame with attributes. The last (right-most) column is a matrix with spectra in the rows. This matrix is the transpose of the matrix used when the organization is 'matrix'. The common arithmetic operations do not work. The spectrum names are stored as the row names of the data frame. This organization can be used for any number of spectra, including 0 or 1. This organization imitates the "tall" format in package hyperSpec. This is the only organization that supports extra data associated with each spectrum, such as physical parameters, time parameters, descriptive strings, or whatever. This extra data occupies the initial columns of the data frame that come before the spectra, and can be any data frame with the right number of rows. This extra data can be assigned to any spectrum with the 'df.row' organization. The class of the object is c('colorSpec', 'data.frame'). colorSpec object attributes The attribute list is kept as small as possible. Here it is:  includetable("tables/table-5.1.html", "430px" )  The user should never have to modify these using the function attr(). Spectrum File Import There are 5 text file formats that can be imported; no binary formats are supported yet. The function readSpectra() reads a few lines from the top of the file to try and determine the type. If successful, it then calls the appropriate read function; see the colorSpec reference guide for details. The file formats are: XYY There is a line matching '^(wave|wv?l)' (not case sensitive) followed by the the names of the spectra. This is the column header line. All lines above this one are taken to be metadata. This is probably the most common file format; see the sample file ciexyz31_1.csv. spreadsheet There is a line matching '^(ID|SAMPLE|Time)'. This line and lines below must be tab-separated. Fields matching '^[A-Z]+([0-9.]+)nm$' are taken to be spectral data and other fields are taken to be extradata. All lines above this one are taken to be metadata. The organization of the returned object is 'df.row'. This is a good format for automated acquisition of many spectra, using a spectrometer. See the sample file E131102.txt.

scope
This is a file format used by Ocean Optics spectrometer software. There is a line >>>>>Begin Processed Spectral Data<<<<<. The following lines contain wavelength and energy separated by a tab. There is only 1 spectrum per file. The organization of the returned object is 'vector'. See the sample file pos1-20x.scope.

CGATS

Appendix D - Proofs

    includeplain("proofs.txt")


Appendix E - Energy vs Power

Consider these subtle differences in the way light sources and responders (detectors) are appropriately measured:

• *Power* is an appropriate way to measure constant light sources, such as the lighting in an office, or a standard illuminant. But *energy* is appropriate for variable sources, such as a pulsed light source.
• The response to *power* is an appropriate way to measure non-integrating responders, such as a biological eye ('power->neural'), a photovoltaic cell ('power->electrical'), or photosynthesis ('power->action'). All of these respond (almost) instantaneously.
The response to *energy* is an appropriate way to measure integrating responders, such as an electronic camera ('energy->electrical'), or erythemal exposure ('energy->action'). For these responders there is a well-defined integration time.

Since color science emphasizes constant light sources and biological eyes, power has always seemed more appropriate to me than energy. But starting with colorSpec version 0.7-1 I decided to switched to energy for these reasons:

• Energy is more fundamental than power. Power is defined from energy by the messy process of differentiation. The conversion between energy of photons and number of photons is straightforward, without a messy integration time. The terms *energy-based* and *photon-based* are well-established in vision science, and in software packages like **photobiology** @photobiology.
• In flash-based photography (energy->electrical) what matters to the color of the photograph is the integral of the spectrum (the energy) of the flash bulb over the exposure interval of the camera. This is a case when the light spectrum is not constant; it can vary over that interval. Similarly, in photosynthesis (energy->action) what matters to the plant is the integral of daylight from sunrise to sunset. Think of the daytime as a very long pulse. For an example, see the file solar.exposure.txt in Appendix B.

I also considered allowing both energy and power, and both photons and photons/time. But this would force the user to decide whether a light source is constant or variable, and whether a responder/detector is integrating or non-integrating. So things quickly got complicated. These common radiant SI quantities - radiant power, irradiance, radiant exitance, radiant intensity, radiance - differ only in area and steradian. Time is now grouped with these 2 geometric units.

Session Information

wzxhzdk:18



Try the colorSpec package in your browser

Any scripts or data that you put into this service are public.

colorSpec documentation built on April 2, 2018, 5:05 p.m.