Example Photography exposure¶
Theory and background¶
This example illustrates how exposure in photography depends on factors: latitude, time of day, day of year, weather, composition. It relates these to camera settings: film speed (e.g. ISO 100), aperture and shutter speed. The mathematical approach and model is taken from book written by V. Setälä. 1 This book illustrates the approach as nomographs but they are different compared with the one generatated here. Book uses shadow length, but we break shadow length into time, date and latitude via solar zenith angle.
The basic equation in Setälä (pp.492-494) can be extracted and written as
where parameters of (1) are listed below:
\(FS\,\) |
Film speed |
DIN value that equals \(10 \log (S) +1 \,\),where S is ISO FILM speed |
\(T\,\) |
shutter time |
\(10 \log \left( \frac{t}{1/10}\right)\) |
\(A\,\) |
aperture |
\(10 \log \left(\frac{N^2}{3.2^2}\right)\) |
\(L\,\) |
shadow length (in steps) |
two times (shadow length)/(person length) \(= 2 \arctan ( \phi) \,\), where \(\phi \,\) is solar zenith angle. |
\(W\,\) |
weather |
Clear sky, Cumulus clouds: 0, Clear sky: 1, Sun through clouds: 3, Sky light gray: 6, Sky dark gray: 9, Thunder-clouds cover sky: 12 |
\(C\,\) |
Composition |
Person under trees: -6, Inside forest : -4, Person in shadow of wall : -1, Person at open place; alley under trees : 2, Buildings; street : 5, Landscape and front matter : 7, Open landscape : 9, Snow landscape and front matter; beach : 11,Snow field; open sea : 13, Clouds : 15 |
It is to be noted that Setälä has stops ten times base-10 logarithmic. Today we think stops in base-2 logarithmic.
Shadow lenght¶
Calculation of shadow length as a function of day of year, time of day and latitude is according to 2. Following equations are used. For fractional year (without time information) we take
\(\gamma = (day-1+0.5)2\pi/365. \,\)
For time offset (eqtime) we use equation (in minutes)
\(TO = 229.18(0.000075+0.001868\cos(\gamma)-0.032077\sin(´\gamma)-0.014615\cos(2\gamma)-0.040849\sin(2\gamma)) \,\)
to calculate that error is below 17 minutes for time axis. We assume that sun is at heightest point at noon and this is the error and approximation. We calculate stops in logarithmic scale and in this case we do not need very accurate equations for time. For declination we use equation
and for hour angle
\(ha=(60h+\overline{TO})/4-180. \,\)
Solar zenith angle (\(\phi \,\)), latitude (LAT), declination (D) and hour angle (ha) are connected with equation:
\(\cos (\phi ) = \sin(LAT)sin(D)+\cos(LAT)\cos(D)\cos(ha). \,\)
This is in our desired form as a function of hour (h), day (day), latitude (LAT), solar zenith angle (\(\phi \,\)):
\(\cos (\phi ) = \sin(LAT)sin(D(\gamma(day)))+\cos(LAT)\cos(D(\gamma(day)))\cos(ha(h)) \,\).
In practice illuminance of flat surface on earth depends on solar zenith angle as \(\cos(\phi)\,\). Setälä uses shadow length that is easily measurable, but scales incorrectly, as value is proportional to \(\tan(\phi)\,\). Also Setälä sums linear value with logarithmic ones as a practical approximation. To correct these assumptions, here we assume that values for shadow length 1 and 10 for Setälä are reasonable, and an equation that scales logarithmically is found:
\(L = 0.33766 - 13.656 \log10 (\cos(\phi)) \,\)
that gives \(L=1\,\) for \(\phi = 26.565 =\arctan(1/2)\,\) and \(L=10\,\) for \(\phi = 78.69 =\arctan(10/2).\,\)
Construction of the nomograph¶
The presented equation is the following:
In order to construct the nomograph, we split the equation into four blocks and an additional block to present values as EV100.
Block 1 (Type 9)¶
formed into determinant:
Block 2 (Type 5)¶
split into two equations for contour construction:
Block 3 (Type 5)¶
split into two equations for contour construction:
Block 4 (Type 3)¶
equals
where
is inverse shutter time.
Block 5 (Type 8)¶
Additional EV100 scale by using relation
Block 6 (Type 1)¶
Maximum focal length calculator according to equation
written as
in order to align correctly with previous equation. The values for the factor \(f\,\) are: DSLR (3/2), 35mm (1), DSLR image stabilization (3/8) and 35mm image stabilization (1/8).
Generated nomograph¶
Source code¶
1"""
2 ex_photo_exposure.py
3
4 Photgraph exposure.
5"""
6import sys
7sys.path.insert(0, "..")
8from pynomo.nomographer import *
9"""
10functions for solartime taken from solareqns.pdf from
11http://www.srrb.noaa.gov/highlights/sunrise/solareqns.PDF
12"""
13
14
15# fractional year
16def gamma(day):
17 return 2 * pi / 365.0 * (day - 1 + 0.5)
18# equation of time
19
20
21def eq_time(day):
22 gamma0 = gamma(day)
23 return 229.18 * (0.000075 + 0.001868 * cos(gamma0) - 0.032077 * sin(gamma0)\
24 - 0.014615 * cos(2 * gamma0) - 0.040849 * sin(2 * gamma0))
25
26# mean correction, with constant correction we make less than 17 minutes error
27# in time axis
28temp_a = arange(0, 365.0, 0.1)
29temp_b = eq_time(temp_a)
30correction = mean(temp_b) # this is 0.0171885 minutes
31
32
33# declination
34def eq_declination(day):
35 g0 = gamma(day)
36 return 0.006918 - 0.399912 * cos(g0) + 0.070257 * sin(g0) - 0.006758 * cos(2 * g0)\
37 + 0.000907 * sin(2 * g0) - 0.002697 * cos(3 * g0) + 0.00148 * sin(3 * g0)
38
39
40def f1(dummy):
41 return 0.0
42
43
44def g1(fii):
45 return cos(fii*pi/180.0)
46
47
48def f2(lat, day):
49 dec = eq_declination(day)
50 return (cos(lat * pi / 180.0) * cos(dec)) / (1.0 + (cos(lat * pi / 180.0) * cos(dec)))
51
52
53def g2(lat, day):
54 dec = eq_declination(day) # in radians
55 return (sin(lat * pi / 180.0) * sin(dec)) / (1.0 + (cos(lat * pi / 180.0) * cos(dec)))
56
57
58def f3(dummy):
59 return 1
60
61
62def g3(h):
63 hr = (h * 60.0 + correction) / 4.0 - 180.0
64 return -1.0 * cos(hr * pi / 180.0)
65
66days_in_month = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
67times1=[]
68for idx in range(0, 12):
69 times1.append(sum(days_in_month[0:idx])+1)
70
71time_titles = ['January', 'February', 'March', 'April', 'May', 'June',
72 'July', 'August', 'September', 'October', 'November', 'December']
73
74phi_params = {'u_min': 0.0,
75 'u_max': 90.0,
76 'u_min_trafo': 0.0,
77 'u_max_trafo': 90.0,
78 'f': f1,
79 'g': g1,
80 'h': lambda u: 1.0,
81 'title': r'Solar zenith angle $\phi$',
82 'title_x_shift': 0.0,
83 'title_y_shift': 0.25,
84 'scale_type': 'linear smart',
85 'tick_levels': 4,
86 'tick_text_levels': 2,
87 'tick_side': 'right',
88 'tag': 'phi',
89 'grid': False,
90 }
91time_params = {'u_min': 0.0,
92 'u_max': 23.0,
93 'u_min_trafo': 0.0,
94 'u_max_trafo': 12.0,
95 'f': f3,
96 'g': g3,
97 'h':lambda u: 1.0,
98 'title': r'Hour (h)',
99 'title_x_shift': 0.0,
100 'title_y_shift': 0.25,
101 'scale_type': 'linear',
102 'tick_levels': 2,
103 'tick_text_levels': 1,
104 'tick_side': 'right',
105 'tag': 'none',
106 'grid': False,
107 }
108lat_day_params = {'ID': 'none', # to identify the axis
109 'tag': 'none', # for aligning block wrt others
110 'title': 'Grid',
111 'title_x_shift': 0.0,
112 'title_y_shift': 0.25,
113 'title_distance_center': 0.5,
114 'title_opposite_tick': True,
115 'u_min': 20.0, # for alignment
116 'u_max': 80.0, # for alignment
117 'f_grid': f2,
118 'g_grid': g2,
119 'h_grid': lambda u, v: 1.0,
120 'u_start': 30.0,
121 'u_stop': 80.0,
122 'v_start': times1[0], # day
123 'v_stop': times1[-1],
124 'u_values': [30.0, 40.0, 50.0, 60.0, 70.0, 80.0],
125 'u_texts': ['30', '40', '50', 'Latitude = 60', '70', '80'],
126 'v_values': times1,
127 'v_texts': time_titles,
128 'grid': True,
129 'text_prefix_u': r'',
130 'text_prefix_v': r'',
131 'text_distance': 0.5,
132 'v_texts_u_start': False,
133 'v_texts_u_stop': True,
134 'u_texts_v_start': False,
135 'u_texts_v_stop': True,
136 }
137block_params = {'block_type': 'type_9',
138 'f1_params': phi_params,
139 'f2_params': lat_day_params,
140 'f3_params': time_params,
141 'transform_ini': True,
142 'isopleth_values': [['x', [60, times1[4]], 14.0]]
143 }
144
145
146# limiting functions are to avoid NaN in contour construction that uses optimization
147def limit_xx(x):
148 x1 = x
149 return x1
150
151
152def limit_x(x):
153 x1 = x
154 return x1
155
156const_A = 0.33766
157const_B = -13.656
158
159block_params_weather = {'block_type': 'type_5',
160 'u_func': lambda u: u,
161 'v_func':lambda x, v: const_A + const_B * log10(limit_x(x)) + v,
162 'u_values': [1.0, 25.0],
163 'u_manual_axis_data': {1.0: '',
164 25.0: ''},
165 'v_values': [0.0, 1.0, 3.0, 6.0, 9.0, 12.0],
166 'v_manual_axis_data': {0.0: ['Clear sky, Cumulus clouds',
167 {'x_corr': 0.5,
168 'y_corr': 0.0,
169 'draw_line': False}],
170 1.0: 'Clear sky',
171 3.0: 'Sun through clouds',
172 6.0: 'Sky light gray',
173 9.0: 'Sky dark gray',
174 12.0: 'Thunder-clouds cover sky'},
175 'v_text_distance': 0.5,
176 'wd_tick_levels': 0,
177 'wd_tick_text_levels': 0,
178 'wd_tick_side': 'right',
179 'wd_title': '',
180 'manual_x_scale': True,
181 'x_min': 0.06,
182 'x_max': 0.99,
183 'u_title': '',
184 'v_title': '',
185 'wd_title_opposite_tick': True,
186 'wd_title_distance_center': 2.5,
187 'wd_align_func': lambda L: acos(limit_xx(10.0**((L - const_A) / const_B))) * 180.0 / pi, # phi as L
188 'wd_func': lambda L: 10.0**((L - const_A) / const_B), # x as L
189 'wd_func_inv': lambda x: const_A+const_B * log10(x), # L as x
190 'wd_tag': 'phi',
191 'mirror_y': True,
192 'mirror_x': False,
193 'width': 10.0,
194 'height': 10.0,
195 'u_scale_opposite': True,
196 'u_tag': 'AA',
197 'horizontal_guides': True,
198 'isopleth_values': [['x', 9.0, 'x']],
199 }
200block_params_scene = {'block_type': 'type_5',
201 'u_func': lambda u: u,
202 'v_func': lambda x, v: x + v,
203 'u_values': [1.0, 25.0],
204 'u_manual_axis_data': {1.0: '',
205 25.0: ''},
206 'u_tag': 'AA',
207 'wd_tag': 'EV',
208 'v_values': [-4.0, -1.0, 2.0, 5.0, 7.0, 9.0, 11.0, 13.0, 15.0],
209 'v_manual_axis_data': {-6.0: 'Person under trees',
210 -4.0: 'Inside forest',
211 -1.0: 'Person in shadow of wall',
212 2.0: 'Person at open place; alley under trees',
213 5.0: 'Buildings; street',
214 7.0: 'Landscape and front matter',
215 9.0: 'Open landscape',
216 11.0: 'Snow landscape and front matter; beach',
217 13.0: 'Snow field; open sea',
218 15.0: 'Clouds',
219 },
220 'wd_tick_levels': 0,
221 'wd_tick_text_levels': 0,
222 'wd_tick_side': 'right',
223 'wd_title': '',
224 'u_title': '',
225 'v_title': '',
226 'wd_title_opposite_tick': True,
227 'wd_title_distance_center': 2.5,
228 'mirror_x': True,
229 'horizontal_guides': True,
230 'u_align_y_offset': -0.9,
231 'isopleth_values': [['x', 2.0, 'x']],
232 }
233camera_params_1 = {'u_min': -10.0,
234 'u_max': 15.0,
235 'function': lambda u: u,
236 'title': r'',
237 'tick_levels': 0,
238 'tick_text_levels': 0,
239 'tag': 'EV',
240 }
241camera_params_2 = {'u_min': 10.0,
242 'u_max': 25600.0,
243 'function': lambda S: -(10 * log10(S) + 1.0),
244 'title': r'Film speed',
245 'manual_axis_data': {10.0: 'ISO 10',
246 20.0: 'ISO 20',
247 50.0: 'ISO 50',
248 100.0: 'ISO 100',
249 200.0: 'ISO 200',
250 400.0: 'ISO 400',
251 800.0: 'ISO 800',
252 1600.0: 'ISO 1600',
253 3200.0: 'ISO 3200',
254 6400.0: 'ISO 6400',
255 12800.0: 'ISO 12800',
256 25600.0: 'ISO 25600',
257 },
258 'scale_type': 'manual line'
259 }
260camera_params_3 = {'u_min': 0.1,
261 'u_max': 10000.0,
262 'function': lambda t: -10 * log10((1.0 / t) / (1.0 / 10.0)) - 30,
263 'manual_axis_data': {1/10.0: '10',
264 1/7.0: '7',
265 1/5.0: '5',
266 1/3.0: '3',
267 1/2.0: '2',
268 1.0: '1',
269 2.0: '1/2',
270 3.0: '1/3',
271 5.0: '1/5',
272 7.0: '1/7',
273 10.0: '1/10',
274 20.0: '1/20',
275 30.0: '1/30',
276 50.0: '1/50',
277 70.0: '1/70',
278 100.0: '1/100',
279 200.0: '1/200',
280 300.0: '1/300',
281 500.0: '1/500',
282 700.0: '1/700',
283 1000.0: '1/1000',
284 2000.0: '1/2000',
285 3000.0: '1/3000',
286 5000.0: '1/5000',
287 7000.0: '1/7000',
288 10000.0: '1/10000',
289 },
290 'scale_type': 'manual line',
291 'title': r't (s)',
292 'text_format': r"1/%3.0f s",
293 'tag': 'shutter',
294 'tick_side': 'left',
295 }
296camera_params_4 = {'u_min': 1.0,
297 'u_max': 22.0,
298 'function': lambda N: 10 * log10((N / 3.2)**2) + 30,
299 'manual_axis_data': {1.0: '$f$/1',
300 1.2: '$f$/1.2',
301 1.4: '$f$/1.4',
302 1.7: '$f$/1.7',
303 2.0: '$f$/2',
304 2.4: '$f$/2.4',
305 2.8: '$f$/2.8',
306 3.3: '$f$/3.3',
307 4.0: '$f$/4',
308 4.8: '$f$/4.8',
309 5.6: '$f$/5.6',
310 6.7: '$f$/6.7',
311 8.0: '$f$/8',
312 9.5: '$f$/9.5',
313 11.0 :'$f$/11',
314 13.0 :'$f$/13',
315 16.0 :'$f$/16',
316 19.0 :'$f$/19',
317 22.0 :'$f$/22',
318 },
319 'scale_type': 'manual line',
320 'title': r'Aperture',
321 }
322block_params_camera = {'block_type': 'type_3',
323 'width': 10.0,
324 'height': 10.0,
325 'f_params': [camera_params_1, camera_params_2, camera_params_3,
326 camera_params_4],
327 'mirror_x': True,
328 'isopleth_values': [['x', 100.0, 'x', 4.0]],
329 }
330
331
332def old_EV(EV): # C2(EV100) in wiki
333 return (-EV + 13.654) / 0.3322
334
335EV_para = {'tag': 'EV',
336 'u_min': 4.0,
337 'u_max': 19.0,
338 'function': lambda u: old_EV(u),
339 'title': r'EV$_{100}$',
340 'tick_levels': 1,
341 'tick_text_levels': 1,
342 'align_func': old_EV,
343 'title_x_shift': 0.5,
344 'tick_side': 'right',
345 }
346EV_block = {'block_type': 'type_8',
347 'f_params': EV_para,
348 'isopleth_values': [['x']],
349 }
350# maximum focal length
351FL_t_para={'u_min': 0.1,
352 'u_max': 10000.0,
353 'function': lambda t:-10 * log10((1.0 / t) / (1.0 / 10.0)) - 30,
354 'scale_type': 'linear',
355 'tick_levels': 0,
356 'tick_text_levels': 0,
357 'title': r't (s)',
358 'text_format': r"1/%3.0f s",
359 'tag': 'shutter',
360 }
361FL_factor_params_2 = {'u_min': 1.0/4.0,
362 'u_max': 3.0/2.0,
363 'function': lambda factor: -10 * log10(factor / 10.0) + 0,
364 'title': r'Sensor, IS',
365 'scale_type': 'manual point',
366 'manual_axis_data': {1.0/(2.0/3.0): 'DSLR',
367 1.0/(1.0): '35mm',
368 1.0/(8.0/3.0): 'DSLR IS',
369 1.0/(4.0): '35mm IS',
370 },
371 'tick_side':'left',
372 'text_size_manual': text.size.footnotesize, # pyx directive
373 }
374FL_fl_params = {'u_min': 20.0,
375 'u_max': 1000.0,
376 'function': lambda FL:-10 * log10(FL) + 30,
377 'title': r'Max focal length',
378 'tick_levels': 3,
379 'tick_text_levels': 2,
380 'tick_side': 'left',
381 'scale_type': 'manual line',
382 'manual_axis_data': {20.0: '20mm',
383 35.0: '35mm',
384 50.0: '50mm',
385 80.0: '80mm',
386 100.0: '100mm',
387 150.0: '150mm',
388 200.0: '200mm',
389 300.0: '300mm',
390 400.0: '400mm',
391 500.0: '500mm',
392 1000.0: '1000mm'}
393 }
394
395FL_block_params = {'block_type': 'type_1',
396 'width': 12.0,
397 'height': 10.0,
398 'f1_params': FL_t_para,
399 'f2_params': FL_factor_params_2,
400 'f3_params': FL_fl_params,
401 'mirror_x': True,
402 'proportion': 0.5,
403 'isopleth_values': [['x', 1.0/(8.0/3.0), 'x']],
404 }
405
406main_params = {'filename': ['ex_photo_exposure.pdf', 'ex_photo_exposure.eps'],
407 'paper_height': 35.0,
408 'paper_width': 35.0,
409 'block_params': [block_params, block_params_weather, block_params_scene,
410 block_params_camera, EV_block, FL_block_params],
411 'transformations': [('rotate', 0.01), ('scale paper',)],
412 'title_x': 7,
413 'title_y': 34,
414 'title_box_width': 10,
415 'title_str': r'\LARGE Photography exposure (Setala 1940) \par \copyright Leif Roschier 2009 '
416 }
417Nomographer(main_params)