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

(1)\[FS-L-A-W+C+T=0 \,\]

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

\begin{eqnarray*} D=0.006918-0.399912\cos(\gamma)+0.070257\sin(\gamma)-0.006758\cos(2\gamma) \\ +0.000907\sin(2\gamma)-0.002697\cos(3\gamma)+0.00148\sin(3\gamma) \end{eqnarray*}

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).\,\)

1

Vilho Setälä: “Valokuvaus”, Otava 1940.

2

https://www.esrl.noaa.gov/gmd/grad/solcalc/solareqns.PDF

Construction of the nomograph

The presented equation is the following:

\begin{eqnarray*} FS - \{0.33766 - 13.656 \log_{10}[ \sin(LAT)\sin(D(\gamma(day)))+\cos(LAT)\cos(D(\gamma(day)))\cos(ha(h))]\}\\ - A - W + C + T = 0. \end{eqnarray*}

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)

\[x_1 \equiv \cos(\phi)=\sin(LAT)sin(D(\gamma(day)))+\cos(LAT)\cos(D(\gamma(day)))\cos(ha(h))\,\]

formed into determinant:

\[\begin{split}\begin{vmatrix} 0 & \cos(\phi) & 1\\ \frac{\cos(LAT)\cos(D(\gamma(day)))}{1+(\cos(LAT)\cos(D(\gamma(day))))} & \frac{\sin(LAT)\sin(D(\gamma(day)))}{1+(\cos(LAT)\cos(D(\gamma(day))))} &1\\ 1 & -\cos(ha(h)) &1 \\ \end{vmatrix} =0.\end{split}\]

Block 2 (Type 5)

\[C_1 \equiv L+W = 0.006918-13.656 \log_{10}(x_1)+W \,\]

split into two equations for contour construction:

\[y_1 = C_1 \,\]
\[y_1 = 0.006918-13.656 \log_{10}(x_1)+W \,\]

Block 3 (Type 5)

\[C_2 \equiv L+W+C = C_1+C \,\]

split into two equations for contour construction:

\[y_2 = C_2 \,\]
\[y_2 = C_1+C \,\]

Block 4 (Type 3)

\[C_2 = FS-A+T \,\]

equals

\[C_2 -(10 \log_{10}(S)+1.0)+10 \log_{10}\left(\frac{N^2}{3.2^2} \right)-10 \log_{10}\left( \frac{1/t_i}{1/10}\right)=0, \,\]

where

\[t_i\equiv 1/t \,\]

is inverse shutter time.

Block 5 (Type 8)

Additional EV100 scale by using relation

\[C_2 =(-EV_{100}+13.654)/0.3322 \,\]

Block 6 (Type 1)

Maximum focal length calculator according to equation

\[t_i / f = FL\]

written as

\[-10 \log_{10}\left( \frac{1/t_i}{1/10}\right)-10 \log_{10}\left( \frac{f}{10} \right) -10 \log_{10}\left( FL \right)=0\]

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

../_images/ex_photo_exposure.png

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)