% PTBAndIsetbioColorimetryTest
% 
% This script compares values calculated by PTB and routines in isetbio (developed by
% Wandell and colleague) for various colorimetric functions.
%
% For information on isetbio, see https://github.com/isetbio/isetbio/wiki.
% It contains its own implementation of many of the colorimetric computations 
% implemented in PTB.  Note that Brainard and Wandell are not completely
% independent sources.
%
% Make sure isetbio is on your path to run these comparisons.  The tests should
% also work if you have the proprietary iset on your path instead.
% isetbio is available on gitHub as https://github.com/isetbio/isetbio.git.
%
% The checks are grouped into cells that check one thing at a time.
%
% Because DHB and BAW have too much time on their hands -
%  "A man with one watch always knows what time it is, a man with two is never sure."
%  "A man with SPM knows how to blur and average his watches so that he can't tell the time"
%
% 7/7/10  dhb  Wrote it.
% x/xx/10 baw  Checked with ISET-4.0 revision 351 (BW)
% 2/28/13 dhb  Updated to work with vset rather than proprietary iset.
% 8/5/13  dhb  Updated to work with isetbio, vset's successor.
% 7/31/14 dhb, ncp Put a cd around call to isetbio xyz2lab to deal with
%              fact that 2014b has added a routine with this name that
%              doesn't work quite the same.
% 2/09/21 mk   Fix for renamed xyz2lab -> ieXYZ2LAB() and ieLAB2XYZ().

if IsOctave
    % Suppress false Octave path warnings:
    warning('off', 'Octave:data-file-in-path', 'local');
end

%% Clear and close
clear; close all;
if (exist('isetbioRootPath','file'))
    fprintf('\nComparing PTB and isetbio Colorimetry\n');
else
    error('Need isetbio on your path to run this test');
end

%% Play the namespace game as necessary.
% This sets up what we need.
isetbioPath = fileparts(which('colorTransformMatrix'));
curDir = pwd;

