Foundations

Depth of Field

The range of object distances that a focused lens images with blur no larger than an acceptable circle of confusion, its exact near and far limits under the thin-lens model, the hyperfocal distance, Levoy's approximations and how depth of field scales with f-number, distance and focal length.

intermediate

A lens focuses one distance exactly. Points nearer or farther are imaged as small blur discs, and as long as those discs stay below some acceptable size the eye, or the next stage of a vision pipeline, treats them as sharp. The range of object distances that passes this test is the depth of field (DOF). It is bounded by a near limit and a far limit, it is not centred on the focus distance, and it grows with the f-number, grows roughly with the square of the focus distance and shrinks roughly with the square of the focal length. Focused far enough away, at the hyperfocal distance, a lens keeps everything from half that distance to infinity acceptably sharp.

Depth of field matters to computer vision because it decides which parts of a scene a fixed camera can measure at full resolution, how much light an inspection set-up has to add to keep a deep part sharp, and how much defocus a depth-from-focus method has to work with. This article states the definition and its one free parameter, derives the exact limits under the thin lens model, compares them with the approximations in Marc Levoy’s Stanford CS 178 lecture slides that Torralba, Isola and Freeman’s Foundations of Computer Vision (hereafter visionbook) reproduces, and checks both with an exact computation and a rendered row of bar targets at known depths.

Definition

Put the sensor where a point at distance UU in front of the lens comes to focus. A point at another distance zz is imaged as a disc, the circle of confusion, whose diameter c(z)c(z) the thin lens model derives and image formation uses to simulate defocus. Choose a largest acceptable diameter CC on the sensor. The depth of field is the set of distances with c(z)≤Cc(z) \le C; it runs from the near limit zn<Uz_n < U to the far limit zf>Uz_f > U, and its length is Dtot=zf−znD_{\text{tot}} = z_f - z_n. Levoy (slide 55) splits it into a front part D1=U−znD_1 = U - z_n and a back part D2=zf−UD_2 = z_f - U.

The definition has one free parameter, CC, and every depth-of-field number depends on it. Levoy (slide 52) says that CC depends on the sensing and reproduction media, the viewing distance and human vision, and gives typical values: 0.02 mm on the negative for prints from 35 mm film, about 6 µm, one pixel, for a high-end SLR, and larger when the image is downsized for the web or the lens is poor. Visionbook (§6.3.1) defines the depth of field as the region around the plane of focus whose blur at the sensor stays below “some tolerance”.

Depth of field is measured in object space. Its image-side counterpart, the range of sensor positions over which a point stays acceptably sharp, is the depth of focus; Levoy’s slide 53 labels both on one diagram.

Intuition

The cone of light from a point converges behind the lens to the point’s image and diverges again after it. A sensor placed slightly before or after that apex cuts the cone in a disc. How fast the disc grows as the object moves away from the focus distance depends on how wide the cone is, which is set by the aperture: a narrow cone (large f-number) stays thin over a long stretch, so a wider range of object distances lands inside the tolerance. That is why stopping down deepens the depth of field, and why it costs light, as exposure describes.

The range is lopsided. Image distance changes quickly with object distance for near objects and slowly for far ones (the thin lens model shows that depth is compressed by the square of the magnification), so a step towards the camera moves the image farther from the sensor than an equal step away. The blur therefore grows faster in front of the focus plane than behind it, and more of the sharp range lies behind. Levoy (slide 53) puts it as “DoF is asymmetrical around the in-focus object plane”.

Moving the focus distance out has a dramatic effect. Far objects all image close to the focal plane, so once the focus is far enough away even a point at infinity falls inside the tolerance. The back limit then jumps to infinity: that focus distance is the hyperfocal distance.

Formal Definition

Conventions

Distances are in millimetres and positive when real, as in the thin lens model: the object distance z>0z > 0 in front of the lens, the image distance behind it. The focus distance is UU, Levoy’s symbol (the thin lens model writes zfz_f for it); ff is the focal length, dd the aperture diameter and N=f/dN = f/d the f-number (visionbook writes AA for the diameter). cc is a blur diameter on the sensor and CC the acceptable one. Visionbook calls the depth of field DD; Levoy calls it DTOTD_{\text{TOT}}.

Exact limits

With the sensor at the image distance of UU, the thin lens model gives the blur diameter of a point at zz as

c(z)=f2 ∣z−U∣N z (U−f).c(z) = \frac{f^2\,|z - U|}{N\,z\,(U - f)}.

Setting c(z)=Cc(z) = C on each side of UU and solving the resulting linear equations gives (our derivation; the example checks it exactly and by root finding)

zn=f2 Uf2+NC (U−f),zf=f2 Uf2−NC (U−f).z_n = \frac{f^2\,U}{f^2 + NC\,(U - f)}, \qquad z_f = \frac{f^2\,U}{f^2 - NC\,(U - f)}.

The far limit is finite only while f2>NC(U−f)f^2 > NC(U - f). As z→∞z \to \infty the blur tends to c(∞)=f2/(N(U−f))c(\infty) = f^2/(N(U - f)), and it equals CC when

U=H=f2NC+f,U = H = \frac{f^2}{NC} + f,

the exact hyperfocal distance in this model (our derivation). For U≥HU \ge H every point beyond UU is sharp. At U=HU = H the near limit is zn=f2H/(2f2)=H/2z_n = f^2 H/(2 f^2) = H/2, so focusing at the hyperfocal distance makes everything from H/2H/2 to infinity acceptably sharp.

Levoy’s formulas

