% optimals.rnw % Time-stamp: "optimals.rnw" \documentclass[11pt]{article} % Set margins to be narrow \RequirePackage[left=1in,top=0.75in,right=1in,bottom=0.75in]{geometry} %\VignetteIndexEntry{Plotting Chromaticity Loci of Optimal and Schrodinger Colors - the MacAdam Limits} %\VignetteEngine{knitr::knitr} \RequirePackage{color} \RequirePackage{fancyvrb} \RequirePackage[T1]{fontenc} \RequirePackage{ae} % ComputerModern Fonts \RequirePackage{fancyhdr} \RequirePackage{float} \RequirePackage{hyperref} \usepackage{lastpage} % block of definecolor's moved down here on Dec 17 2021. Kurt Hornik \definecolor{darkblue}{rgb}{0,0,0.5} \definecolor{blue}{rgb}{0,0,0.8} \definecolor{lightblue}{rgb}{0.2,0.2,0.9} \definecolor{darkred}{rgb}{0.6,0.0,0.0} \definecolor{red}{rgb}{0.7,0,0} \definecolor{darkgreen}{rgb}{0.0,0.4,0.0} \definecolor{lightgray}{rgb}{0.7,0.7,0.7} \definecolor{darkorange}{rgb}{0.75, 0.45, 0} \definecolor{purple}{rgb}{0.65, 0, 0.75} \definecolor{goldenrod}{rgb}{0.80, 0.61, 0.11} \definecolor{lightyellow}{rgb}{0.98,0.94,0.83} \pagestyle{fancy} \cfoot{page \thepage\ of \pageref{LastPage}} \renewcommand{\headrulewidth}{0pt} % \code mini environment ttfamily->texttt \newcommand\code{\bgroup\@codex} \def\@codex#1{{\color{darkred} \normalfont\ttfamily\hyphenchar\font=-1 #1}\egroup} % This environment defines the look of R ouput \DefineVerbatimEnvironment{Soutput}{Verbatim}{ fontsize=\small, formatcom=\color{darkblue} } \begin{document} % \SweaveOpts{concordance=TRUE} \title{ {\Huge Plotting Chromaticity Loci of Optimal and Schr{\"o}dinger Colors} \\ {\Large Reproducing plots of MacAdam Limits}} \author{Glenn Davis \url{ }} \maketitle % \thispagestyle{fancy} % Setup stuff. <>= require("knitr",quietly=TRUE) opts_chunk$set(fig.path="figs/ag2-", fig.align="center", fig.width=7, fig.height=7, comment="") knit_hooks$set(output = function(x, options) { paste('\\begin{Soutput}\n', x, '\\end{Soutput}\n', sep = '') }) options(width=90) if(!file.exists("figs")) dir.create("figs") @ % ---------------------------------------------------------------------------- \section*{Introduction} The goal of this vignette is to reproduce 2 figures in \cite{W&S}, and to make similar ones. These figures plot sections of the surface of 2-transition colors. These colors are also called the \emph{Schr{\"o}dinger colors}, following \cite{West}. The sections are often called the \emph{MacAdam limits}, after \cite{macadam1935}. To these plots we add sections of the the \emph{optimal colors}, which are the colors on the boundary of the object color solid, which is also called the \emph{R{\"o}sch Farbk{\"o}rper}. The plots show that the difference is not significant; which confirms the statement in \cite{Burns2021} that the difference: "... has no impact on practical colorimetric computations." In all plots, the Schr{\"o}dinger colors are plotted in black, and the optimal colors in red. The featured functions from \textbf{colorSpec} used in this vignette are \code{sectionSchrodingerColors()} and \code{sectionOptimalColors()}. But they requires some help from the function \code{plotSections()} and others in the file \code{optimal-help.R}. <>= library( colorSpec ) source( "optimal-help.R" ) # make vector of levels to be used for the sections in all the plots Ylevel=c( seq( 0.10, 0.90, by=0.1 ), 0.95 ) @ \vspace*{1\baselineskip} % ---------------------------------------------------------------------------- \section*{Illuminant A} First, build the "material responder" from Illuminant A and standard CMFs: \setcounter{figure}{0} <>= wave = seq(380,800,by=2) A.eye = product( A.1nm, "material", xyz1931.1nm, wavelength=wave ) white = product( neutralMaterial(1,wave=wave), A.eye ) @ Make the plot. <>= par( omi=rep(0,4), mai=c(0.5,0.6,0,0) ) seclist = sectionOptimalColors( A.eye, normal=c(0,1,0), beta=white[2]*Ylevel ) plotSections( seclist, Ylevel, xyz1931.1nm, white, col='red' ) seclist = sectionSchrodingerColors( A.eye, normal=c(0,1,0), beta=white[2]*Ylevel ) plotSections( seclist, Ylevel, xyz1931.1nm, white, add=TRUE ) @ Compare this with Figure 2 in \cite{macadam1935}, and Figure 3(3.7) in \cite{W&S}. The optimal colors are plotted first in red, and then the Schr{\"o}dinger colors in black. As you can see, there is no significant difference between them. This is because the outer chromaticity diagram is almost convex. \vspace*{1\baselineskip} % ---------------------------------------------------------------------------- \section*{Illuminant D65} First, build the "material responder" from Illuminant D65 and standard CMFs: <>= wave = seq(380,800,by=2) D65.eye = product( D65.1nm, "material", xyz1931.1nm, wavelength=wave ) white = product( neutralMaterial(1,wave=wave), D65.eye ) @ Make the plot: <>= par( omi=rep(0,4), mai=c(0.5,0.6,0,0) ) seclist = sectionOptimalColors( D65.eye, normal=c(0,1,0), beta=white[2]*Ylevel ) plotSections( seclist, Ylevel, xyz1931.1nm, white, col='red' ) seclist = sectionSchrodingerColors( D65.eye, normal=c(0,1,0), beta=white[2]*Ylevel ) plotSections( seclist, Ylevel, xyz1931.1nm, white, add=TRUE ) @ Compare this with Figure 4(3.7) in \cite{W&S}. \vspace*{1\baselineskip} % ---------------------------------------------------------------------------- \section*{Illuminant D65, with the Cone Fundamentals of Stockman and Sharpe} First, build the "material responder" from Illuminant D65, but this time with the updated cone fundamentals from \cite{STOCKMAN2000}. <>= wave = seq(380,800,by=5) D65.eye = product( D65.1nm, "material", lms2000.1nm, wavelength=wave ) white = product( neutralMaterial(1,wave=wave), D65.eye ) @ Make the plot: <>= par( omi=rep(0,4), mai=c(0.5,.6,0,0) ) normal = c(1,1,1)/3 ; beta = sum(white*normal) * Ylevel seclist = sectionOptimalColors( D65.eye, normal=normal, beta=beta ) plotSections( seclist, Ylevel, lms2000.1nm , white, col='red' ) seclist = sectionSchrodingerColors( D65.eye, normal=normal, beta=beta ) plotSections( seclist, Ylevel, lms2000.1nm , white, add=TRUE ) @ Compare with Figure 7 in \cite{Logvinenko2009}. Although the chromaticity polygon reverses itself on the right side, and is not convex. None of the levels are dark enough to reveal any significant difference between the red and black sections. \vspace*{1\baselineskip} % ---------------------------------------------------------------------------- \section*{Illuminant C} First, build the "material responder" from Illuminant C and standard CMFs: <>= wave = seq(380,780,by=2) C.eye = product( C.5nm, "material", xyz1931.1nm, wavelength=wave ) white = product( neutralMaterial(1,wave=wave), C.eye ) @ Make the plot. <>= par( omi=rep(0,4), mai=c(0.5,0.6,0,0) ) seclist = sectionOptimalColors( C.eye, normal=c(0,1,0), beta=white[2]*Ylevel ) plotSections( seclist, Ylevel, xyz1931.1nm, white, col='red' ) seclist = sectionSchrodingerColors( C.eye, normal=c(0,1,0), beta=white[2]*Ylevel ) plotSections( seclist, Ylevel, xyz1931.1nm, white, add=TRUE ) @ Compare this with Figure 3 in \cite{macadam1935}. \vspace*{1\baselineskip} % ---------------------------------------------------------------------------- \section*{The Eye of a Bee} One of the first known examples of an eye with a non-convex chromaticity polygon is that of the eye of a bee. We use the plots from \cite{West}. <>= wave = seq(320,600,by=2) path = system.file( 'extdata/eyes/BeeEye.txt', package='colorSpec' ) bee = readSpectra( path, wavelength=wave ) plot( bee ) @ Compare with Figure 3(a) in \cite{West}. <>= E.eye = product( illuminantE(1,wavelength=wave), "material", bee ) white = product( neutralMaterial(1,wave=wave), E.eye ) par( omi=rep(0,4), mai=c(0.5,0.6,0,0) ) normal = c(1,1,1)/3 ; beta = sum(white*normal) * Ylevel seclist = sectionOptimalColors( E.eye, normal=normal, beta=beta ) plotSections( seclist, Ylevel, bee, white, col='red' ) seclist = sectionSchrodingerColors( E.eye, normal=normal, beta=beta ) plotSections( seclist, Ylevel, bee, white, add=TRUE ) @ Compare with Figure 3(b) in \cite{West}. For the first time in this vignette, we see a significant difference between the Schr{\"o}dinger and optimal colors. Note that the optimal colors (red) are always \emph{outside} the Schr{\"o}dinger colors (black). \vspace*{1\baselineskip} % ---------------------------------------------------------------------------- \section*{An RGB Scanner} This also works with object color from an electrical RGB scanner. The chromaticities in this case are: $$ r = R/(R + G + B) ~~~~~~ g = G/(R + G + B)$$ Make a scanner from a tungsten source and a Flea2 camera: <>= wave = seq(420,680,by=5) Flea2.scanner = product( A.1nm, "material", Flea2.RGB, wavelength=wave ) white = product( neutralMaterial(1,wave=wave), Flea2.scanner ) @ Make the plot. <>= par( omi=rep(0,4), mai=c(0.5,0.6,0,0) ) normal = c(1,1,1)/3 ; beta = sum(white*normal) * Ylevel seclist = sectionOptimalColors( Flea2.scanner, normal=normal, beta=beta ) plotSections( seclist, Ylevel, Flea2.scanner, white, col='red' ) seclist = sectionSchrodingerColors( Flea2.scanner, normal=normal, beta=beta ) plotSections( seclist, Ylevel, Flea2.scanner, white, add=TRUE ) @ The wavelengths have been trimmed at each end to avoid weak responsivities that wander around too much. Even after trimming, the spectrum locus is not convex in the inverval from about 500 to 560 nm. This shows that the color solid does \emph{not} satisfy the 2-transition property. The optimal color chromaticity locii are convex, as they must be; and some optimal colors are outside the spectrum locus. See Figure 3 and more discussion in \cite{West}. % \Sexpr{knitr::knit_exit()} \vspace*{2\baselineskip} % ---------------------------------------------------------------------------- \bibliographystyle{plain} \bibliography{optimals} % ---------------------------------------------------------------------------- \section*{Appendix} This document was prepared \today \quad with the following configuration: <>= knit_hooks$set(output = function(x, options) { x }) toLatex(sessionInfo(), locale=FALSE) @ \end{document}