%% XYZ-related colorimetry
%
% xy
fprintf('\n***** Basic XYZ *****\n');
testXYZs = [[1 2 1]' [2 1 0.5]' [1 1 1]' [0.6 2.3 4]'];
ptbxyYs = XYZToxyY(testXYZs);
ptbxys = ptbxyYs(1:2,:);
isetxys = chromaticity(testXYZs')';
if (any(abs(ptbxys-isetxys) > 1e-10))
    fprintf('PTB-ISET DIFFERENCE for XYZ to xy\n');
else
    fprintf('PTB-ISET AGREE for XYZ to xy\n');
end

%% xyY
ptbXYZs = xyYToXYZ(ptbxyYs);
if (any(abs(testXYZs-ptbXYZs) > 1e-10))
    fprintf('PTB FAILS XYZ to xyY to XYZ\n');
else
    fprintf('PTB PASSES XYZ to xyY to XYZ\n');
end
isetXYZs = xyy2xyz(ptbxyYs')';
if (any(abs(testXYZs-isetXYZs) > 1e-10))
    fprintf('PTB-ISET DIFFERENCE for xyY to XYZ\n');
else
    fprintf('PTB-ISET AGREE for xyY to XYZ\n');
end 

%% CIE uv chromaticity
ptbuvYs = XYZTouvY(testXYZs);
ptbuvs = ptbuvYs(1:2,:);
[isetus,isetvs] = xyz2uv(testXYZs');
isetuvs = [isetus' ; isetvs'];
if (any(abs(ptbuvs-isetuvs) > 1e-10))
    fprintf('PTB-ISET DIFFERENCE for XYZ to uv\n');
    fprintf('\tI think this is because ISET implements an obsolete version of the standard\n');
else
    fprintf('PTB-ISET AGREE for XYZ to uv\n');
end

%% CIELUV
whiteXYZ = [3,4,3]';
ptbLuvs = XYZToLuv(testXYZs,whiteXYZ);
isetLuvs = xyz2luv(testXYZs',whiteXYZ')';
if (any(abs(ptbLuvs-isetLuvs) > 1e-10))
    fprintf('PTB-ISET DIFFERENCE for XYZ to Luv\n');
    fprintf('\tPresumably because the uv transformation differs.\n');
else
    fprintf('PTB-ISET AGREE for XYZ to Luv\n');
end

%% CIELAB
whiteXYZ = [3,4,3]';
ptbLabs = XYZToLab(testXYZs,whiteXYZ);
cd(isetbioPath);
isetLabs = ieXYZ2LAB(testXYZs',whiteXYZ')';
cd(curDir);
if (any(abs(ptbLabs-isetLabs) > 1e-10))
    fprintf('PTB-ISET DIFFERENCE for XYZ to Lab\n');
else
    fprintf('PTB-ISET AGREE for XYZ to Lab\n');
end
ptbXYZCheck = LabToXYZ(ptbLabs,whiteXYZ);
isetXYZCheck = ieLAB2XYZ(isetLabs',whiteXYZ')';
if (any(abs(testXYZs-ptbXYZCheck) > 1e-10))
    fprintf('PTB FAILS XYZ to Lab to XYZ\n');
else
    fprintf('PTB PASSES XYZ to Lab to XYZ\n');
end
if (any(abs(testXYZs-isetXYZCheck) > 1e-10))
    fprintf('ISET FAILS XYZ to Lab to XYZ\n');
else
    fprintf('ISET PASSES XYZ to Lab to XYZ\n');
end

%% sRGB
%
% The iset routines seem to use a matrix that is 1/100 of the standard
% definition.  Or, at least, 1/100 of what the PTB routines use.  To
% account for this, I multiply XYZ values by 100 before passing them
% to the iset routines.
%
% The iset routines take an exponent argument for the sRGB gamma transform.
% At http://www.w3.org/Graphics/Color/sRGB this is specified as 2.4.  The
% iset routine xyz2srgb uses 2.2, with a comment that an update at
% www.srgb.com changed this from 2.4 to 2.2.  I think this is wrong,
% though.  The text I can find on the web says that the 2.4 exponent, plus
% the linear initial region, is designed to approximate a gamma of 2.2.
% That is, you want 2.4 in the formulae to approximate the 2.2 industry
% standard gamma.  Site www.srgb.com now appears gone, by the way, but all
% the other sites I find seem to be the same in this regard.
% 
% Also note that if you change the exponent in the iset sRGB formulae, you
% also should probably change the cutoff used at the low-end, where the
% sRGB standard special cases the functional form of the gamma curve.  Here
% the test is set for 2.4.
%
% Finally,the default gamma used by iset lrgb2srgb and by xzy2srgb
% currently differ, so you really want to be careful using these.  The
% inverse routine srgb2lrgb doesn't allow passing of the exponent, and it
% is hard coded as 2.4.  This causes a failure of the iset sRGB gamma
% routines to self-invert for gamma other than 2.4, and with their
% defaults.
%
% One other convention difference is that the PTB routine rounds to
% integers for the settings, while the iset routine leaves the rounding up
% to the caller.
fprintf('\n***** sRGB *****\n');

% Create some test sRGB values and convert them in the PTB framework
ptbSRGBs = [[188 188 188]' [124 218 89]' [255 149 203]' [255 3 203]'];
ptbSRGBPrimary = SRGBGammaUncorrect(ptbSRGBs);
ptbXYZs = SRGBPrimaryToXYZ(ptbSRGBPrimary);

% The ISET form takes the frame buffer values in the [0,1] regime
isetSRGBs = ptbSRGBs/255;
isetSRGBs = XW2RGBFormat(isetSRGBs',4,1);
isetXYZ   = srgb2xyz(isetSRGBs);
isetXYZs  = RGB2XWFormat(isetXYZ)';

if (any(abs(isetXYZs-ptbXYZs) > 1e-10))
    d = isetXYZs - ptbXYZs;
    fprintf('PTB-ISET DIFFERENCE for XYZ to sRGB: %f\n',max(abs(d(:))));
    d = d ./ptbXYZs;
    fprintf('PTB-ISET Percent XYZ DIFFERENCE: %f\n',max(abs(d(:))));
else
    fprintf('PTB-ISET AGREE for XYZ to sRGB\n');
end

% PTB testing of inversion
if (any(abs(XYZToSRGBPrimary(ptbXYZs)-ptbSRGBPrimary) > 1e-10))
    fprintf('PTB FAILS linear sRGB to XYZ to linear sRGB\n');
else
    fprintf('PTB PASSES linear sRGB to XYZ to linear sRGB\n');
end
if (any(abs(SRGBGammaCorrect(ptbSRGBPrimary)-ptbSRGBs) > 1e-10))
    fprintf('PTB FAILS sRGB to linear sRGB to sRGB\n');
else
    fprintf('PTB PASSES sRGB to linear sRGB to sRGB\n');
end

% Compare sRGB matrices
[nil,ptbSRGBMatrix] = XYZToSRGBPrimary([]);
isetSRGBMatrix = colorTransformMatrix('xyz2srgb')';

if (any(abs(ptbSRGBMatrix-isetSRGBMatrix) > 1e-10))
    fprintf('PTB-ISET DIFFERENCE for sRGB transform matrix\n');
else
    fprintf('PTB-ISET AGREE for sRGB transform matrix\n');
end

% XYZ -> lRGB 
% Reformat shape
ptbXYZsImage = CalFormatToImage(ptbXYZs,1,size(ptbXYZs,2));

% ISET convert 
[isetSRGBImage,isetSRGBPrimaryImage] = xyz2srgb(ptbXYZsImage);

% Reformat shape
isetSRGBs = ImageToCalFormat(isetSRGBImage); 
isetSRGBPrimary = ImageToCalFormat(isetSRGBPrimaryImage);

if (any(abs(ptbSRGBPrimary-isetSRGBPrimary) > 1e-10))
    d = ptbSRGBPrimary - isetSRGBPrimary;
    fprintf('PTB-ISET DIFFERENCE for XYZ to sRGB: %f\n',max(abs(d(:))));
    d = d ./isetSRGBPrimary;
    fprintf('PTB-ISET Percent RGB DIFFERENCE: %f\n',max(abs(d(:))));
else
    fprintf('PTB-ISET AGREE for XYZ to linear sRGB\n');
end

% ISET/PTB sRGB comparison in integer gamma corrected space
if (any(abs(round(isetSRGBs*255)-ptbSRGBs) > 1e-10))
    fprintf('PTB-ISET DIFFERENCE for XYZ to sRGB\n');
else
    fprintf('PTB-ISET AGREE for XYZ to sRGB\n');
end

% lrgb -> srgb -> lrgb in ISET
isetSRGBPrimaryCheckImage = srgb2lrgb(isetSRGBImage);
isetSRGBPrimaryCheck = ImageToCalFormat(isetSRGBPrimaryCheckImage);
if (any(abs(isetSRGBPrimaryCheck-isetSRGBPrimary) > 1e-10))
    fprintf('ISET FAILS linear sRGB to sRGB to linear sRGB\n');
else
    fprintf('ISET PASSES linear sRGB to sRGB to linear sRGB\n');
end

%% Quanta/Energy
%
% The ISET routines define c and h to more places than the PTB, so the
% agreement is only good to about 5 significant places.  Seems OK to me.
fprintf('\n***** Energy/Quanta *****\n');
load spd_D65
spdEnergyTest = spd_D65;
wlsTest = SToWls(S_D65);
testPlaces = 5;
ptbQuanta = EnergyToQuanta(wlsTest,spdEnergyTest);
isetQuanta = Energy2Quanta(wlsTest,spdEnergyTest);
if (any(abs(ptbQuanta-isetQuanta) > (10^-testPlaces)*min(ptbQuanta)))
    fprintf('PTB-ISET DIFFERENCE for energy to quanta conversion at %d significant places\n',testPlaces);
else
    fprintf('PTB-ISET AGREE for energy to quanta conversion to %d significant places\n',testPlaces);
end
if (any(abs(QuantaToEnergy(wlsTest,ptbQuanta)-spdEnergyTest) > 1e-10))
    fprintf('PTB FAILS energy to quanta to energy\n');
else
    fprintf('PTB PASSES energy to quanta to energy\n');
end
if (any(abs(Quanta2Energy(wlsTest,isetQuanta')'- spdEnergyTest) > 1e-10))
    fprintf('ISET FAILS energy to quanta to energy\n');
else
    fprintf('ISET PASSES energy to quanta to energy\n');
end

%% CIE daylights
% 
% These routines are now running in ISET and everything agrees.
fprintf('\n***** CIE Daylights *****\n');
load B_cieday
testWls = SToWls(S_cieday);
testTemp = 4987;
ptbDaySpd = GenerateCIEDay(testTemp,B_cieday);
ptbDaySpd = ptbDaySpd/max(ptbDaySpd(:));

% Iset version of normalized daylight
isetDaySpd = daylight(testWls,testTemp);
isetDaySpd = isetDaySpd/max(isetDaySpd(:));
if (any(abs(isetDaySpd-ptbDaySpd) > 1e-10))
    fprintf('PTB-ISET DIFFERENCE for daylight');
else
    fprintf('PTB-ISET AGREE for daylight\n');
end

%% Not yet really implemented
% The code below calls some isetbio routines that
% either don't yet have PTB counterparts, or for which
% we have not yet written the comparison code.

% Calculate correlated color temperature
isetColorTemp = cct([.31,.32]');

% Calculate spd of a daylight with some color temperature
wave = 400:700;
Tc = 6500; spd = cct2sun(wave, Tc, 'energy'); % plot(wave,spd);

% deltaE94.m
% deltaE2000.m
% deltaEab.m
% deltaEuv.m

% ieResponsivityConvert.m
% ieScotopicLuminanceFromEnergy.m
bb = blackbody(wave,6500,'watts');
chromaticity(ieXYZFromEnergy(bb',wave));
ieLuminanceFromEnergy(bb',wave);

bb = blackbody(wave,6500,'quanta');
chromaticity(ieXYZFromPhotons(bb',wave));
ieLuminanceFromPhotons(bb',wave);