Levoy (slides 53–55) derives the limits from the object-space conjugate of the circle of confusion: a disc of diameter CC on the sensor corresponds to a disc of diameter C/MTC/M_T in the focus plane, where MTM_T is the transverse magnification, and he approximates it as CU/fCU/f. Visionbook (§6.3.1), which follows Levoy’s derivation, states the condition for this step, U≫fU \gg f. Similar triangles then give

D1=NCU2f2+NCU,D2=NCU2f2−NCU,D_1 = \frac{NCU^2}{f^2 + NCU}, \qquad D_2 = \frac{NCU^2}{f^2 - NCU},

and their sum (slide 56)

DTOT=D1+D2=2NCU2f2f4−N2C2U2≈2NCU2f2,D_{\text{TOT}} = D_1 + D_2 = \frac{2NCU^2 f^2}{f^4 - N^2C^2U^2} \approx \frac{2NCU^2}{f^2},

where the N2C2U2N^2C^2U^2 term “can be ignored when conjugate of circle of confusion is small relative to the aperture” (slide 56). Visionbook prints the same exact and approximate forms of DD; its sentence before the approximation names the dropped term N2C2D2N^2C^2D^2, a typo for N2C2U2N^2C^2U^2 as on Levoy’s slide. Levoy (slide 67) defines the hyperfocal distance as the focus distance at which D2D_2 becomes infinite, H=f2/(NC)H = f^2/(NC), and shows that his D1D_1 then equals H/2H/2.

The two sets of formulas differ in one place (our derivation). In the thin-lens model the magnification at UU is ∣MT∣=f/(U−f)|M_T| = f/(U - f), so the exact conjugate is C(U−f)/fC(U - f)/f, and the exact limits are Levoy’s with NC(U−f)NC(U - f) in place of NCUNCU in the denominators:

D1exact=NC (U−f) Uf2+NC (U−f),D2exact=NC (U−f) Uf2−NC (U−f).D_1^{\text{exact}} = \frac{NC\,(U - f)\,U}{f^2 + NC\,(U - f)}, \qquad D_2^{\text{exact}} = \frac{NC\,(U - f)\,U}{f^2 - NC\,(U - f)}.

Levoy’s hyperfocal distance is therefore short of the exact one by exactly ff, and his formulas are the exact ones in the limit U≫fU \gg f. The boxed approximation drops a second term: its ratio to Levoy’s own D1+D2D_1 + D_2 is 1−(NCU/f2)2=1−(U/HL)21 - (NCU/f^2)^2 = 1 - (U/H_L)^2, writing HL=f2/(NC)H_L = f^2/(NC) (our algebra from slide 56), so it underestimates the depth of field more and more as UU approaches the hyperfocal distance.

Properties

Scaling with f-number, distance and focal length

The approximation DTOT≈2NCU2/f2D_{\text{TOT}} \approx 2NCU^2/f^2 summarizes the trade-offs. Levoy’s slides 65, 66 and 68 state them: depth of field is linear in the f-number, quadratic in the subject distance and inverse quadratic in the focal length. Visionbook (Fig. 6.12) shows the first on photographs of a ruler taken at f/2, f/4 and f/8 from the same camera position, where the region of sharp focus doubles from each f-number to the next. All three statements hold only while UU is well below the hyperfocal distance (slide 56’s condition). Near it the exact depth grows much faster than the approximation, and beyond it the depth is infinite.

Asymmetry

Since zf−U>U−znz_f - U > U - z_n whenever the far limit is finite (the denominators in the exact limits differ only in the sign of NC(U−f)NC(U - f)), more depth lies behind the focus plane than in front. For a distant focus the back part becomes much larger than the front; at the hyperfocal distance the front is H/2H/2 and the back is infinite. Close up, with U≪HU \ll H, the two parts are nearly equal.

The hyperfocal distance

Focusing at HH maximizes the sharp range for a given aperture and CC: everything from H/2H/2 to infinity. Levoy (slide 67) works an example from a photograph taken with a 27 mm lens at f/6.3, with CC set to one pixel of an HD projector, 2.5 µm × 2816/1920 on the sensor, and gets H=31.6H = 31.6 m: had he focused at 32 m, everything from 16 m to infinity would have been sharp on the projector. (Our arithmetic: C=3.67C = 3.67 µm and f2/(NC)=31.6f^2/(NC) = 31.6 m; the exact HH is 27 mm longer.)

Sensor size

At the same field of view and pixel count, a smaller sensor has a shorter focal length and smaller pixels, so ff and CC shrink by the same factor. Levoy (slide 69) concludes from the approximation that depth of field then grows linearly as the sensor shrinks, and that this is why amateur cinematographers prefer the larger chips of SLRs, which give more control over depth of field; focal length and field of view relates the focal length to the format. In our reading of the same formula, a phone camera’s deep depth of field follows from its short focal length, which is why computational photography synthesizes shallow depth of field instead.

Dolly zoom and background blur

Multiplying ff and UU by the same factor leaves U2/f2U^2/f^2, and hence the approximate depth of field, unchanged. Levoy (slide 71) uses this: zooming in and stepping back keeps the subject the same size and the depth of field the same, “but background gets blurrier”. Slide 72 adds that both lenses then have the same depth of field on the subject, while the telephoto sees and enlarges a smaller part of the background.

The choice of C

Because DTOTD_{\text{TOT}} is proportional to CC in the approximation, a factor-of-three change in the criterion, say from Levoy’s 6 µm pixel to his 0.02 mm film value, changes the depth of field by about a factor of three. Levoy’s slides 57–59 show this on three photographs: the same shot has one depth of field for the camera’s pixels and a larger effective one for a 1024-pixel projector. A depth of field quoted without its CC cannot be compared with another.

Depth of focus

