13
34
% this is done, but the template may be useful someday.
14
35
% 03/20/12 dhb Update cal file for PTB 3.
15
36
% 04/09/12 dhb Add test of irradiance to troland conversion.
20
% Set some photoreceptor properties.
21
% whatCalc = 'LivingDog';
22
whatCalc = 'LivingHumanFovea';
37
% 04/27/13 dhb More extensive comments.
38
% 7/19/13 dhb Print out photoreceptors structure using PrintPhotoreceptors.
39
% dhb Add monochromatic light option to the section that starts with trolands.
40
% 8/11/13 dhb Add test of AborbtanceToAbsorbance.
41
% dhb Protect against case when absorbance is provided directly.
47
%% Set photoreceptor properties.
49
% The photoreceptors structure gets filled with
50
% key parameters values (pupil size, eye length,
51
% pre-retinal absorbance, etc.)
53
% The routine DefaultPhotoreceptors is a high level
54
% call. It fills in the 'source' fields and some
55
% values according to high-level descriptor (e.g.,
56
% ('CIE2Deg'). See help for that routine
57
% for available options.
59
% The routine FillInPhotoreceptors fetches the actual
60
% values for various fields, depending on the source.
62
% To get a feel for this, check what is in the photoreceptors
63
% structure after the first call, and then after the second.
24
65
photoreceptors = DefaultPhotoreceptors(whatCalc);
66
photoreceptors.eyeLengthMM.source = 'LeGrand';
25
67
photoreceptors = FillInPhotoreceptors(photoreceptors);
27
% Define common wavelength sampling for this script.
69
%% Check AbsorbtancetoAbsorbance
71
% Simple check that this routine does what we expect, since
72
% we never use it anywhere else and this seems like as good
73
% a place to test it as any.
75
% We omit the normalization, because sometimes the wavelength
76
% sampling we use leads to a maximimum initial absorbance that
77
% is not unity, and letting AbsorbtanceToAbsorbance normalize
78
% causes disagreement.
79
testAbsorbance = photoreceptors.absorbance;
80
testAbsorbtance = photoreceptors.absorbtance;
81
checkAbsorbance = AbsorbtanceToAbsorbance(testAbsorbtance, photoreceptors.nomogram.S, photoreceptors.axialDensity.value,false);
82
diffs = testAbsorbance-checkAbsorbance;
83
if (max(abs(diffs(:))) > 1e-7)
84
error('Cannot properly invert absorbance/absorbtance computations');
87
%% Define common wavelength sampling for this script.
89
% S is [start delta nsamples] for the wavelengths in nm.
90
% This is standard PTB convention.
28
91
S = photoreceptors.nomogram.S;
30
% Get light spectrum. You can choose various
31
% illustrative examples.
32
whichLight = 'fromMonitorRadiance';
33
%whichLight = 'fromTrolands';
35
% Here we'll start with a xenon arc lamp relative spectrum
37
% Convert from a specification of trolands and relative spectral power distribution.
38
% Computation to retinal irradiance is done two ways just for fun.
93
%% XYZ color matching functions
95
T_xyz = SplineCmf(S_xyz1931,683*T_xyz1931,S);
98
%% Get light spectrum. You can choose various illustrative examples.
102
% 'fromMonitorRadiance'
103
% 'fromUniformQuantalSpd'
104
whichInputType = 'fromTrolands';
105
switch (whichInputType)
107
% Start with troland value and a relative spectrum
109
fprintf('Computing from troland value and relative spectrum\n');
110
% Give troland value and type
112
% Type may be 'Photopic', 'Scotopic', or 'JuddVos'
41
114
trolandType = 'Photopic';
43
irradianceWatts = TrolandsToRetIrradiance(spd_xenonArc,S_xenonArc,trolands, ...
117
fprintf('Using photopic trolands\n');
119
fprintf('Using scotopic trolands\n');
121
fprintf('Using Judd-Vos luminosity function in troland calculations\n');
123
fprintf('Unknown troland type specified');
128
% We do these computations for a fixed pupil size, ignoring the
129
% pupilDiamter field of the photoreceptors structure. That field
130
% is set up to specify a source formula that estimates pupil diameter
133
% Since we are starting in trolands, the pupil size shouldn't actually
134
% effect the calculations, except for finding the radiance that is
135
% equivalent to the specified troland value.
137
% We remove the pupilDiameter.source field to make sure we aren't sending
138
% mixed messages about how we want to handle pupil diameter.
139
photoreceptors.pupilDiameter.value = 2;
140
if (isfield(photoreceptors.pupilDiameter,'source'))
141
photoreceptors.pupilDiameter = rmfield(photoreceptors.pupilDiameter,'source');
143
pupilAreaMm2 = pi*(photoreceptors.pupilDiameter.value/2)^2;
145
% Specify relative spectrum to be used in
146
% conversion to a full spectrum.
152
% If type is 'Monochromatic', must specify
154
spectrumType = 'Monochromatic';
155
switch (spectrumType)
157
monoWavelengthNm = 550;
159
monoWavelengthIndex = find(wls == monoWavelengthNm);
160
if (isempty(monoWavelengthIndex))
161
error('No sample wavelength matches desired wavelength');
163
spd_fromTrolands = zeros(size(wls));
164
spd_fromTrolands(monoWavelengthIndex) = 1;
165
fprintf('Using monochromatic %d nm light as relative spectrum\n',monoWavelengthNm);
168
spd_fromTrolands = SplineSpd(S_xenonArc,spd_xenonArc,S);
169
clear S_xenonArc spd_xenonArc
170
fprintf('Using the spectrum of a xenon arc lamp as relative spectrum\n');
172
error('Unknown spectrum type specified');
175
% Convert trolands back to spectral retinal irradiance. This
176
% depends on the pupil size and eye length specified.
177
irradianceWattsPerUm2 = TrolandsToRetIrradiance(spd_fromTrolands,S,trolands, ...
44
178
trolandType,photoreceptors.species,photoreceptors.eyeLengthMM.value);
45
irradianceTrolandsCheck = RetIrradianceToTrolands(irradianceWatts,S_xenonArc,trolandType, ...
179
irradianceTrolandsCheck = RetIrradianceToTrolands(irradianceWattsPerUm2,S,trolandType, ...
46
180
photoreceptors.species,photoreceptors.eyeLengthMM.value);
47
181
trolandsCheck = sum(irradianceTrolandsCheck);
48
fprintf('\nInput trolands is %0.1f, checked value is %0.1f\n\n',trolands,trolandsCheck);
49
irradianceWatts = SplineSpd(S_xenonArc,irradianceWatts,S);
182
fprintf('Input troland value is %0.1f, checked value is %0.1f\n',trolands,trolandsCheck);
183
irradianceWattsPerUm2 = SplineSpd(S,irradianceWattsPerUm2,S);
51
185
% Another way to do this calculation. Pupil size should cancel out. Should get
52
% same answer as above.
54
pupilAreaMM = pi*(pupilSizeMM/2)^2;
55
luminance = TrolandsToLum(trolands,pupilAreaMM);
56
radianceWatts = LumToRadiance(spd_xenonArc,S_xenonArc,luminance,trolandType);
57
irradianceWattsCheck = RadianceToRetIrradiance(radianceWatts,S_xenonArc,pupilAreaMM,photoreceptors.eyeLengthMM.value);
186
% same answer as above. This has as a byproduct computing a stimulus radiance,
187
% which is useful for some of the common printout below.
188
luminanceCdM2FromTrolands = TrolandsToLum(trolands,pupilAreaMm2);
189
radianceWattsPerM2Sr = LumToRadiance(spd_fromTrolands,S,luminanceCdM2FromTrolands,trolandType);
190
photopicLuminanceCdM2 = T_Y*radianceWattsPerM2Sr;
191
irradianceWattsCheck = RadianceToRetIrradiance(radianceWattsPerM2Sr,S,pupilAreaMm2,photoreceptors.eyeLengthMM.value);
58
192
figure(1); clf; hold on
59
set(plot(SToWls(S),irradianceWatts,'r'),'LineWidth',2);
60
set(plot(SToWls(S_xenonArc),irradianceWattsCheck,'k'),'LineWidth',2);
193
set(plot(SToWls(S),irradianceWattsPerUm2,'r'),'LineWidth',2);
194
set(plot(SToWls(S),irradianceWattsCheck,'k'),'LineWidth',2);
61
195
set(title('Check of trolands to irradiance calculation'),'FontSize',14);
62
196
set(xlabel('Wavelength (mm)'),'FontSize',14);
63
197
set(ylabel('Irradiance'),'FontSize',14);
199
% For case if monochromatic light, can check retinal irradiance against
200
% the direct formulae provided in W&S, 2cd edition, p. 105, eqs 2.4.4
201
% Note that these equations are missing a factor of
202
% the wavelength in the numerator for the quantal conversions, as
203
% pointed out by Makous in his 1997 JOSA paper.
205
% This only works for 'Photopic' and 'Scotopic' trolands.
207
% There must be an eye length implicit in this calculation. Using
208
% the LeGrand model eye length gives good agreement between these
209
% and our more general calculations done in the main section below.
211
% Makous (1997), JOSA A, 14, p. 2331 gives retinal illuminances of
212
% 5.44 quanta/[um2-sec] for 1 scotopic troland, and 14.65 quanta/[um2-sec]
213
% for 1 photopic troland, with the calulations specified for 510 nm.
214
% The calculations here, done in several different ways, yield
215
% 5.442 (scotopic, agrees) and 26.85 (photopic, does not agree). But at
216
% 550 nm, the code here yields 14.64 quanta/[um2-sec], which seems close
217
% enough to the provided 14.65 to make me think that Makous' value is
218
% actually for a wavelength close to 550 nm. That would be a more typical
219
% wavelength at which to do photopic calculations, despite what the text
222
% Note that there are also errors in Tables 3 and 4 of the Makous
223
% paper, and that corrected values appear in Tables 3 and 2 of
224
% Makous (2004), Scotopic vision, In The Visual Neurosciences,
225
% Werner and Chalupa (eds). What does not seem to be specified
226
% in either place is the wavelengths used in the calculations of
228
if (strcmp(spectrumType,'Monochromatic'))
232
T_vLambda = SplineCmf(S_xyz1931,T_xyz1931(2,:),S);
233
clear T_xyz1931 S_xyz1931
234
magicFactorE = 5.261e-12;
235
magicFactorQ = 2.649e13;
238
T_vLambda = SplineCmf(S_rods,T_rods,S);
239
magicFactorE = 2.114e-12;
240
magicFactorQ = 1.064e13;
243
if (strcmp(spectrumType,'Monochromatic'))
245
case {'Photopic','Scotopic'}
246
irradianceDirectWattsPerMm2Check = magicFactorE*trolands/T_vLambda(monoWavelengthIndex);
247
irradianceDirectQuantaPerMm2SecCheck = 1e-9*monoWavelengthNm*magicFactorQ*trolands/T_vLambda(monoWavelengthIndex);
248
fprintf('Retinal irradiance in area units computed from from trolands via Wyszecki and Stiles formulae\n');
249
fprintf('\t%0.4g Watts/mm2\n\t%0.4g quanta/[mm2-sec]\n',sum(irradianceDirectWattsPerMm2Check),sum(irradianceDirectQuantaPerMm2SecCheck));
65
253
% Start with radiance measurements, which we just
66
254
% pull out of the Toolbox's default calibration file.
67
255
case 'fromMonitorRadiance'
68
256
% Load light radiance. We'll use a monitor white.
69
257
% The original units are watts/sr-m^2-wlinterval.
70
258
cal = LoadCalFile('PTB3TestCal');
71
radianceWatts = SplineSpd(cal.S_device,sum(cal.P_device,2),S);
259
radianceWattsPerM2Sr = SplineSpd(cal.S_device,sum(cal.P_device,2),S);
73
261
% Find pupil area, needed to get retinal irradiance. We compute
74
% pupil area based on the luminance of stimulus.
76
T_xyz = SplineCmf(S_xyz1931,683*T_xyz1931,S);
77
theXYZ = T_xyz*radianceWatts; theLuminance = theXYZ(2);
78
[nil,pupilAreaMM] = PupilDiameterFromLum(theLuminance,photoreceptors.pupilDiameter.source);
262
% pupil area based on the luminance of stimulus according to the
263
% algorithm specified in the photoreceptors structure.
264
theXYZ = T_xyz*radianceWattsPerM2Sr; theLuminance = theXYZ(2);
265
[nil,pupilAreaMm2] = PupilDiameterFromLum(theLuminance,photoreceptors.pupilDiameter.source);
266
photopicLuminanceCdM2 = T_Y*radianceWattsPerM2Sr;
80
268
% Convert radiance of source to retinal irradiance and convert to quantal units.
81
irradianceWatts = RadianceToRetIrradiance(radianceWatts,S, ...
82
pupilAreaMM,photoreceptors.eyeLengthMM.value);
269
irradianceWattsPerUm2 = RadianceToRetIrradiance(radianceWattsPerM2Sr,S, ...
270
pupilAreaMm2,photoreceptors.eyeLengthMM.value);
84
% This is here to match a parameterization the Brian Wandell supplied
272
% This light as well as some parameter tweaking are here to match a parameterization that Brian Wandell supplied
85
273
% to match what his code to do these computations produces. Note also
86
274
% the mucking with the photoreceptors structure. Wandell estimates
87
% L, M, S isomerizations/cone-sec of 16.5, 12.68, 2.27.
275
% L, M, S isomerizations/cone-sec of 16.5, 12.68, 2.27. These are very close to the numbers
88
277
case 'fromUniformQuantalSpd',
89
278
% Load corneal cone sensitivities in energy units, convert to quantal sensitivities
90
279
% and set specified peak absorbtance.
106
296
T_cones = diag(peakIsomerizationEfficiency)*T_cones;
107
297
photoreceptors.isomerizationAbsorbtance = T_cones;
109
% Get spectral luminous efficiency function
110
load T_xyz1931; T_xyz = T_xyz1931; S_xyz = S_xyz1931;
111
% load T_xyzJuddVos; T_xyz = T_xyzJuddVos; S_xyz = S_xyzJuddVos;
112
T_Y = 683*SplineCmf(S_xyz,T_xyz(2,:),S);
114
299
% Create a spectrally uniform spd (in quantal units), and convert
115
300
% to energy units.
116
301
uniformSpd = QuantaToEnergy(S,ones(S(3),1));
118
303
% Normalize to radiance corresponding to 1 cd/m2.
119
304
normConst = T_Y*uniformSpd;
120
radianceWatts = uniformSpd/normConst;
305
radianceWattsPerM2Sr = uniformSpd/normConst;
306
photopicLuminanceCdM2 = T_Y*radianceWattsPerM2Sr;
122
% Set pupil diameter for 1mm2 pupil area, photoreceptor diameter for 4mm2 collecting
308
% Set pupil diameter for 1 mm2 pupil area, photoreceptor diameter for 4 mm2 collecting
123
309
% area. Set eye length to 17 mm.
124
310
photoreceptors.pupilDiameter.value = 2*sqrt(1/pi);
125
pupilAreaMM = pi*(photoreceptors.pupilDiameter.value/2)^2;
311
pupilAreaMm2 = pi*(photoreceptors.pupilDiameter.value/2)^2;
126
312
photoreceptors.ISdiameter.value = [2*sqrt(4/pi) 2*sqrt(4/pi) 2*sqrt(4/pi)]';
127
313
photoreceptors.eyeLengthMM.value = 17;
128
irradianceWatts = RadianceToRetIrradiance(radianceWatts,S,pupilAreaMM,photoreceptors.eyeLengthMM.value );
131
irradianceQuanta = EnergyToQuanta(S,irradianceWatts);
314
irradianceWattsPerUm2 = RadianceToRetIrradiance(radianceWattsPerM2Sr,S,pupilAreaMm2,photoreceptors.eyeLengthMM.value );
317
%% Print out a whole bunch of quantities that are equivalent to the radiance, given
318
% other eye parameters.
319
radianceWattsPerCm2Sr = (10.^-4)*radianceWattsPerM2Sr;
320
radianceQuantaPerCm2SrSec = EnergyToQuanta(S,radianceWattsPerCm2Sr);
321
degPerMm = RetinalMMToDegrees(1,photoreceptors.eyeLengthMM.value);
322
irradianceWattsPerUm2Check = RadianceToRetIrradiance(radianceWattsPerM2Sr,S,pupilAreaMm2,photoreceptors.eyeLengthMM.value);
323
if (any(abs(irradianceWattsPerUm2 - irradianceWattsPerUm2Check) > 1e-10))
324
error('Back computation of retinal irradiance from radiance does not check');
326
irradianceScotTrolands = RetIrradianceToTrolands(irradianceWattsPerUm2, S, 'Scotopic', [], num2str(photoreceptors.eyeLengthMM.value));
327
irradiancePhotTrolands = RetIrradianceToTrolands(irradianceWattsPerUm2, S, 'Photopic', [], num2str(photoreceptors.eyeLengthMM.value));
328
irradianceQuantaPerUm2Sec = EnergyToQuanta(S,irradianceWattsPerUm2);
329
irradianceWattsPerCm2 = (10.^8)*irradianceWattsPerUm2;
330
irradianceQuantaPerCm2Sec = (10.^8)*irradianceQuantaPerUm2Sec;
331
irradianceQuantaPerMm2Sec = (10.^-2)*irradianceQuantaPerCm2Sec;
332
irradianceQuantaPerUm2Sec = (10.^-6)*irradianceQuantaPerMm2Sec;
333
irradianceQuantaPerDeg2Sec = (degPerMm^2)*irradianceQuantaPerMm2Sec;
335
% Print out photoreceptor stucture information
337
PrintPhotoreceptors(photoreceptors);
340
% Radiometric iformation
341
fprintf('Luminance %0.3f cd/m2\n',photopicLuminanceCdM2);
342
fprintf('Stimulus retinal irradiance %0.4g (%0.1f log10) watts/cm2\n',sum(irradianceWattsPerCm2),log10(sum(irradianceWattsPerCm2)));
343
fprintf('Stimulus retinal irradiance %0.4g (%0.1f log10) watts/mm2\n',1e-2*sum(irradianceWattsPerCm2),log10(sum(irradianceWattsPerCm2)));
344
fprintf('Stimulus retinal irradiance %0.4g (%0.1f log10) quanta/[cm2-sec]\n',sum(irradianceQuantaPerCm2Sec),log10(sum(irradianceQuantaPerCm2Sec)));
345
fprintf('Stimulus retinal irradiance %0.4g (%0.1f log10) quanta/[mm2-sec]\n',sum(irradianceQuantaPerMm2Sec),log10(sum(irradianceQuantaPerMm2Sec)));
346
fprintf('Stimulus retinal irradiance %0.4g (%0.1f log10) quanta/[um2-sec]\n',sum(irradianceQuantaPerUm2Sec),log10(sum(irradianceQuantaPerUm2Sec)));
347
fprintf('Stimulus retinal irradiance %0.4g (%0.1f log10) quanta/[deg2-sec]\n',sum(irradianceQuantaPerDeg2Sec),log10(sum(irradianceQuantaPerDeg2Sec)));
350
%% Get retinal irradiance in quanta/[sec-um^2-wlinterval]
351
irradianceQuanta = EnergyToQuanta(S,irradianceWattsPerUm2);
132
352
figure(2); clf; set(gcf,'Position',[100 400 700 300]);
133
353
subplot(1,2,1); hold on
134
354
set(plot(SToWls(S),irradianceQuanta,'r'),'LineWidth',2);