File: ComputeRawConeFundamentals.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 (260 lines) | stat: -rw-r--r-- 12,708 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
function [T_quantalAbsorptionsNormalized,T_quantalAbsorptions,T_quantalIsomerizations,adjIndDiffParams] = ComputeRawConeFundamentals(params,staticParams)
% [T_quantalAbsorptionsNormalized,T_quantalAbsorptions,T_quantalIsomerizations,adjIndDiffParams] = ComputeRawConeFundamentals(params,staticParams)
%
% Function to compute normalized cone quantal sensitivities from underlying
% pieces and parameters.
%
% Note that 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 types of quantal sensitivity functions.  The
% first gives the probability that a photon will be absorbed.  These are
% returned in variable T_quantalAbsorptionsNormalized and
% T_quantalAbsorptions, with the first being normalized. The second is the
% probability that the photon will cause a photopigment isomerization. This
% is returned in T_quantalIsomerizations.
%
% It is T_quantalIsomerizations that you want to use to compute
% isomerization rates from retinal illuminance. See note at the end of
% function FillInPhotoreceptors for some information about conventions.  In
% particular, this routine takes pre-retinal absorption into account in its
% computation of probability of absorptions and isomerizations, so that the
% relevant retinal illuminance 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.  Nor does it
% account for the collecting area of a photoreceptor, for cones the inner
% segment diameter.
%
% In the passed params structure, you can either pass the lambdaMax values
% for the photopigment, in which case the absorbance is computed from the
% specified nomogram, or you can pass the absorbance values directly in
% T_xxx format.  A typical choice in this case would be
% 10.^T_log10coneabsorbance_ss for the Stockman-Sharpe/CIE estimates.
%
% The typical use of this function is to be called by
% ComputeCIEConeFundamentals, which sets up the passed structures acording
% to the CIE standard. This routine, however, could in principle be used
% with a wide variety of choices of the component pieces.
%
% The other place this function is used is in attempts to fit a set of cone
% fundamentals by doing parameter search over the pieces.  It is this
% second use that led to the parameters being split over two separate
% structures, one that is held static during the fit and the other which
% contains the parameters that could be searched over by a calling routine.
% For examples, see:
%   FitConeFundamentalsWithNomogram, FitConeFundamentalsTest.
% Looking around today (8/10/13), I (DHB) don't see any examples where this
% routine is called directly.  Rather, it is a subfunction called by
% ComputeCIEConeFundamentals.  The search routines above use
% ComputeCIEConeFundamentals, and only search over lambdaMax values.  I
% think I wrote this with the thought that I might one day search over more
% parameters, but lost motivation to carry it throught.
%
% The computations done here are very similar to those done in routine
% FillInPhotoreceptors.  I (DHB) think that I forgot about what
% FillInPhotoreceptors could do when I wrote this, which has led to some
% redundancy. FillInPhotoreceptors returns a field called
% effectiveAbsorptance, which are the actual quantal efficiencies (not
% normalized) referred to the cornea.  FillInPhotoceptors also computes a
% field isomerizationAbsorptance, which takes the quantal efficiency of
% isomerizations (probability of an isomerization given an absorption into
% acount.
%
% It would probably be clever to unify the two sets of routines a little
% more, but we may never get to it.  The routine ComputeCIEConeFundamentals
% does contain a check that this routine and what is returned by
% FillInPhotoreceptors agree with each other, for cases where the
% parameters match.
%
% See ComputeCIEConeFundamentals for the breakdown of how the Asano et al.
% (2016) individual differences model is specified in params.indDiffParams.
%
% See ComputeCIEConeFundamentals for documentation of the adjIndDiffParams
% output argument.
%
% See also: ComputeCIEConeFundamentals, CIEConeFundamentalsTest,
% FitConeFundamentalsWithNomogram,
%           FitConeFundamentalsTest, DefaultPhotoreceptors,
%           FillInPhotoreceptors.
%
% 8/12/11  dhb  Starting to make this actually work.
% 8/14/11  dhb  Change name, expand comments.
% 8/10/13  dhb  Expand comments.  Return unscaled quantal efficiencies too.
% 2/26/16  dhb, ms  Add in Asano et al. (2016) individual observer adjustments
% 3/30/17  ms   Added output argument returning adjusted ind differences
% 6/4/18   ms   Included absorbance spectrum into adjusted ind diff output arg

% Handle bad value
index = find(params.axialDensity <= 0.0001);
if (~isempty(index))
    params.axialDensity(index) = 0.0001;
end

% Figure out how many receptor classes we're handling
if (isfield(params,'absorbance'))
    nReceptorTypes = size(params.absorbance,1);
else
    nReceptorTypes = length(params.lambdaMax);
end

% Fill in null individual differences parameters if they are not passed
if isempty(params.indDiffParams)
    params.indDiffParams.lambdaMaxShift = zeros(nReceptorTypes,1);
    params.indDiffParams.shiftType = 'linear';
    params.indDiffParams.dlens = 0;
    params.indDiffParams.dmac = 0;
    params.indDiffParams.dphotopigment = zeros(nReceptorTypes,1);
end

% Handle optional values for lens and macular pigment density
% in the parameters structure.  There are two mutually exclusive
% ways to do this.  For historical reasons, we can pass an additive
% density adjustment.  More recently we implemented the Asano et al. (2016)
% parameterization.  We don't allow both to happen at once.
%
% The logic here is a little hairy, because the way that we used to
% adjust lens and mac density was additive, but Asano et al. (2016) do
% it in a multiplicative fashion, so we need a flag to keep track of what
% we're going to do with the numbers down below.
if (~isfield(params,'extraLens'))
    params.extraLens = 0;
end
if (~isfield(params,'extraMac'))
    params.extraMac = 0;
end
if (params.extraLens ~= 0 && params.indDiffParams.dlens ~= 0)
    error('Cannot specify lens density adjustment two ways');
end
if (params.extraMac ~= 0 && params.indDiffParams.dmac ~= 0)
    error('Cannot specify macular pigment density adjustment two ways');
end
OLDLENSWAY = true;
if (params.extraLens == 0)
    OLDLENSWAY = false;
    params.extraLens = params.indDiffParams.dlens/100;
end
OLDMACWAY = true;
if (params.extraMac == 0)
    OLDMACWAY = false;
    params.extraMac = params.indDiffParams.dmac/100;
end

% Prereceptor transmittance.  Check that passed parameters are not so weird
% as to lead to transmittances greater than 1, and throw error if so.
if (OLDLENSWAY)
    fprintf('Using old way of adjusting lens density.  Consider switching to newer implementation via the params.indDiffParams field\n');
    lens = 10.^-(-log10(staticParams.lensTransmittance)+params.extraLens);
else
    lens = 10.^-(-log10(staticParams.lensTransmittance) * (1 + params.extraLens));
end
if (any(lens > 1))
    error('You have passed parameters that make lens transmittance greater than 1');
end
%lens(lens > 1) = 1;
adjIndDiffParams.lens = lens;

if (OLDMACWAY)
    fprintf('Using old way of adjusting macular pigment density.  Consider switching to newer implementation via the params.indDiffParams field\n');
    mac = 10.^-(-log10(staticParams.macularTransmittance)+params.extraMac);
else
    mac = 10.^-(-log10(staticParams.macularTransmittance) * (1 + params.extraMac));
end
if (any(mac > 1))
    error('You have passed parameters that make macular pigment transmittance greater than 1');
end
adjIndDiffParams.mac = mac;
%mac(mac > 1) = 1;

% Compute nomogram if absorbance wasn't passed directly.  We detect
% a direct pass by the existance of params.absorbance.
if (isfield(params,'absorbance'))
    absorbance = params.absorbance;
else
    absorbance = PhotopigmentNomogram(staticParams.S,params.lambdaMax,staticParams.whichNomogram);
end

% Shift absorbance, if desired
if (~isempty(params.indDiffParams.lambdaMaxShift))
    if (length(params.indDiffParams.lambdaMaxShift) ~= size(absorbance,1))
        error('Length of passed lambdaMaxShift does not match number of absorbances available to shift');
    end
    
    absorbance = ShiftPhotopigmentAbsorbance(staticParams.S,absorbance,params.indDiffParams.lambdaMaxShift,params.indDiffParams.shiftType);
end
adjIndDiffParams.absorbance = absorbance;

% Compute absorptance
%
% Handle special case where we deal with ser/ala polymorphism for L cone
%
% We've put in the Asano et al. (2016) multiplicative adjustment.  Since we
% weren't adjusting photopigment density previously (except for
% self-screening), we don't have to deal with two threads here.
%
% Note that density can also get adjusted according to light level to
% account for photopigment bleaching.  That happens in
% FillInPhotoreceptors, which would typically be called before we get here.
% We think this is OK, because both the Asano et al. and the fraction
% bleaching adjustment are multiplicative adjustments of axial density, and
% multiplication commutes so it doesn't matter what order we do things in.
if (size(absorbance,1) == 4)
    if (any(params.indDiffParams.dphotopigment ~= 0))
        error('Cannot use Asano et al. individual cone model with our weird 4 cone calling mode');
    end
    absorptance = AbsorbanceToAbsorptance(absorbance,staticParams.S,...
        [params.axialDensity(1) ; params.axialDensity(1) ; ...
        params.axialDensity(2) ; params.axialDensity(3)]);
