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