1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
|
function [T_quantalAbsorptionsNormalized,T_quantalAbsorptions,T_quantalIsomerizations,adjIndDiffParams,params,staticParams] = ...
ComputeCIEConeFundamentals(S,fieldSizeDegrees,ageInYears,pupilDiameterMM,lambdaMax,whichNomogram,LserWeight, ...
DORODS,rodAxialDensity,fractionPigmentBleached,indDiffParams)
% [T_quantalAbsorptionsNormalized,T_quantalAbsorptions,T_quantalIsomerizations,adjIndDiffParams,params,staticParams] = ...
% ComputeCIEConeFundamentals(S,fieldSizeDegrees,ageInYears,pupilDiameterMM,[lambdaMax],[whichNomogram],[LserWeight], ...
% [DORODS],[rodAxialDensity],[fractionPigmentBleached],[indDiffParams])
%
% Function to compute normalized cone quantal sensitivities
% from underlying pieces, as specified in CIE 170-1:2006.
%
% IMPORTANT: This routine returns quantal sensitivities. You
% may want energy sensitivities. In that case, use EnergyToQuanta to convert
% T_energy = EnergyToQuanta(S,T_quantal')'
% and then renormalize. (You call EnergyToQuanta because you're converting
% sensitivities, which go the opposite direction from spectra.)
% The routine also returns two quantal sensitivity functions. The first gives
% the probability that a photon will be absorbed. The second is the probability
% that the photon will cause a photopigment isomerization. It is the latter
% that is what you want to compute isomerization rates from retinal illuminance.
% See note at the end of function FillInPhotoreceptors for some information about
% convention. In particular, this routine takes pre-retinal absorption into
% account in its computation of probability of absorptions and isomerizations,
% so that the relevant retinal illuminant is one computed without accounting for
% those factors. This routine does not account for light attenuation due to
% the pupil, however. The only use of pupil size here is becuase of its
% slight effect on lens density as accounted for in the CIE standard.
%
% This standard allows customizing the fundamentals for
% field size, observer age, and pupil size in mm.
%
% To get the Stockman-Sharpe/CIE 2-deg fundamentals, use
% fieldSizeDegrees = 2;
% ageInYears = 32;
% pupilDiameterMM = 3;
% and don't pass the rest of the arguments.
%
% To get the Stockman-Sharpe/CIE 10-deg fundamentals, use
% fieldSizeDegrees = 10;
% ageInYears = 32;
% pupilDiameterMM = 3;
% and don't pass the rest of the arguments.
%
% Although this routine will compute something over any wavelength
% range, I'd (DHB) recommend not going lower than 390 or above about 780 without
% thinking hard about how various pieces were extrapolated out of the range
% that they are specified in the standard. Indeed, the lens optical
% density measurements only go down to 400 nm and these are extropolated
% to go below 400.
%
% This routine will compute from tabulated absorbance or absorbance based on a nomogram, where
% whichNomogram can be any source understood by the routine PhotopigmentNomogram. To obtain
% the nomogram behavior, pass a lambdaMax vector. You can then also optionally pass a nomogram
% source (default: StockmanSharpe). This option (using shifted nomograms) is not part of the
% CIE standard. See NOTE below for another way to handle individual differences
%
% The nominal values of lambdaMax to fit the CIE 2-degree fundamentals with the
% Stockman-Sharpe nomogram are 558.9, 530.3, and 420.7 nm for the LMS cones respectively.
% These in fact do a reasonable job of reconstructing the CIE 2-degree fundamentals, although
% there are small deviations from what you get if you simply read in the tabulated cone
% absorbances. Thus starting with these as nominal values and shifting is one way to
% produce fundamentals tailored to observers with different known photopigments.
%
% If you pass lambaMax and its length is 4, then first two values are treated as
% the peak wavelengths of the ser/ala variants of the L cone pigment, and these
% are then weighted according to LserWeight and (1-LserWeight). The default
% for LserWeight is 0.56. After travelling it for a distance to try to get better
% agreement between the nomogram based fundamentals and the tabulated fundamentals
% I (DHB) gave up and decided that using a single lambdaMax is as good as anything
% else I could come up with. If you are interested, see FitConeFundamentalsTest.
%
% NOTE 1: When we first implemented the CIE standard, adding this shifting feature
% seemed like a good idea to allow exploration of individual differences in photopigments.
% But, with 0 shift, none of the nomograms exactly reproduce the tabulated photopigment absorbance
% spectral sensitivities, and this is not so good. We are phasing out our
% use of this feature in favor of simply shifting the tabulated
% photopigment absorbances, and indeed in favor of adopting the method
% published by Asano, Fairchild, & Blonde (2016), PLOS One, doi: 10.1371/journal.pone.0145671
% to tailor the CIE fundamentals to individual observers. This is done by
% passing the argument indDiffParams, which is a structure as follows.
% indDiffParams.dlens - deviation in % from CIE computed peak lens density
% indDiffParams.dmac - deviation in % from CIE peak macular pigment density
% indDiffParams.dphotopigment - vector of deviations in % from CIE photopigment peak density.
% indDiffParams.lambdaMaxShift - vector of values (in nm) to shift lambda max of each photopigment absorbance by.
% indDiffParams.shiftType - 'linear' (default) or 'log'. 'linear' gets the Asano et al. behavior
%
% You also can shift the absorbances along a wavenumber axis after you have
% obtained them. To do this, pass argument lambdaMaxShift with the same
% number of entries as the number of absorbances that are used.
%
% The adjIndDiffParams output is a struct which is populated by ComputeRawConeFundamentals.
% It contains the actual parameter values for the parameters adjusted using the indDiffParams
% input. It contains the following fields:
% adjIndDiffParams.mac - the adjusted macular pigment transmittance as a function of wavelength
% as calculated in line 151 of ComputeRawConeFundamentals.
% adjIndDiffParams.lens - the adjusted lens transmittance as a function of wavelength as calculated
% in line 41 of ComputeRawConeFundamentals.
% adjIndDiffParams.dphotopigment - 3-vector of the adjusted photopigment axial density for
% L, M and S cones (in that order), as calculated in lines
% 200-202 of ComputeRawConeFundamentals; or rods, as calculated
% in line 216 of ComputeRawConeFundamentals if params.DORODS is true.
% adjIndDiffParams.absorbance - Photopigment absorbance as given in line 188 of ComputeRawConeFundamentals
% adjIndDiffParams.absorptance - Photopigment absorptance as given in line 230 of ComputeRawConeFundamentals
%
% For both adjIndDiffParams.mac and adjIndDiffParams.lens, the wavelength
% spacing is the same as in the S input variable of this function.
%
% The params and staticParams outputs are the argument strucutures that
% were passed to ComputeRawConeFundamentals by this routine to do the work.
% These can be useful if you'd like, say, to susequently use
% ComputeRawConeFundamentals to produce estimates for (e.g.) melanopsin or
% the rods, where you keep everything else as consistent as possible to
% what this routine does. Note that this is all a bit klugy for historical
% reasons, as there is redundancy between what you can/might do with
% adjIndDiffParams and with these two return outputs. In particular, these
% two return outputs would let you call ComputeRawConeFundamentals and get
% adjIndDiffParams directly from there.
%
% This function also has an option to compute rod spectral sensitivities, using
% the pre-retinal values that come from the CIE standard. Set DORODS to true on
% call. You then need to explicitly pass a single lambdaMax value. You can
% also pass an optional rodAxialDensity value. If you don't pass that, the
% routine uses the 'Alpern' estimate for 'Human'/'Rod' embodied in routine
% PhotopigmentAxialDensity. The default nomogram for the rod spectral
% absorbance is 'StockmanSharpe', but you can override with any of the
% others available in routine PhotopigmentNomogram. Use of this requires
% good choices for lambdaMax, rodAxialDensity, and the nomogram. We are
% working on identifying those values more precisely.
%
% Finally, you can adjust the returned spectral sensitivities to account for
% the possibility that some of the pigment in the cones is bleached. Pass
% a column vector with same length as number of spectral sensitivities beingt
% computed. You need to estimate the fraction elsewhere.
%
% Relevant to individual differences, S & S (2000) estimate the wavelength difference
% between the ser/ala variants to be be 2.7 nm (ser longer).
%
% NOTE 2. The CIE standard is specified for field sizes between 1 and 10
% degrees. Our code will extrapolate using the given formulae to larger
% field sizes without complaining. We think this is reasonable; see
% CIEConeFundamentalsFieldSizeTest and its header comments, but be aware
% that you have sailed into little charted territory if you do this.
%
% See also: ComputeRawConeFundamentals, CIEConeFundamentalsTest, CIEConeFundamentalsFieldSizeTest,
% FitConeFundamentalsTest, FitConeFundamentalsWithNomogram, StockmanSharpeNomogram,
% ComputePhotopigmentBleaching.
%
% 8/13/11 dhb Wrote it.
% 8/14/11 dhb Clean up a little.
% 12/16/12 dhb, ms Add rod option.
% 08/10/13 dhb Test for consistency between what's returned by FillInPhotoreceptors and
% what's returned by ComputeRawConeFundamentals.
% 05/24/14 dhb Add fractionPigmentBleached optional arg.
% 05/26/14 dhb Comment improvements.
% 02/08/16 dhb, ms Add lambdaMaxShift argument.
% ms Don't do two way check when lambdaMax is shifted.
% 02/24/16 dhb, ms Started to implement Asano et al. individual difference model
% 3/30/17 ms Added output argument returning adjusted ind differences
% 8/1/17 dhb, ms Added return of params and staticParams.
%% Are we doing rods rather than cones?
if (nargin < 8 || isempty(DORODS))
DORODS = 0;
end
%% Check whether we'll adjust axial density for bleaching
if (nargin < 10 || isempty(fractionPigmentBleached))
DOBLEACHING = 0;
else
DOBLEACHING = 1;
end
%% Check for passed lambdaMaxShift
if (nargin < 11 || isempty(indDiffParams))
params.indDiffParams = [];
else
params.indDiffParams = indDiffParams;
end
%% Get some basic parameters.
%
%
% We start with default CIE parameters in
% the photoreceptors structure, and then override
% as necessary.
% then override to match the CIE standard.
if (fieldSizeDegrees <= 4)
whatCalc = 'CIE2Deg';
else
whatCalc = 'CIE10Deg';
end
photoreceptors = DefaultPhotoreceptors(whatCalc);
%% Override default values so that FillInPhotoreceptors does
% our work for us. The CIE standard uses field size,
% age, and pupil diameter to computer other values.
% to compute other quantities.
photoreceptors.nomogram.S = S;
photoreceptors.fieldSizeDegrees = fieldSizeDegrees;
photoreceptors.pupilDiameter.value = pupilDiameterMM;
photoreceptors.ageInYears = ageInYears;
% Absorbance. Use tabulated CIE values (which are in the
% default CIE photoreceptors structure) unless a nomogram and
% lambdaMax values are passed.
SET_ABSORBANCE = false;
if (nargin > 4 && ~isempty(lambdaMax))
if (nargin < 6 || isempty(whichNomogram))
whichNomogram = 'StockmanSharpe';
end
photoreceptors = rmfield(photoreceptors,'absorbance');
photoreceptors.nomogram.source = whichNomogram;
photoreceptors.nomogram.lambdaMax = lambdaMax;
params.lambdaMax = lambdaMax;
staticParams.whichNomogram = whichNomogram;
else
% Absorbance is going to be specified directly. We get
% it after the call to FillInPhotoreceptors below,
% which will convert a file containing the absorbance into
% the needed data at the needed wavelength spacing.
SET_ABSORBANCE = true;
end
%% Are we doing the rods? In that case, a little more
% mucking is necessary.
if (DORODS)
if (isempty(lambdaMax) || length(lambdaMax) ~= 1)
error('When computing for rods, must specify exactly one lambda max');
end
photoreceptors.types = {'Rod'};
photoreceptors.nomogram.lambdaMax = lambdaMax;
photoreceptors.OSlength.source = 'None';
photoreceptors.specificDensity.source = 'None';
photoreceptors.axialDensity.source = 'Alpern';
params.DORODS = true;
end
%% Pigment bleaching
%
% Hope for the best with respect to dimensionality of what is passed.
% FillInPhotoreceptors will throw an error if the dimension isn't
% matched to that of the axialDensity value field.
if (DOBLEACHING)
photoreceptors.fractionPigmentBleached.value = fractionPigmentBleached;
end
%% Do the work. Note that to modify this code, you'll want a good
% understanding of the order of precedence enforced by FillInPhotoreceptors.
% This is non-trivial, although the concept is that if a quantity that
% can be computed is specified directly in the passed structure is
% actually specified, the speciefied value overrides what could be computed.
photoreceptors = FillInPhotoreceptors(photoreceptors);
if (SET_ABSORBANCE)
params.absorbance = photoreceptors.absorbance;
end
%% Set up for call into the low level routine that computes the CIE fundamentals.
staticParams.S = photoreceptors.nomogram.S;
staticParams.fieldSizeDegrees = photoreceptors.fieldSizeDegrees;
staticParams.ageInYears = photoreceptors.ageInYears;
staticParams.pupilDiameterMM = photoreceptors.pupilDiameter.value;
staticParams.lensTransmittance = photoreceptors.lensDensity.transmittance;
staticParams.macularTransmittance = photoreceptors.macularPigmentDensity.transmittance;
staticParams.quantalEfficiency = photoreceptors.quantalEfficiency.value;
CHECK_FOR_AGREEMENT = true;
if (nargin < 7 || isempty(LserWeight))
staticParams.LserWeight = 0.56;
else
staticParams.LserWeight = LserWeight;
end
if (DORODS && nargin >= 9 && ~isempty(rodAxialDensity))
params.axialDensity = rodAxialDensity;
CHECK_FOR_AGREEMENT = false;
else
params.axialDensity = photoreceptors.axialDensity.bleachedValue;
end
if (~isfield(params,'absorbance'))
if (length(params.lambdaMax) ~= 3 && length(params.lambdaMax) ~= 1)
CHECK_FOR_AGREEMENT = false;
end
end
% Shift in lambda max bookkeeping.
if isfield(params,'indDiffParams')
CHECK_FOR_AGREEMENT = false;
end
%% Drop into more general routine to compute
%
% See comment in ComputeRawConeFundamentals about the fact that
% we ought to unify this routine and what FillInPhotoreceptors does.
[T_quantalAbsorptionsNormalized,T_quantalAbsorptions,T_quantalIsomerizations,adjIndDiffParams] = ComputeRawConeFundamentals(params,staticParams);
%% A little reality check.
%
% The call to FillInPhotoreceptors also computes what here is called
% T_quantal. It is in the field effectiveAbsorptance. For cases where
% we aren't playing games with the parameters after the call to
% FillInPhotoreceptors, we can check for agreement.
if (CHECK_FOR_AGREEMENT)
diffs = abs(T_quantalAbsorptions(:)-photoreceptors.effectiveAbsorptance(:));
if (max(diffs(:)) > 1e-7)
error('Two ways of computing absorption quantal efficiency referred to the cornea DO NOT AGREE');
end
diffs = abs(T_quantalIsomerizations(:)-photoreceptors.isomerizationAbsorptance(:));
if (max(diffs(:)) > 1e-7)
error('Two ways of computing isomerization quantal efficiency referred to the cornea DO NOT AGREE');
end
end
end
|