File: ComputeCIEConeFundamentals.m

package info (click to toggle)
psychtoolbox-3 3.0.19.14.dfsg1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 86,796 kB
  • sloc: ansic: 176,245; cpp: 20,103; objc: 5,393; sh: 2,753; python: 1,397; php: 384; makefile: 193; java: 113
file content (311 lines) | stat: -rw-r--r-- 15,744 bytes parent folder | download | duplicates (3)
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