On the image side, the blur c=d ∣s−sf∣/sc = d\,|s - s_f|/s at sensor distance sfs_f for an image at ss (thin lens model) stays below CC while ∣s−sf∣≤Cs/d=NCs/f|s - s_f| \le C s/d = NCs/f, so for distant objects (s≈fs \approx f) the depth of focus is about ±NC\pm NC around the image plane (our derivation). With N=8N = 8 and C=6C = 6 µm that is about ±50 µm, which sets how precisely a sensor or a focus mechanism has to be positioned.

Diffraction

Stopping down extends depth of field in the geometric model without limit, but diffraction blur grows with the f-number. Image formation gives the arithmetic (an Airy radius of 1.22λN1.22\lambda N, 5.4 µm at f/8 and 10.7 µm at f/16 for green light), and the point spread function combines diffraction with the defocus disc. In our reading, once the diffraction blur approaches CC, a smaller aperture no longer makes anything sharper. Levoy’s rule of thumb (Optics II, slide 40) is to avoid f-numbers above f/13 on a full-frame camera.

Not a convolution

A defocused image is not a sharp image blurred with one kernel. Each depth has its own disc, and at occlusion boundaries the light reaching a pixel comes partly from the foreground and partly from what it hides. Levoy (slides 76–77) notes that a wide aperture can see past a small occluder such as a chain-link fence: for a pixel focused on the subject some rays strike the occluder and some pass beside it, so the occluder lowers the subject’s contrast without blocking it. Image deblurring describes the defocus PSF and its variation with depth; the example below renders each depth as a separate layer and so leaves occlusion out.

Bokeh

Out-of-focus highlights take the shape of the aperture, because the defocus disc is a scaled copy of the aperture (thin lens model). Levoy (slide 73) uses “bokeh” for the appearance of small out-of-focus features in a photograph with shallow depth of field, determined by the boundary of the aperture.

Examples

Exact limits against the approximations, and a rendered depth ramp

The script has two parts. Part (a) computes, for a 50 mm lens with C=0.02C = 0.02 mm (Levoy’s film value) at f/2, f/4, f/8 and f/16, the exact near and far limits at focus distances of 1, 3 and 10 m and at 0.5, 0.9 and 0.99 of the exact hyperfocal distance HH. It checks the closed forms against SciPy’s brentq root finder applied to c(z)=Cc(z) = C, checks that Levoy’s slide-56 form equals his D1+D2D_1 + D_2, and reports the relative errors of his D1D_1, D2D_2 and approximate DTOTD_{\text{TOT}} against the exact values. It then focuses at the exact HH and at Levoy’s HL=f2/(NC)H_L = f^2/(NC).

Part (b) renders a receding row of bar targets. The lens is focused at U=2U = 2 m, the pixel pitch is 2.5 µm (so CC is 8 pixels), and 601 bar patches are placed at depths from 1500 to 2700 mm in 2 mm steps. Each patch is a periodic row of bars, 6 pixels white and 6 black, blurred by the uniform disc of its own exact circle of confusion. Because the bars vary only along xx, convolving the patch with the disc is the same as convolving one row with the disc’s projection (its chord length), which the code integrates exactly over each pixel. Each patch is blurred on its own: the rendering is layered, with no occlusion between depths, which is the simplification that Levoy’s “not a convolution” slides warn about. There is no noise and no randomness.

A patch counts as sharp when its Michelson contrast, (max⁡−min⁡)/(max⁡+min⁡)(\max - \min)/(\max + \min), is at least the contrast TT of the same bars blurred by a disc of diameter exactly CC. The criterion and the blur therefore share one definition. Because a disc’s transfer function changes sign (point spread function), strongly defocused bars can regain some contrast, so the script checks on a 0.01-pixel grid of disc diameters up to 90 pixels that contrast is at least TT exactly when c≤Cc \le C (check C3). The rendered near and far limits are the nearest and farthest sharp patches.

The checks and hypotheses, each with its bar, are:

  • C0: closed form against brentq, relative difference ≤ 10−910^{-9};
  • C1: Levoy’s slide-56 DTOTD_{\text{TOT}} equals his D1+D2D_1 + D_2 within 10−1210^{-12};
  • H1a: wherever U/H≤0.2U/H \le 0.2, the approximate DTOTD_{\text{TOT}} is within 6% of exact;
  • H1b: at U=0.5H,0.9H,0.99HU = 0.5H, 0.9H, 0.99H its error grows and reaches at least 50% at 0.9H0.9H;
  • H4a: the back depth exceeds the front depth in every finite row;
  • H2a: at the exact HH, c(∞)=Cc(\infty) = C and zn=H/2z_n = H/2, within 10−1210^{-12};
  • H2b: at Levoy’s HLH_L the far limit is finite and equals HL2/fH_L^2/f;
  • C2 (data checks): every disc kernel sums to 1, the bar row has mean 0.5, and the unblurred contrast is 1, within 10−1210^{-12};
  • C3: contrast ≥ TT exactly when c≤Cc \le C;
  • H3a: the rendered limits equal the exact limits rounded inwards to the 2 mm grid, and the sharp patches are contiguous;
  • H3b: the rendered range doubles from f/2 to f/4 and from f/4 to f/8, within ±0.05;
  • H4b: the rendered back depth exceeds the front depth.
import os
os.environ["OPENBLAS_NUM_THREADS"] = os.environ["OMP_NUM_THREADS"] = "1"
import numpy as np
from scipy.optimize import brentq

# Units: mm. Thin lens, distances positive when real. The sensor is focused for the
# object distance U; a point at distance z then blurs into a disc of diameter c(z).
f, C = 50.0, 0.02                              # focal length, acceptable CoC diameter
coc = lambda z, U, N: f**2 * abs(z - U) / (N * z * (U - f))

