Nothing
# ... colorSpec objects that can be naturally multiplied
# there are 5 different types of sequences
#
# wavelength 'identical' then all objects have the same wavelength (default)
# 'auto' resample all of them to a suitable computed new sequence
# numeric vector, new wavelengths to resample to
#
# integration 'rectangular' (the default), or 'trapezoidal'
#
# return: a new colorSpec object,
# or a matrix with a column for each output channel and a row for each input spectrum
product.colorSpec <- function( ... ) #, wavelength='identical' , integration='rectangular'
{
theList = list(...)
n = length(theList)
if( n == 0 )
{
log.string( ERROR, "No arguments !" )
return(NULL)
}
# assign defaults
wavelength = 'identical'
integration = 'rectangular'
# get the LHS names
theNames = names( theList )
if( ! is.null(theNames) )
{
# extract just the non-named arguments as the colorSpec objects
mask.noname = nchar(theNames) == 0
# and so the complement are named arguments, which we want to pass to resample()
mask.named = ! mask.noname
sig = pmatch( theNames, c("wavelength","integration") )
idx = which( sig == 1 )
if( length(idx) == 1 )
{
wavelength = theList[[idx]]
mask.named[idx] = FALSE
}
idx = which( sig == 2 )
if( length(idx) == 1 )
{
integration = theList[[idx]]
mask.named[idx] = FALSE
option = c("rectangular","trapezoidal")
idx = pmatch( integration, option )
if( is.na(idx) )
{
log.string( ERROR, "integration='%s' is invalid.", as.character(integration) )
return(NULL)
}
integration = option[idx]
}
# print( mask.named )
# all named args, except wavelength !
resample.argv = theList[ mask.named ] #; print( resample.argv ) # wavelength is not here, it is passed to resample() separately
log.object( TRACE, wavelength )
log.object( TRACE, integration )
}
else
{
mask.noname = rep( TRUE, n )
resample.argv = NULL
}
if( sum(mask.noname) == 0 )
{
log.string( ERROR, "No colorSpec arguments." )
return(NULL)
}
# now get the actual RHS names
theNames = as.character( substitute(list(...)) ) # ; print( theNames )
if( length(theNames) == n+1 )
theNames = theNames[ 2:(n+1) ]
else
{
log.string( WARN, "length(theNames) = %d != %d. Using fake names.", length(theNames), n+1 )
theNames = sprintf( "Name%d", 1:n )
}
# form subset for processing
theList = theList[ mask.noname ]
theNames = theNames[ mask.noname ]
names(theList) = theNames #; print( str(theList) )
n = length( theList )
log.string( TRACE, "Found %d objects in '...'", n )
if( n == 0 )
{
log.string( ERROR, "No colorSpec arguments." )
return(NULL)
}
type.return = returnTypeProduct( theList )
if( is.na(type.return) )
{
log.string( ERROR, "Cannot form product, because the argument sequence is invalid." )
return(NULL)
}
if( n == 1 )
{
# a single colorSpec, a trivial case
return( theList[[1]] )
}
# gather type, spectra, and quantity
type = character(n)
quantity = character(n)
spectra = integer(n)
k.character = 0
for( k in 1:n )
{
if( is.character(theList[[k]]) )
{
k.character = k
type[k] = "character"
quantity[k] = "character"
spectra[k] = 1L
}
else
{
type[k] = type( theList[[k]] )
quantity[k] = quantity( theList[[k]] )
spectra[k] = numSpectra( theList[[k]] )
}
}
# check # of spectra
if( any(spectra == 0) )
{
log.object( ERROR, spectra )
log.string( ERROR, "one or more of of the %d colorSpec objects has 0 spectra.", n )
return(NULL)
}
if( type.return != "matrix" )
{
# easier case
spectra.multi = spectra[ 1 < spectra ]
spectra.unique = unique( spectra.multi )
if( 2 <= length(spectra.unique) )
{
log.object( ERROR, spectra )
log.string( ERROR, "# of spectra in list is invalid." )
return(NULL)
}
}
else
{
# more complex, try to split sequence into a begin and end (left and right) parts
# each part must meet conditions in previous if block
res = splitSequence( spectra )
if( is.null(res) )
{
log.object( ERROR, spectra )
log.string( ERROR, "# of spectra in list is invalid." )
return(NULL)
}
seq.beg = res$begin
seq.end = res$end
if( res$ambiguous )
log.string( WARN, "The returned matrix is ambiguous; because it depends on the splitting. Inspect the matrix carefully." )
}
# examine wavelengths
if( wavelength[1] == 'auto' ) wavelength = NULL
ok = is.null(wavelength) || is.character(wavelength) || is.numeric(wavelength)
if( ! ok )
{
log.string( ERROR, "wavelength type '%s' is invalid", typeof(wavelength) )
return(NULL)
}
do.resample = TRUE
if( is.character(wavelength) )
{
if( wavelength[1] == "identical" )
{
# verify that all the objects have identical wavelengths
wavelength = wavelength.colorSpec( theList[[1]] )
for( k in 2:n )
{
if( k == k.character ) next
if( ! identical( wavelength.colorSpec(theList[[k]]), wavelength ) )
{
log.string( ERROR, "'%s' does not have the same wavelengths as '%s'. Consider option wavelength='auto'.",
theNames[1], theNames[k] )
return(NULL)
}
}
do.resample = FALSE
}
else
{
log.string( ERROR, "wavelength option '%s' is invalid.", wavelength[1] )
return(NULL)
}
}
else if( is.null(wavelength) )
{
# compute a new wavelength sequence
mask = sapply( theList, is.colorSpec ) #; print( mask )
# sublist = theList[mask] ; print( str(sublist) )
wavelength = commonWavelength( lapply( theList[mask], wavelength.colorSpec ) )
log.string( INFO, "From %d objects, computed %d common wavelengths: %g to %g nm with step of %g nm",
sum(mask), length(wavelength), wavelength[1], wavelength[length(wavelength)], wavelength[2]-wavelength[1] )
}
#if( ! isRegularSequence(wavelength) )
# {
# log.string( ERROR, "Cannot form product, because resampling wavelength sequence is not regular." )
# return(NULL)
# }
if( do.resample )
{
# resample all of them
log.string( INFO, "Resampling all %d objects...", n )
#print( resample.argv )
# make arguments for resample(). Always wavelength and maybe more in resample.argv.
argv = c( list(wave=wavelength), resample.argv )
#print( str(argv) )
for( k in 1:n )
{
if( k == k.character ) next
#print( "-------------" )
#print( k )
#print( str(theList[[k]]) )
# modifyList() will add argv to the colorSpec argument
argv.resample = modifyList( list(theList[[k]]), argv ) #; print( str(argv.resample) )
theList[[k]] = do.call( resample, argv.resample ) # resample( theList[[k]], wave=wavelength )
}
}
#if( ! is.regular( theList[[1]] ) )
# {
# log.string( ERROR, "Cannot form product, because wavelength sequence is not regular." )
# return(NULL)
# }
step.wl = step.wl( theList[[1]] )
# convert to proper quantities
for( k in 1:n )
{
if( k == k.character ) next
pattern = "^absorb"
if( grepl( pattern, quantity[k] ) )
{
# convert from absorbance to transmittance
log.string( DEBUG, "converting spectrum %d from absorbance to transmittance.", k )
theList[[k]] = linearize(theList[[k]])
}
pattern = "^photons"
if( grepl( pattern, quantity[k] ) )
{
# convert from actinometric to radiometric
log.string( DEBUG, "converting spectrum %d from actinometric to radiometric.", k )
theList[[k]] = radiometric(theList[[k]])
}
}
if( type.return == "matrix" )
{
# output is *not* a colorSpec
# output is a matrix - a product of a left part and a right part
mat.left = simpleProduct( theList[ seq.beg ] )
mat.right = simpleProduct( theList[ seq.end ] )
p = length(wavelength)
if( isRegularSequence(wavelength) )
{
log.string( INFO, "Wavelength sequence is regular. step=%g. integration='%s'", step.wl, integration )
# this is the easy case, the step size can be brought outside
if( integration == 'trapezoidal' )
{
# scale first and last rows of mat.left. Do not bother to find which matrix is smaller
mat.left[c(1,p), ] = 0.5 * mat.left[c(1,p), ]
}
out = step.wl * crossprod( mat.left, mat.right ) #; print( str(out) )
}
else
{
log.string( INFO, "Wavelength sequence is irregular. integration='%s'", integration )
#weight = diff( wavelength, lag=2 ) / 2
#w.end = c( wavelength[2] - wavelength[1], wavelength[p] - wavelength[p-1] )
weight = breakandstep(wavelength,integration)$stepvec
if( length(weight) != p )
{
log.string( ERROR, "length(weight)= %d != %d.", length(weight), p )
return(NULL)
}
# apply weight to the smaller matrix
q = min( ncol(mat.left), ncol(mat.right) )
mat.weight = matrix( weight, nrow=p, ncol=q )
if( q == ncol(mat.left) )
mat.left = mat.weight * mat.left
else
mat.right = mat.weight * mat.right
out = crossprod( mat.left, mat.right )
}
# assign row names from the left part
idx = which( spectra == nrow(out) )
idx = idx[ 1 ]
rownames(out) = specnames(theList[[idx]])
# assign col names from the right part
idx = which( spectra == ncol(out) )
idx = idx[ length(idx) ]
colnames(out) = specnames(theList[[idx]])
if( all( nchar(colnames(out)) == 1 ) )
# change 'x' to 'X', and 'r' to 'R', etc.
colnames(out) = toupper(colnames(out))
# this line is not good; the attr is printed when the matrix is !
#attr( out, "product" ) = list( wavelength=wavelength, integration=integration )
return( out )
}
if( k.character == 0 )
{
# the case without a variable material is easier
# compute simple product of matrices 1 to n
mat.cum = simpleProduct( theList )
# channels = max( spectra )
# for the column names, pick the first one with max number of spectra
idx = which.max( spectra )
colnames(mat.cum) = specnames( theList[[ idx ]] )
if( type[1] == "light" )
# starts with light and ends with a material
quantity = quantity( theList[[1]] )
else if( type[n] == "responsivity.light" )
# starts with material and ends with a responder
quantity = quantity( theList[[n]] )
else
{
# just a sequence of materials ! could be transmittance or reflectance !
# use crude rule to select which one
if( any( quantity == "transmittance" ) )
quantity = "transmittance"
else
quantity = "reflectance"
}
out = colorSpec( mat.cum, wavelength, quantity=quantity )
attr( out, "sequence" ) = theList
return( out )
}
# there is a variable material "slot".
# since materials are not fluorescent, the position of the "slot" does not matter.
# the type of the returned colorSpec will be "responsivity.material"
if( type.return != "responsivity.material" )
{
log.string( FATAL, "type.return='%s' != '%s'.", type.return, "responsivity.material" )
return(NULL)
}
quantity = quantity( theList[[n]] )
pattern = "^(energy|power)"
if( ! grepl( pattern, quantity ) )
{
log.string( FATAL, "Internal error. Last object quantity='%s' does not begin with 'energy' or 'power'.", quantity )
return(NULL)
}
quantity = sub( pattern, "material", quantity )
# compute simple product of matrices 1 to n, but skip the variable slot
mat.left = simpleProduct( theList[ 1:(k.character-1) ] )
mat.right = simpleProduct( theList[ (k.character+1):n ] )
channels = max( ncol(mat.left), ncol(mat.right) )
if( ncol(mat.left) < channels )
mat.left = matrix( mat.left, nrow(mat.left), channels )
if( ncol(mat.right) < channels )
mat.right = matrix( mat.right, nrow(mat.right), channels )
mat.cum = mat.left * mat.right
# channels = ncol( mat.cum )
specnames = specnames( theList[[n]] )
if( length(specnames) < channels )
specnames = sprintf( "%s.%d", specnames[1], 1:channels )
colnames(mat.cum) = specnames
out = colorSpec( mat.cum, wavelength, quantity=quantity )
attr( out, "sequence" ) = theList
return( out )
}
# .wavelist a list of wavelength vectors
# returns a suitable intersection vector of wavelengths
# always begins and ends on integral nm, and always regular
commonWavelength <- function( .wavelist )
{
if( length(.wavelist) == 0 ) return(NULL)
if( length(.wavelist) == 1 ) return(.wavelist[[1]])
wmin = max( sapply( .wavelist, function(x) { x[1] } ) )
wmax = min( sapply( .wavelist, function(x) { x[length(x)] } ) )
wmin = round(wmin)
wmax = round(wmax)
if( wmax <= wmin )
{
log.string( ERROR, "wavelength intersection [%g,%g] is empty.", wmin, wmax )
return(NULL)
}
wstep = min( sapply( .wavelist, function(x) { mean(diff(x)) } ) )
# round to nearest power of 2
if( wstep < 1 )
wstep = 2 ^ round( log2(wstep) )
out = seq( wmin, wmax, by=wstep )
return( out )
}
# .list a list of valid colorSpec objects
# returns product matrix, with possible replication of columns
simpleProduct <- function( .list )
{
n = length( .list )
out = coredata( .list[[1]], forcemat=T )
if( n == 1 ) return(out)
# verify spectra counts
spectra = sapply( .list, numSpectra.colorSpec )
channels = max( spectra )
mask = spectra==1 | spectra==channels
if( ! all(mask) )
{
log.object( FATAL, spectra )
log.string( FATAL, "Internal error. Invalid spectra counts." )
return(NULL)
}
if( ncol(out) < channels )
out = matrix( out, nrow(out), channels )
for( k in 2:n )
{
mat = coredata( .list[[k]], forcemat=T )
if( ncol(mat) < channels )
mat = matrix( mat, nrow(mat), channels )
out = out * mat
}
return( out )
}
# .list as passed to product.colorSpec()
#
# returns type of product colorSpec objects
# can be "matrix", or NA in case of an invalid .list
# does not check the number of spectra, which might invalidate .list later
returnTypeProduct <- function( .list )
{
n = length( .list )
theNames = names( .list )
if( is.null(theNames) )
{
theNames = as.character( substitute(.list) ) #; print( theNames )
theNames = theNames[ 2:(n+1) ]
}
out = as.character( NA )
type = character(n)
k.character = 0
for( k in 1:n )
{
if( is.character(.list[[k]]) )
{
if( 0 < k.character )
{
log.string( ERROR, "String '%s' is too many strings. There can be at most 1 string.", .list[[k]] )
return(out)
}
k.character = k
if( k==1 || k==n )
{
log.string( ERROR, "String '%s' must appear in the interior of the argument list.", .list[[k]] )
return(out)
}
type[k] = "character"
next
}
# print( sprintf( 'class %s', paste(clist[[k]],collapse=',') ) )
if( ! "colorSpec" %in% class( .list[[k]] ) )
{
log.string( ERROR, "class(%s) = '%s', which is invalid.", theNames[k], paste( class(.list[[k]]) ,collapse=',') )
return(out)
}
if( ! is.colorSpec( .list[[k]] ) )
{
log.string( ERROR, "%s is not a valid colorSpec object.", theNames[k] )
return(out)
}
type[k] = type( .list[[k]] )
}
if( n == 1 )
# trivial case
return( type[1] )
if( 3 <= n )
{
# check interior
for( k in 2:(n-1) )
{
if( k == k.character ) next
if( type[k] != "material" )
{
log.string( ERROR, "type(%s) = '%s' which is invalid for list interior. It must be 'material'.",
theNames[k], type[k] )
return(out)
}
}
}
# good so far, now determine the return type
if( type[1]=="material" && type[n]=="material" )
out = "material"
else if( type[1]=="light" && type[n]=="material" )
out = "light"
else if( type[1]=="material" && type[n]=="responsivity.light" )
out = "responsivity.light"
else if( type[1]=="light" && type[n]=="responsivity.light" )
{
# 2 cases
if( k.character == 0 )
out = "matrix"
else
out = "responsivity.material" # variable material slot
}
else if( type[1]=="material" && type[n]=="responsivity.material" )
out = "matrix"
else
{
log.string( ERROR, "Cannot form a product. The types of the colorSpec objects are invalid" )
}
return( out )
}
# x a sequence of 2 or more positive integers
# split x into 2 parts, so each one has at most 1 unique value > 1
# returns a list with:
# begin a sequence from 1:k
# end a sequence from (k+1):n
# or NULL in case that x cannot be split
splitSequence <- function( x )
{
n = length(x)
out = list()
out$begin = 1:(n-1)
out$end = n
out$ambiguous = FALSE
x.multi = x[ 1 < x ]
if( length(x.multi) == 0 )
# all 1s
return(out)
x.multi.unique = unique(x.multi)
if( 3 <= length(x.multi.unique) )
# not splittable
return(NULL)
if( length(x.multi.unique) == 1 )
{
# exactly one unique value greater than 1
if( 3 <= n )
out$ambiguous = x.multi.unique %in% x[ 2:(n-1) ]
return(out)
}
# there are 2 unique values greater than 1
# there must be 1 or more jumps
jump = which( diff(x.multi) != 0 )
if( 1 < length(jump) )
# not splittable
return(NULL)
# only 1 jump
# find last occurence of x1
k = which( x == x.multi[1] )
k = k[ length(k) ]
out$begin = 1:k
out$end = (k+1):n
return( out )
}
#-------- UseMethod() calls --------------#
product <- function( ... ) #, wavelength="identical"
{
UseMethod("product")
}
#-------- testing --------------#
testVarArg <- function( ... ) #, wavelength="identical"
{
theList = list(...)
print( as.character( substitute(list(...)) ) )
theNames = names(theList)
print( theNames )
# split into unnamed and named parts
if( is.null(theNames) )
{
list.noname = theList
list.named = NULL
}
else
{
mask = nchar(theNames)==0
list.noname = theList[ mask ]
list.named = theList[ ! mask ]
}
cat( "No names:\n" )
print( str(list.noname) )
cat( "\nNamed:\n" )
print( str(list.named) )
return(F)
print( str(substitute(...)) )
print( deparse(substitute(...)) )
print( substitute(..2) )
print( str(...) )
print( deparse(substitute(..1,parent.frame(n=2) ) ) )
print( deparse(substitute(..1) ) )
theList = list(...)
print( str(theList) )
# print( str(pairlist(...)) )
print( names(theList) )
for( k in 1:length(theList) )
print( deparse(substitute( theList[k], parent.frame(n = 2) ) ) )
# print( str( c(...) ) )
print( wavelength )
print( lapply( theList, class ) )
return( invisible(TRUE) )
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.