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
|