elseif (size(absorbance,1) == 3)
    if (length(params.indDiffParams.dphotopigment) ~= 3)
        error('Density adjustment parameter length not right for cones');
    end
    LDensity = params.axialDensity(1) * (1 + params.indDiffParams.dphotopigment(1)/100);
    MDensity = params.axialDensity(2) * (1 + params.indDiffParams.dphotopigment(2)/100);
    SDensity = params.axialDensity(3) * (1 + params.indDiffParams.dphotopigment(3)/100);
    absorptance = AbsorbanceToAbsorptance(absorbance,staticParams.S,[LDensity ; MDensity ; SDensity]);
    adjIndDiffParams.dphotopigment = [LDensity MDensity SDensity];
elseif (size(absorbance,1) == 1 && params.DORODS)
    if (length(params.indDiffParams.dphotopigment) ~= 1)
        error('Density adjustment parameter length not right for rods');
    end
    RodDensity = params.axialDensity(1) + params.indDiffParams.dphotopigment(1)/100;
    absorptance = AbsorbanceToAbsorptance(absorbance,staticParams.S,RodDensity);
    adjIndDiffParams.dphotopigment = RodDensity;
else
    error('Unexpected number of photopigment lambda max values passed');
end
adjIndDiffParams.absorptance = absorptance;