def limits(U, N):
    """Exact near and far limits, where c(z) = C (far = inf beyond the hyperfocal)."""
    a = N * C * (U - f)
    far = f**2 * U / (f**2 - a) if f**2 > a else np.inf
    return f**2 * U / (f**2 + a), far

def levoy(U, N):
    """Levoy's D1, D2 (slide 55), his D_tot (slide 56) and its approximation."""
    k = N * C * U**2
    D1 = k / (f**2 + N * C * U)
    D2 = k / (f**2 - N * C * U) if f**2 > N * C * U else np.nan
    D_full = 2 * k * f**2 / (f**4 - (N * C * U)**2)
    return D1, D2, D_full, 2 * k / f**2

held = lambda ok: "held" if ok else "failed"
print("(a) f = 50 mm, C = 0.02 mm; relative errors are Levoy / exact - 1")
print("N   U (mm)       U/H     near (mm)    far (mm)       D_tot exact   D_tot approx  "
      "err D_tot   err D1    err D2    root diff")
root_max = alg_max = 0.0
h1a, h1b_rows, h4a = [], {}, []
for N in (2, 4, 8, 16):
    H = f**2 / (N * C) + f                     # exact hyperfocal distance
    for q, U in ((None, 1000.0), (None, 3000.0), (None, 10000.0),
                 (0.5, 0.5 * H), (0.9, 0.9 * H), (0.99, 0.99 * H)):
        near, far = limits(U, N)
        g = lambda z: coc(z, U, N) - C
        rn = brentq(g, f + 1e-6, U, xtol=1e-300, rtol=4 * np.finfo(float).eps, maxiter=500)
        D1, D2, D_full, D_ap = levoy(U, N)
        if np.isinf(far):
            print(f"{N:<3} {U:<12.3f} {U / H:<7.4f} {near:<12.4f} inf            inf           "
                  f"{D_ap:<13.4f} -")
            root_max = max(root_max, abs(rn / near - 1))
            continue
        rf = brentq(g, U, 1e12, xtol=1e-300, rtol=4 * np.finfo(float).eps, maxiter=500)
        root_max = max(root_max, abs(rn / near - 1), abs(rf / far - 1))
        alg_max = max(alg_max, abs(D_full / (D1 + D2) - 1))
        e_tot, e1, e2 = D_ap / (far - near) - 1, D1 / (U - near) - 1, D2 / (far - U) - 1
        print(f"{N:<3} {U:<12.3f} {U / H:<7.4f} {near:<12.4f} {far:<14.4f} {far - near:<13.4f} "
              f"{D_ap:<13.4f} {100 * e_tot:+9.4f}% {100 * e1:+8.4f}% {100 * e2:+8.4f}% {abs(rf / far - 1):.1e}")
        if U / H <= 0.2:
            h1a.append(abs(e_tot))
        if q is not None:
            h1b_rows.setdefault(N, []).append(abs(e_tot))
        h4a.append(far - U > U - near)
print(f"C0 closed form vs brentq, max rel diff {root_max:.1e} <= 1e-9: {held(root_max <= 1e-9)}")
print(f"C1 Levoy slide 56 D_tot = D1 + D2, max rel diff {alg_max:.1e} <= 1e-12: {held(alg_max <= 1e-12)}")
print(f"H1a U/H <= 0.2: max |err D_tot| {100 * max(h1a):.4f}% <= 6% over {len(h1a)} rows: {held(max(h1a) <= 0.06)}")
ok = all(len(r) == 3 and r[0] < r[1] < r[2] and r[1] >= 0.5 for r in h1b_rows.values())
print("H1b |err D_tot| at q = 0.5, 0.9, 0.99: " + "; ".join(
    f"N={N} ({len(r)} rows) " + ", ".join(f"{100 * e:.4f}%" for e in r) for N, r in h1b_rows.items())
    + f": increasing and >= 50% at 0.9: {held(ok)}")
print(f"H4a back depth > front depth in all {len(h4a)} finite rows: {held(all(h4a))}")

print("\nHyperfocal focus")
h2a = h2b = True
for N in (2, 4, 8, 16):
    HL = f**2 / (N * C); H = HL + f
    c_inf = f**2 / (N * (H - f))               # blur of a point at infinity
    near_H = limits(H, N)[0]
    nL, fL = limits(HL, N)
    rf = brentq(lambda z: coc(z, HL, N) - C, HL, 1e12, xtol=1e-300, rtol=4 * np.finfo(float).eps, maxiter=500)
    e_a = max(abs(c_inf / C - 1), abs(near_H / (H / 2) - 1))
    e_b = max(abs(fL / (HL**2 / f) - 1), abs(rf / fL - 1))
    h2a &= e_a <= 1e-12; h2b &= np.isfinite(fL) and e_b <= 1e-9
    print(f"N={N:<3} exact H {H:9.1f}: c(inf)/C {c_inf / C:.12f}, near/(H/2) {near_H / (H / 2):.12f}  |  "
          f"Levoy H {HL:9.1f}: far {fL:14.3f} = H^2/f {HL**2 / f:14.3f} (rel {e_b:.1e}), "
          f"near {nL:.4f} vs H/2 {HL / 2:.4f}")
print(f"H2a far limit infinite and near = H/2 at the exact H (<= 1e-12): {held(h2a)}")
print(f"H2b far limit finite and = H_Levoy^2/f at Levoy's H (<= 1e-9): {held(h2b)}")

