# tests xyYtoMunsell() and MunsellToxyY() over special colors # tests sRGBtoMunsell() and MunsellTosRGB() over special colors library( munsellinterpol ) options( width=144 ) printf <- function( msg, ... ) { mess = sprintf( msg[1], ... ) # should this really be msg[1] ? cat( mess, '\n' ) #, file=stderr() ) } # returns time in seconds, from an arbitrary origin gettime <- function() { if( requireNamespace('microbenchmark') ) return( microbenchmark::get_nanotime() * 1.e-9 ) else return( as.double( base::Sys.time() ) ) } testCentroids <- function() { # path = "../inst/extdata/Centrals_ISCC_NBS.txt" path = system.file( "extdata/Centroids_ISCC-NBS.txt", package='munsellinterpol' ) df = read.table( path, header=T, sep='\t', stringsAsFactors=F ) printf( "----------- testCentroids() %d samples -----------------------", nrow(df) ) # printf( "Testing centroids..." ) # go forwards HVC -> xyY HVC = HVCfromMunsellName( df$MunsellSpec ) time_start = gettime() res = MunsellToxyY( HVC, warn=FALSE ) time_elapsed = gettime() - time_start failures = sum( is.na(res$xyY[ ,1]) ) printf( "There were %d forward failures, out of %d samples. time=%g sec. %g/sample.", failures, nrow(df), time_elapsed, time_elapsed/nrow(HVC) ) if( 0 < failures ) return(FALSE) # now go backwards xyY -> HVC time_start = gettime() res = xyYtoMunsell( res$xyY, perf=TRUE ) time_elapsed = gettime() - time_start failures = sum( is.na(res$HVC[ ,1]) ) printf( "There were %d reverse failures, out of %d samples. time=%g sec. %g/sample.", failures, nrow(HVC), time_elapsed, time_elapsed/nrow(HVC) ) printf( "Performance:") cat( "Time Elapsed 5-number summary: " ) cat( fivenum(res$time.elapsed,na.rm=T), '\n' ) cat( "Iterations 5-number summary: " ) cat( fivenum(res$iterations,na.rm=T), '\n' ) cat( "Evaluations 5-number summary: " ) cat( fivenum(res$evaluations,na.rm=T), '\n' ) # compare HVC and res$HVC tol = 0.01 HVC.delta = abs(HVC - res$HVC) HVC.delta[ ,1] = pmin( HVC.delta[ ,1], 100 - HVC.delta[ ,1] ) err = rowSums( HVC.delta ) idx = which.max( err ) df = data.frame( row.names=1 ) df$HVC = HVC[ idx, , drop=F ] df$xyY = res$xyY[ idx, , drop=F ] df$HVC.back = res$HVC[ idx, , drop=F ] printf( "Maximum round-trip error = %g (tol=%g).", err[idx], tol ) print( df ) # how many exceeded the tolerance count = sum( tol < err ) if( 0 < count ) printf( "%d round-trip errors exceeded %g.", count, tol ) if( 0 < failures || 0 < count ) return(FALSE) return( TRUE ) } testOptimals <- function() { path = system.file( "extdata/OptimalColorsForIlluminantC.txt", package='munsellinterpol' ) df = read.table( path, header=T ) printf( "--------- testOptimals() %d samples -----------------------", nrow(df) ) xyY = as.matrix( df ) osname = Sys.info()[ 'sysname' ] solaris = grepl( 'sun', osname, ignore.case=TRUE ) out = TRUE for( hcinterp in c('bilin','bicub') ) { for( vinterp in c('lin','cub') ) { # go backwards xyY -> HVC printf( "----- hcinterp='%s', vinterp='%s' -----", hcinterp, vinterp ) time_start = gettime() res = xyYtoMunsell( xyY, warn=FALSE, perf=TRUE, hcinterp=hcinterp, vinterp=vinterp ) time_elapsed = gettime() - time_start # print( res ) # return(FALSE) HVC = res$HVC mask = is.na( HVC[ ,1] ) failures = sum( mask ) printf( "testOptimals(). There were %d inversion failures, out of %d samples. time=%g sec. %g/sample. OS=%s", failures, nrow(xyY), time_elapsed, time_elapsed/nrow(xyY), osname ) # in 2018, solaris started to give 5 iteration failures in CRAN testing # in 2019 (R v 3.6.1), the same thing happened with fedora (both clang and gcc). # So for safety in the future, just set limit to 5 ! limit = 5 # ifelse( solaris, 5, 0 ) # with solaris, 5 errors were detected in CRAN testing if( limit < failures ) { colnames(xyY) = c('x','y','Y') colnames(HVC) = c('H','V','C') df = data.frame( row.names=1:failures ) df$xyY = xyY[mask, , drop=FALSE] df$HVC = HVC[mask, , drop=FALSE] print( df ) #print( res ) out = FALSE } printf( "Performance:") cat( "Time Elapsed 5-number summary: " ) cat( fivenum(res$time.elapsed,na.rm=T), '\n' ) cat( "Iterations 5-number summary: " ) cat( fivenum(res$iterations,na.rm=T), '\n' ) cat( "Evaluations 5-number summary: " ) cat( fivenum(res$evaluations,na.rm=T), '\n' ) if( max( res$iterations, na.rm=T ) == 100 ) { idx = which( res$iterations == 100 ) # ; print(idx) printf( "There were %d samples that failed to converge.", length(idx) ) df = res[ idx, , drop=FALSE ] print(df) } if( out ) { # do round-trip xyY.back = MunsellToxyY( HVC, warn=FALSE, hcinterp=hcinterp, vinterp=vinterp )$xyY delta = rowSums( abs(xyY - xyY.back) ) # ignore pure black mask = xyY[ ,3]==0 & xyY.back[ ,3]==0 delta[mask] = 0 cat( "Round trip xyY -> HVC -> xyY inversion error 5-number summary: " ) cat( fivenum(delta), '\n' ) idx = which.max( delta ) df = data.frame( row.names=1 ) df$xyY = xyY[ idx, , drop=F ] df$HVC = HVC[ idx, , drop=F ] df$xyY.back = xyY.back[ idx, , drop=F ] printf( "Maximum inversion error occurs for this one:" ) print( df ) } } } return( out ) } testReals <- function() { df.real = subset( Munsell2xy, real==TRUE ) printf( "---------------- testReals() %d samples -----------------------", nrow(df.real) ) HVC = cbind( df.real$H, df.real$V, df.real$C ) xyY = cbind( df.real$x, df.real$y, YfromV(df.real$V) ) # go backwards xyY -> HVC time_start = gettime() res = xyYtoMunsell( xyY, warn=FALSE, perf=TRUE ) time_elapsed = gettime() - time_start mask = is.na( res$HVC[ ,1] ) failures = sum( mask ) printf( "testReals(). There were %d inversion failures, out of %d samples. time=%g sec. %g/sample.", failures, nrow(xyY), time_elapsed, time_elapsed/nrow(xyY) ) if( 0 < failures ) { colnames(HVC) = c('H','V','C') colnames(xyY) = c('x','y','Y') df = data.frame( row.names=MunsellNameFromHVC( HVC[mask, ] ) ) df$HVC = HVC[mask, ] df$xyY = xyY[mask, ] print( df ) } printf( "Performance:") cat( "Time Elapsed 5-number summary: " ) cat( fivenum(res$time.elapsed,na.rm=T), '\n' ) cat( "Iterations 5-number summary: " ) cat( fivenum(res$iterations,na.rm=T), '\n' ) cat( "Evaluations 5-number summary: " ) cat( fivenum(res$evaluations,na.rm=T), '\n' ) # compare HVC and res$HVC HVC.delta = abs(HVC - res$HVC) HVC.delta[ ,1] = pmin( HVC.delta[ ,1], 100 - HVC.delta[ ,1] ) delta = rowSums( HVC.delta ) cat( "Round trip HVC -> xyY -> HVC inversion error 5-number summary: " ) cat( fivenum(delta), '\n' ) idx = which.max( delta ) df = data.frame( row.names=1 ) df$HVC = HVC[ idx, , drop=F ] df$xyY = xyY[ idx, , drop=F ] df$HVC.back = res$HVC[ idx, , drop=F ] printf( "Maximum inversion error occurs for this one:" ) print( df ) return( failures == 0 ) } testNeutrals <- function() { # check that Chroma is small, and finite RGB = matrix( 0:255, 256, 3 ) colnames(RGB) = c('R','G','B') printf( "----------- testNeutrals() %d samples -----------------------", nrow(RGB) ) HVC = sRGBtoMunsell( RGB ) chroma = HVC[ ,3] mask = is.na(chroma) failures = sum(mask) if( 0 < failures ) { printf( "testNeutrals(). There were %d failures to convert.", failures ) df = data.frame( row.names=which(mask) ) df$RGB = RGB[mask, ] print( df ) } tol = 0 # 1.e-13 changed to 0 in v 3.0-0 mask = (is.finite(chroma) & tol < chroma) count = sum( mask ) if( 0 < count ) { printf( "testNeutrals(). There were %d chromas > %g.", count, tol ) df = data.frame( row.names=which(mask) ) df$RGB = RGB[mask, ] df$Chroma = chroma[mask] print( df ) } # now go back res = MunsellTosRGB( HVC ) RGB.delta = abs(RGB - res$RGB) delta = rowSums( RGB.delta ) cat( "Round-trip error (RGB -> HVC -> RGB) 5-number summary: " ) cat( fivenum(delta), '\n' ) return( failures==0 && count==0 ) } testNearNeutrals <- function() { # check that Chroma is small, and finite RGB = matrix( 0, 0, 3 ) colnames(RGB) = c('R','G','B') for( k in 10:255 ) { RGB = rbind( RGB, matrix(k,3,3) - diag(3) ) } printf( "------- testNearNeutrals() %d samples -----------------------", nrow(RGB) ) HVC = sRGBtoMunsell( RGB ) chroma = HVC[ ,3] mask = is.na(chroma) failures = sum(mask) if( 0 < failures ) { printf( "testNearNeutrals(). There were %d failures to convert.", failures ) df = data.frame( row.names=which(mask) ) df$RGB = RGB[mask, ] print( df ) return(FALSE) } tol = 0.30 mask = (is.finite(chroma) & tol < chroma) count = sum( mask ) if( 0 < count ) { printf( "testNearNeutrals(). There were %d chromas > %g.", count, tol ) df = data.frame( row.names=which(mask) ) df$RGB = RGB[mask, ] df$Chroma = chroma[mask] print( df ) return(FALSE) } # now go back res = MunsellTosRGB( HVC ) RGB.delta = abs(RGB - res$RGB) delta = rowSums( RGB.delta ) cat( "Round-trip error (RGB->HVC->RGB) 5-number summary: " ) cat( fivenum(delta), '\n' ) tol = 0.005 if( tol < max(delta) ) { printf( "Maximum round-trip error = %g > %g.", max(delta), tol ) return(FALSE) } return( TRUE ) } testDarks <- function() { rgbmax = 8 # 10 # 5 RGB = as.matrix( expand.grid( R=0:rgbmax, G=0:rgbmax, B=0:rgbmax ) ) printf( "--------------------- testDarks() %d samples -----------------------", nrow(RGB) ) HVC = sRGBtoMunsell( RGB ) chroma = HVC[ ,3] mask = is.na(chroma) failures = sum(mask) if( 0 < failures ) { printf( "testDarks(). There were %d failures to convert.", failures ) df = data.frame( row.names=which(mask) ) df$RGB = RGB[mask, , drop=FALSE ] print( df ) return(FALSE) } # now go back res = MunsellTosRGB( HVC ) RGB.delta = abs(RGB - res$RGB) delta = rowSums( RGB.delta ) cat( "Round-trip error (RGB->HVC->RGB) 5-number summary: " ) cat( fivenum(delta), '\n' ) return( failures==0 ) } { x = gettime() # load microbenchmark if( testCentroids() ) printf( "testCentroids() passed." ) else stop( "testCentroids() failed !", call.=FALSE ) if( testNeutrals() ) printf( "testNeutrals() passed." ) else stop( "testNeutrals() failed !", call.=FALSE ) if( testNearNeutrals() ) printf( "testNearNeutrals() passed." ) else stop( "testNearNeutrals() failed !", call.=FALSE ) if( testDarks() ) printf( "testDarks() passed." ) else stop( "testDarks() failed !", call.=FALSE ) if( testReals() ) printf( "testReals() passed." ) else stop( "testReals() failed !", call.=FALSE ) if( testOptimals() ) printf( "testOptimals() passed." ) else stop( "testOptimals() failed !", call.=FALSE ) printf( "Passed all Munsell Transforms tests !" ) }