%% Put together pre-receptor and receptor parts
for i = 1:size(absorptance,1)
    absorptance(i,:) = absorptance(i,:) .* lens .* mac;
end

%% Put it into the right form
if (size(absorptance,1) == 4)
    T_quantalAbsorptions = zeros(3,staticParams.S(3));
    T_quantalAbsorptions(1,:) = staticParams.LserWeight*absorptance(1,:) + ...
        (1-staticParams.LserWeight)*absorptance(2,:);
    T_quantalAbsorptions(2,:) = absorptance(3,:);
    T_quantalAbsorptions(3,:) = absorptance(4,:);
elseif (size(absorptance,1) == 3)
    T_quantalAbsorptions = zeros(3,staticParams.S(3));
    T_quantalAbsorptions(1,:) = absorptance(1,:);
    T_quantalAbsorptions(2,:) = absorptance(2,:);
    T_quantalAbsorptions(3,:) = absorptance(3,:);
elseif (size(absorptance,1) == 1 && params.DORODS)
    T_quantalAbsorptions = zeros(1,staticParams.S(3));
    T_quantalAbsorptions(1,:) = absorptance(1,:);
else
    error('Unexpected number of photopigment lambda max values passed');
end

%% Normalize to max of one for each receptor, and also compute isomerization quantal efficiency.
for i = 1:size(T_quantalAbsorptions,1)
    T_quantalIsomerizations = T_quantalAbsorptions*staticParams.quantalEfficiency(i);
    T_quantalAbsorptionsNormalized(i,:) = T_quantalAbsorptions(i,:)/max(T_quantalAbsorptions(i,:));
end