# (b) Rendered depth ramp: bar patches at known depths, each blurred by its own disc
U, pitch, W, P = 2000.0, 0.0025, 192, 12       # focus (mm), pixel pitch (mm), row length, bar period (px)
row = (np.arange(W) % P < P // 2).astype(float)            # 6 px white, 6 px black

def disc_kernel(c):
    """Uniform disc of diameter c px projected onto x (bars vary only in x) and
    integrated exactly over each pixel; circular, centred at index 0."""
    x = np.arange(W) - W // 2
    r = c / 2
    if r <= 0:
        k = (x == 0).astype(float)
    else:
        def F(t):                                            # integral of the chord length
            t = np.clip(t, -r, r)
            return r * r * (np.arcsin(t / r) + np.pi / 2) + t * np.sqrt(r * r - t * t)
        k = (F(x + 0.5) - F(x - 0.5)) / (np.pi * r * r)
    return np.roll(k, -(W // 2))

def contrast(c):
    b = np.real(np.fft.ifft(np.fft.fft(row) * np.fft.fft(disc_kernel(c))))
    return (b.max() - b.min()) / (b.max() + b.min())

T = contrast(C / pitch)                                     # the C-sized disc's own contrast
cs = np.round(np.arange(0, 9001) * 0.01, 2)
con = np.array([contrast(c) for c in cs])
ksum = max(abs(disc_kernel(c).sum() - 1) for c in (0.0, 0.5, 8.0, 85.5))
print(f"\n(b) U = {U:.0f} mm, pixel {pitch * 1e3:.1f} um, C = {C / pitch:.0f} px, bars {P // 2}/{P // 2} px")
print(f"C2 kernel sums |sum-1| max {ksum:.1e} <= 1e-12, row mean {row.mean()}, contrast(0) {contrast(0.0)}: "
      f"{held(ksum <= 1e-12 and row.mean() == 0.5 and abs(contrast(0.0) - 1) <= 1e-12)}")
lo, hi = con[cs <= 8].min(), con[cs > 8].max()
print(f"C3 threshold T {T:.6f}; min contrast c <= 8 px {lo:.6f}; max contrast 8 < c <= 90 px {hi:.6f}: "
      f"{held(lo >= T and hi < T)}")

zs = np.arange(1500, 2701, 2)
print("N   near grid (exact)      far grid (exact)       range  front  back  contiguous")
rng = {}
h3a = h4b = True
for N in (2, 4, 8):
    sharp = np.array([contrast(coc(z, U, N) / pitch) >= T for z in zs])
    idx = np.flatnonzero(sharp)
    zn, zf = zs[idx[0]], zs[idx[-1]]
    en, ef = limits(U, N)
    gn, gf = zs[zs >= en].min(), zs[zs <= ef].max()
    contig = idx[-1] - idx[0] + 1 == len(idx)
    rng[N] = zf - zn
    h3a &= contig and zn == gn and zf == gf
    h4b &= (zf - U) > (U - zn)
    print(f"{N:<3} {zn} ({en:.4f})      {zf} ({ef:.4f})      {zf - zn:<6} {U - zn:<6.0f} {zf - U:<5.0f} {contig}")
print(f"H3a rendered limits = analytic grid limits (within one 2 mm step), contiguous: {held(h3a)}")
r42, r84 = rng[4] / rng[2], rng[8] / rng[4]
print(f"H3b range ratios f/4:f/2 {r42:.4f}, f/8:f/4 {r84:.4f}, within 2 +- 0.05: "
      f"{held(abs(r42 - 2) <= 0.05 and abs(r84 - 2) <= 0.05)}")
print(f"H4b rendered back > front: {held(h4b)}")

Output (NumPy 2.3.5, SciPy 1.16.3; the script runs in under a second):

(a) f = 50 mm, C = 0.02 mm; relative errors are Levoy / exact - 1
N   U (mm)       U/H     near (mm)    far (mm)       D_tot exact   D_tot approx  err D_tot   err D1    err D2    root diff
2   1000.000     0.0160  985.0276     1015.4346      30.4070       32.0000         +5.2388%  +5.1803%  +5.3487% 0.0e+00
2   3000.000     0.0480  2864.7823    3148.6146      283.8323      288.0000        +1.4684%  +1.6173%  +1.7804% 0.0e+00
2   10000.000    0.1599  8626.6391    11893.4348     3266.7958     3200.0000       -2.0447%  +0.4332%  +0.5982% 0.0e+00
2   31275.000    0.5000  20855.5615   62500.0000     41644.4385    31300.0200     -24.8399%  +0.1067%  +0.3205% 2.2e-16
2   56295.000    0.9000  29630.1950   562500.0000    532869.8050   101412.0648    -80.9687%  +0.0468%  +0.8954% 1.0e-15
2   61924.500    0.9900  31117.9643   6187500.0000   6156382.0357  122708.5984    -98.0068%  +0.0406%  +8.7759% 6.9e-15
4   1000.000     0.0319  970.4969     1031.3531      60.8562       64.0000         +5.1659%  +5.1000%  +5.4371% 2.2e-16
4   3000.000     0.0958  2741.2281    3312.7208      571.4928      576.0000        +0.7887%  +1.5465%  +1.8749% 1.1e-16
4   10000.000    0.3195  7584.9515    14671.3615     7086.4100     6400.0000       -9.6863%  +0.3807%  +0.7390% 0.0e+00
4   15650.000    0.5000  10438.9007   31250.0000     20811.0993    15675.0400     -24.6794%  +0.2136%  +0.6421% 0.0e+00
4   28170.000    0.9000  14827.5644   281250.0000    266422.4356   50787.1296     -80.9374%  +0.0935%  +1.8041% 1.3e-15
4   30987.000    0.9900  15571.4820   3093750.0000   3078178.5180  61452.4268     -98.0036%  +0.0812% +19.2038% 6.0e-15
8   1000.000     0.0638  942.6848     1064.7359      122.0512      128.0000        +4.8740%  +4.9466%  +5.6230% 2.2e-16
8   3000.000     0.1914  2523.5532    3698.2249      1174.6717     1152.0000       -1.9300%  +1.4219%  +2.0977% 0.0e+00
8   10000.000    0.6380  6109.4819    27533.0396     21423.5577    12800.0000     -40.2527%  +0.3064%  +1.3959% 0.0e+00
8   7837.500     0.5000  5230.5793    15625.0000     10394.4207    7862.5800      -24.3577%  +0.4276%  +1.2882% 2.2e-16
8   14107.500    0.9000  7426.2507    140625.0000    133198.7493   25474.7592     -80.8746%  +0.1869%  +3.6623% 6.7e-16
8   15518.250    0.9900  7798.2410    1546875.0000   1539076.7590  30824.4586     -97.9972%  +0.1622% +47.3131% 5.3e-15
16  1000.000     0.1272  891.5835     1138.4335      246.8501      256.0000        +3.7067%  +4.6659%  +6.0357% 0.0e+00
16  3000.000     0.3816  2177.7003    4820.0514      2642.3511     2304.0000      -12.8049%  +1.2246%  +2.7515% 0.0e+00
16  10000.000    1.2719  4398.3110    inf            inf           25600.0000    -
16  3931.250     0.5000  2626.4364    7812.5000      5186.0636     3956.4100      -23.7107%  +0.8570%  +2.5931% 1.1e-16
16  7076.250     0.9000  3725.5970    70312.5000     66586.9030    12818.7684     -80.7488%  +0.3734%  +7.5511% 0.0e+00
16  7783.875     0.9900  3911.6208    773437.5000    769525.8792   15510.7098     -97.9844%  +0.3238% +176.4483% 7.8e-16
C0 closed form vs brentq, max rel diff 6.9e-15 <= 1e-9: held
C1 Levoy slide 56 D_tot = D1 + D2, max rel diff 8.4e-15 <= 1e-12: held
H1a U/H <= 0.2: max |err D_tot| 5.2388% <= 6% over 8 rows: held
H1b |err D_tot| at q = 0.5, 0.9, 0.99: N=2 (3 rows) 24.8399%, 80.9687%, 98.0068%; N=4 (3 rows) 24.6794%, 80.9374%, 98.0036%; N=8 (3 rows) 24.3577%, 80.8746%, 97.9972%; N=16 (3 rows) 23.7107%, 80.7488%, 97.9844%: increasing and >= 50% at 0.9: held
H4a back depth > front depth in all 23 finite rows: held

Hyperfocal focus
N=2   exact H   62550.0: c(inf)/C 1.000000000000, near/(H/2) 1.000000000000  |  Levoy H   62500.0: far   78125000.000 = H^2/f   78125000.000 (rel 3.0e-14), near 31262.5050 vs H/2 31250.0000
N=4   exact H   31300.0: c(inf)/C 1.000000000000, near/(H/2) 1.000000000000  |  Levoy H   31250.0: far   19531250.000 = H^2/f   19531250.000 (rel 4.9e-14), near 15637.5100 vs H/2 15625.0000
N=8   exact H   15675.0: c(inf)/C 1.000000000000, near/(H/2) 1.000000000000  |  Levoy H   15625.0: far    4882812.500 = H^2/f    4882812.500 (rel 2.9e-14), near 7825.0200 vs H/2 7812.5000
N=16  exact H    7862.5: c(inf)/C 1.000000000000, near/(H/2) 1.000000000000  |  Levoy H    7812.5: far    1220703.125 = H^2/f    1220703.125 (rel 1.5e-14), near 3918.7901 vs H/2 3906.2500
H2a far limit infinite and near = H/2 at the exact H (<= 1e-12): held
H2b far limit finite and = H_Levoy^2/f at Levoy's H (<= 1e-9): held

(b) U = 2000 mm, pixel 2.5 um, C = 8 px, bars 6/6 px
C2 kernel sums |sum-1| max 0.0e+00 <= 1e-12, row mean 0.5, contrast(0) 1.0000000000000002: held
C3 threshold T 0.688357; min contrast c <= 8 px 0.688357; max contrast 8 < c <= 90 px 0.686907: held
N   near grid (exact)      far grid (exact)       range  front  back  contiguous
2   1940 (1939.4880)      2064 (2064.4096)      124    60     64    True
4   1884 (1882.5301)      2132 (2133.1058)      248    116    132   True
8   1780 (1778.0939)      2284 (2285.1920)      504    220    284   True
H3a rendered limits = analytic grid limits (within one 2 mm step), contiguous: held
H3b range ratios f/4:f/2 2.0000, f/8:f/4 2.0323, within 2 +- 0.05: held
H4b rendered back > front: held

Every check and hypothesis held in this run. The root-finding check C0 held: the closed-form limits match brentq’s roots to 6.9×10−156.9 \times 10^{-15}, so they are the exact solutions of c(z)=Cc(z) = C. Check C1 held: Levoy’s slide-56 form is his D1+D2D_1 + D_2 to 8.4×10−158.4 \times 10^{-15}.

Hypothesis H1a held. In the eight rows with U/H≤0.2U/H \le 0.2 the largest error of the approximate DTOTD_{\text{TOT}} is +5.2388%, at f/2 and 1 m. The 1 m rows are where the approximation does worst at small U/HU/H: +5.2388%, +5.1659%, +4.8740% and +3.7067% at f/2 to f/16, with Levoy’s D1D_1 also about 5% long in each of these rows. In our reading this is mainly the U≫fU \gg f step: Levoy’s conjugate CU/fCU/f exceeds the exact C(U−f)/fC(U - f)/f by the factor U/(U−f)U/(U - f), which is 5.26% at 1 m (our arithmetic). The dropped N2C2U2N^2C^2U^2 term pulls the other way, by the factor 1−(U/HL)21 - (U/H_L)^2, which at 1 m is 0.03% at f/2 and 1.6% at f/16 with the printed HLH_L (our arithmetic), so the 1 m error falls as the f-number grows. At 10 m and f/2 the signs split, with D1D_1 and D2D_2 long by +0.4332% and +0.5982% but the approximate total short by 2.0447%; there the dropped term’s factor 1−(U/HL)21 - (U/H_L)^2 is 1−0.1621 - 0.16^2, about 2.6% short (our arithmetic), and it outweighs the conjugate error.

Hypothesis H1b held: the error of the approximation grows towards the hyperfocal distance, from about 24% at 0.5H0.5H through about 81% at 0.9H0.9H to about 98% at 0.99H0.99H, three rows per f-number. The percentages are nearly the same at every f-number for a given fraction of HH, which follows from the factor 1−(U/HL)21 - (U/H_L)^2 when U≫fU \gg f (our reading). Levoy’s D2D_2 is within 8% of exact in every row short of 0.99H0.99H, but at 0.99H0.99H it is 8.7759% long at f/2 and 176.4483% long at f/16. In our reading this is because his D2D_2 diverges at his HLH_L, which lies ff short of the exact HH; at f/16 the printed hyperfocal distances are 7812.5 and 7862.5 mm, so 0.99H0.99H (7783.875 mm) is within 30 mm of HLH_L. One row, f/16 at 10 m (U/H=1.2719U/H = 1.2719), is beyond the hyperfocal distance: the exact depth of field is infinite, while the approximation gives a finite 25.6 m.

Hypothesis H4a held: the back depth exceeds the front in all 23 rows with a finite far limit.

Both hyperfocal hypotheses held. H2a: at the exact HH the blur of a point at infinity is exactly CC and the near limit is exactly H/2H/2, both printed as 1.000000000000. H2b: focused at Levoy’s HLH_L, the far limit is finite and equals HL2/fH_L^2/f, from 78,125 m at f/2 down to 1,220.7 m at f/16. For practical purposes that is still infinity, since HL2/f=HL⋅HL/fH_L^2/f = H_L \cdot H_L/f is a large multiple of HLH_L. The printed near limit at HLH_L is slightly beyond HL/2H_L/2: 31262.5050 mm against 31250 mm at f/2 (0.04% farther), and 3918.7901 mm against 3906.25 mm at f/16 (0.32%).

In part (b), the data checks C2 held. The unblurred contrast prints as 1.0000000000000002, within the 10−1210^{-12} tolerance; the last digit is floating-point round-off in the FFT. The validity check C3 held: the threshold contrast of the bars under the 8-pixel disc is 0.688357, every smaller disc gives at least that, and every larger disc up to 90 pixels gives at most 0.686907. The largest disc in the render, at f/2 and 1500 mm, has a diameter of 0.2137 mm, 85.5 pixels, by the blur formula above (our arithmetic), so the checked grid covers every patch. Contrast is thus a faithful stand-in for blur diameter on these bars.

Hypothesis H3a held. At each f-number the rendered limits are the exact limits rounded inwards to the 2 mm grid (1940 and 2064 mm at f/2, against 1939.4880 and 2064.4096 mm), and the sharp patches form one contiguous run. Hypothesis H3b held: the rendered range is 124, 248 and 504 mm, so the ratios are 2.0000 and 2.0323. The exact ranges from the printed limits are 124.9216, 250.5757 and 507.0981 mm, each a little more than double the previous one, which the 2 mm grid rounds. This is the doubling that visionbook’s ruler photographs show, here on a scene with known depths. Hypothesis H4b held: 60 mm in front against 64 mm behind at f/2, 116 against 132 at f/4 and 220 against 284 at f/8, so the asymmetry grows as the range gets longer.

Process note. The example illustrates exact relations rather than testing an open question. The PLAN records that the bars of H1a, H1b and H3b were set after a planning script had printed the values they bound: “All bars written after reachable.py printed the values: example presented as an illustration.” It listed nine departures from the batch plan’s entry for this example, before the pilot:

  1. C=0.02C = 0.02 mm, Levoy’s film value, where the batch plan asked for “a stated C”;
  2. focus distances of 1, 3 and 10 m and 0.5, 0.9 and 0.99 of HH;
  3. D1D_1 and D2D_2 each compared with the exact values, and the added check C1;
  4. numeric bars for “a few percent”, “diverges” and “doubles”;
  5. the near limit at Levoy’s HLH_L printed against HL/2H_L/2;
  6. the disc blur computed exactly through its projection on a periodic, noise-free row;
  7. the added checks C2 and C3;
  8. the render’s parameters, with the asymmetry checked both exactly and on the render;
  9. SciPy’s root finder as the only library cross-check, since no image library has a depth-of-field routine.

The first pilot found two faults in the code.

  • H1b row selection. H1b selected its rows by a threshold, U>0.4HU > 0.4H, instead of by the planned fractions of HH. This brought in the f/8 row at 10 m, and the check failed.
  • C2 float comparison. C2 compared the unblurred contrast with 1 for floating-point equality, and that check failed.

With the editor’s approval both were repaired before this run. H1b now selects its rows by their fraction of HH and prints the number of rows per f-number, which must be 3. C2 now uses a tolerance of 10−1210^{-12}. Between the two pilots, H1b and C2 changed from failed to held; every other printed line is identical. This run is the repeated pilot.

Common Misconceptions

  • “Depth of field is centred on the focus distance.” More of it lies behind: 64 against 60 mm at f/2 and 284 against 220 mm at f/8 in the example. At the hyperfocal distance the back part is infinite.
  • “Depth of field is a property of the lens.” It depends on the acceptable blur CC, which is a choice that depends on the sensor, the output and the viewer (Levoy, slide 52). The same exposure has different depths of field for different CC.
  • “Depth of field is proportional to the f-number.” Only in the approximation, and only well short of the hyperfocal distance. Near HH the exact depth grows much faster than linearly, and beyond it the depth is infinite.
  • “Focus at infinity for maximum sharpness of a landscape.” With the sensor focused at infinity the near limit is at HL=f2/(NC)H_L = f^2/(NC) (our derivation, the limit of znz_n as U→∞U \to \infty); focusing at HH brings it in to H/2H/2 while keeping infinity sharp.
  • “Levoy’s H=f2/(NC)H = f^2/(NC) and f2/(NC)+ff^2/(NC) + f are the same.” They differ by ff. The first comes from the U≫fU \gg f approximation and leaves a finite (if distant) far limit, as the example prints; the second is exact in the thin-lens model.
  • “Stopping down always makes more of the image sharp.” Past the point where diffraction blur approaches CC, the whole image gets softer (image formation).
  • “Synthetic background blur is depth of field.” Phone portrait modes blur an image by an estimated depth map, deliberately not physically correct defocus (computational photography). In our reading, optical depth of field also mixes foreground and background at occlusions, which a blur applied per pixel does not.
  • “A depth-of-field routine computes the exact limits.” ISETCam’s opticsDoF.m describes its calculation as finding two points on either side of the focus distance with equal circles of confusion, but its code returns the approximation 2NCU2/f22NCU^2/f^2, with a comment attributing the formula to Wikipedia. Results from it inherit the approximation’s error near the hyperfocal distance.

Where It Is Used

  • Camera set-up. Exposure treats depth of field as one of the costs of the aperture: one stop smaller shrinks the defocus blur by 2\sqrt{2} and halves the light, so a set-up that needs both a deep sharp range and short exposures has to add light. Vision system design lists the aperture’s effects on light, depth of field and diffraction among the optical choices. The digital camera article notes that the aperture sets both the light and the depth of field.
  • Dataset capture. Tanks and Temples records that its capture lens was usually focused at the hyperfocal distance.
  • Range from focus. Active perception describes Krotkov’s work, which modelled the depth of field to predict the precision of range from focus and opened the aperture to make it shallow. Depth from focus and depth from defocus, mentioned in depth estimation, turn the dependence of blur on depth into a measurement, so a shallow depth of field is what they need.
  • Microscopy. At high magnification the depth of field is shallower than a tissue section is thick. Whole-slide imaging describes focus maps and extended depth of field, which combines images from several focal planes.
  • Deblurring and synthesis. Image deblurring inverts defocus outside the depth of field, and computational photography synthesizes shallow depth of field and records light fields that can be refocused after capture.

Related

  • Exposure

    How much light a camera records in one frame, set by shutter time, aperture and gain; how exposure is counted in stops, metered and controlled automatically, and what each setting costs a vision system in motion blur, noise, depth of field and clipping.

  • Point Spread Function

    The image a camera forms of a single point of light, how diffraction, defocus, aberrations and the pixel aperture combine into it, its Fourier transform the optical transfer function and its magnitude the MTF, and how the MTF is measured from a slanted edge.

  • Image Deblurring

    Recovering a sharp image from a blurred one, covering the convolution model and point-spread function, inverse, Wiener and Tikhonov filtering, Landweber and Richardson–Lucy iterations, regularization and stopping, boundary handling and ringing, blind deconvolution, motion and defocus blur, and learned deblurring in outline.

  • Computational Photography

    Making photographs by combining capture and computation, from burst align-and-merge, HDR, multi-frame denoising and super-resolution to synthetic depth of field, panoramas, flash/no-flash pairs, light-field cameras and coded apertures and exposures; how these reached phones, and why phone images break the single-exposure assumptions of vision methods.

  • Active Perception

    The idea that a perceiving agent should decide why, what, how, when and where to sense, from Bajcsy's 1988 definition and the active vision of the late 1980s to next-best-view planning by information gain, active recognition and object search, learned exploration policies and active perception in embodied AI.

  • Designing a Computer Vision System

    How a working vision system is designed from its requirements, through the choice of sensor, optics, lighting, compute and model, to the data it is trained on, its validation in the field and its maintenance once deployed, with the requirement arithmetic for a measurement station worked through in code.

References

  1. Levoy, M. (2013). Optics I: Lenses and Apertures. Lecture slides, CS 178: Digital Photography, Stanford University, Spring 2013 (cited by slide number).
  2. Levoy, M. (2013). Optics II: Practical Photographic Lenses. Lecture slides, CS 178: Digital Photography, Stanford University, Spring 2013 (cited by slide number).
  3. Torralba, A., Isola, P. & Freeman, W. T. (2024). Foundations of Computer Vision. MIT Press. Chapter 6, Lenses, §6.3.1 Depth of Field (read in the online edition).
  4. ISETCam (Stanford Center for Image Systems Engineering). opticsDoF.m, at commit 3e0253421752eb72f2d1353327dffd0d433abdaf (MIT licence; code and header comments read).