Foundations

Dynamic Range

The ratio between the largest and the smallest signal a sensor, scene, file or display can handle, how EMVA 1288, Levoy's course notes and ISETCam define it for a camera, how to express it in ratios, decibels, stops and bits, and why bit depth, gain and exposure change what a capture keeps.

intermediate

Dynamic range is the ratio between the largest and the smallest value that a part of an imaging system can handle. For a camera sensor it is the ratio between the signal at which the pixel saturates and the smallest signal that can still be told apart from noise. The same word is used for a scene, for the brightest and darkest radiance that matter in it, for a stored file and for a display, and the four numbers are different quantities. Dynamic range is abbreviated DR and quoted as a plain ratio, in decibels, in stops or in “bits”, and the units, like the definitions behind them, change the numbers.

The sensor’s noise sources, its saturation capacity and the EMVA 1288 measurement of dynamic range by the photon transfer method are described in image sensor; choosing shutter time, aperture and gain for a scene, and the clipping that follows a wrong choice, are covered under exposure. Combining several exposures to go beyond one frame belongs to high dynamic range imaging, and compressing the result for a display to tone mapping. This article covers the quantity itself: its definitions, its units, what changes it, and what it does not say.

Definition

The EMVA 1288 standard, which defines how camera datasheets report sensor parameters, defines the dynamic range as the ratio of the saturation exposure to the absolute sensitivity threshold, the mean number of photons that gives a signal-to-noise ratio of 1 (EMVA 1288, Section 2.7, Eq. 28). Both ends are defined by statistics, not by the pixel’s physical limits: saturation is set where clipping of the digital values is still minor, and the threshold is where the mean signal equals its own noise.

Other sources use other ends. Levoy’s Stanford lecture slides define dynamic range as the “max output swing” over the “noise in the dark”, computed from the full-well capacity and the dark noise (Levoy, Noise and ISO, slide 23). Simulation code such as ISETCam, from Stanford’s Center for Image Systems Engineering, computes it from the pixel’s voltage swing and its noise (ISETCam, pixelDR.m, sensorDR.m). All three are ratios of a largest to a smallest usable signal, and none of them gives the same number for the same sensor, as the first example shows.

Intuition

Think of a pixel as a bucket that collects drops of rain and a ruler that measures the water. The largest reading is the brim: past it the bucket overflows and every heavier rain reads the same. The smallest useful reading is set by how much the level jitters when no rain falls, from the measuring error and from the stray drops that fall anyway: a level that does not rise above that jitter cannot be told from an empty bucket. The dynamic range is how many times the jitter fits between the bottom and the brim.

Three things follow from the picture. A bigger bucket or a steadier ruler gives a larger range, so the range is a property of the sensor and its readout, not of the scene. A ruler with coarse marks adds its own error, so the converter that turns charge into numbers can shorten the range. And leaving the bucket out longer does not change the range: it moves the window of rain intensities that land between the jitter and the brim, which is what exposure chooses.

Formal Definition

EMVA 1288

EMVA 1288 models a linear camera by mean values and variances, in photons μp\mu_p, electrons μe=ημp\mu_e = \eta \mu_p with quantum efficiency η\eta, and digital numbers (DN) through the overall system gain KK in DN per electron; the image sensor article sets out the model. The signal-to-noise ratio of the mean signal above the dark level is (EMVA 1288, Eqs. 20–21)

SNR(μp)=μy−μy.darkσy=ημpσd2+σq2/K2+ημp,\mathrm{SNR}(\mu_p) = \frac{\mu_y - \mu_{y.\mathrm{dark}}}{\sigma_y} = \frac{\eta \mu_p}{\sqrt{\sigma_d^2 + \sigma_q^2 / K^2 + \eta \mu_p}},

where σd\sigma_d is the temporal dark noise in electrons and σq2=1/12 DN2\sigma_q^2 = 1/12\ \mathrm{DN}^2 is the variance of rounding to integers. Setting the SNR to 1 and solving for the signal gives the absolute sensitivity threshold in electrons (EMVA 1288, Eq. 27):

μe.min=σd2+σq2/K2+1/4+12 ≥1,\mu_{e.\mathrm{min}} = \sqrt{\sigma_d^2 + \sigma_q^2 / K^2 + 1/4} + \frac{1}{2} \ \ge 1,

which the standard notes is not equal to σd\sigma_d but a bit higher, because of the quantization noise and the nonlinear SNR curve. The saturation capacity μe.sat=ημp.sat\mu_{e.\mathrm{sat}} = \eta \mu_{p.\mathrm{sat}} (Eq. 24) is measured, not taken from the pixel design: the exposure is raised until between 0.1% and 0.2% of the pixels show the maximum value, and the mean signal there is the saturation value (EMVA 1288, Section 6.6). The standard warns that the saturation capacity must not be confused with the full-well capacity, and is normally lower, because the digital output is clipped at 2k−12^k - 1 before the pixel is physically full (Section 2.7). The dynamic range is

DR=μp.satμp.min=μe.satμe.min,\mathrm{DR} = \frac{\mu_{p.\mathrm{sat}}}{\mu_{p.\mathrm{min}}} = \frac{\mu_{e.\mathrm{sat}}}{\mu_{e.\mathrm{min}}},

where the second form follows because η\eta divides both ends (Eqs. 24 and 26). The standard asks for it “as a ratio and in dB (20 log10 DR)” (Section 6.6), and its notation table lists the units of DR as 1, dB and bits (Appendix B).

Levoy’s course definition

Levoy (Noise and ISO, slide 23) writes

DR=saturation level−D tD t+Nr2,\mathrm{DR} = \frac{\text{saturation level} - D\,t}{\sqrt{D\,t + N_r^2}},

with the full-well capacity as the saturation level, the dark current DD in electrons per pixel per second, the exposure time tt and the read noise NrN_r in electrons. The denominator is the noise in the dark, not a threshold at SNR 1, and quantization does not appear. Because D tD\,t grows with time, the slides describe dynamic range as fixed for a particular sensor and exposure time (slide 39). Levoy gives two worked examples for a 1-second exposure: a cooled Retiga 4000R scientific camera, with 3,313:1 (11.7 bits), and the Aptina MT9P031 sensor of a Nokia N95 phone, with 1500:1 (10.5 bits) (slide 23).

ISETCam

ISETCam’s MATLAB code has two such functions at the commit read here. pixelDR returns 20log⁡1020 \log_{10} of the voltage swing minus the dark voltage, over the square root of the dark-voltage variance plus the read-noise variance; its comments state that the dark voltage variance “is simply equal to the dark voltage” because it is Poisson. sensorDR converts the dark voltage to electrons first, with the comment that the noise terms are Poisson in electrons “but not in volts”, adds the variance of the offset fixed-pattern noise (DSNU), and returns 10log⁡1010 \log_{10} of the ratio; its header notes that the factor was changed from 20 to 10 on 26 June 2024 in its dev-hdrsensor branch. In electron units, pixelDR’s ratio is Levoy’s formula, and sensorDR‘s ratio is the same with the offset noise added; the two functions’ decibel values differ by the factor of two in front of the logarithm.

Units

A dynamic range RR can be written as:

  • a ratio, R:1R{:}1;
  • decibels, 20log⁡10R20 \log_{10} R, the amplitude convention that EMVA 1288 (Section 6.6), Levoy (Noise and ISO, slide 12, for SNR) and ISETCam’s pixelDR use for ratios of signals;
  • stops or exposure values, log⁡2R\log_2 R, the photographers’ unit in which one stop is a factor of two in light (see exposure);
  • bits, which in Levoy’s slides is the same number as stops, log⁡2R\log_2 R: 3,313:1 is 11.7 bits.

It follows from the definitions that one stop is 20log⁡102≈6.0220 \log_{10} 2 \approx 6.02 dB, and that a range quoted with 10log⁡1010 \log_{10} is half the decibel figure of the same ratio quoted with 20log⁡1020 \log_{10}. “Bits” of dynamic range are not the bit depth of the converter: a 12-bit ADC can deliver fewer than 12 bits of dynamic range, as the properties below explain.

Properties

Scene, sensor, file and display ranges are different quantities. The scene range is fixed by the light; the sensor range by the pixel and readout; the file range by the encoding; the display range by the screen or print. The high dynamic range imaging article gives figures for the first three: about 120 dB for a typical outdoor scene and 60–70 dB for a conventional CMOS sensor in one capture, taken from Onzon, Mannan and Heide (2021), and usually only 256 values per channel for images stored for display. Tone mapping quotes Reinhard and colleagues for “over four orders” of magnitude within one scene, while prints and displays reproduce at best about two. When the scene’s range that matters exceeds the sensor’s, no single exposure can keep it all: every setting either clips the highlights or loses the shadows in noise (exposure).

The ADC’s bit depth can limit the range, but is not the range. A kk-bit converter has 2k2^k levels, and its rounding adds σq2/K2\sigma_q^2 / K^2 to the noise in electrons, so a coarse converter raises μe.min\mu_{e.\mathrm{min}}. EMVA 1288 notes that when the dark noise is below 0.5 DN, which it says happens only in the rare case of an 8-bit camera with a high-quality sensor, the dark noise cannot be measured reliably and the dynamic range is limited by the quantization noise (Section 6.5); for an 8-bit camera it gives the largest measurable dynamic range as 255/0.49≈500255/0.49 \approx 500 (Appendix C.4). The reverse is common too: image formation and image sensor note that the nominal bit depth of many cameras exceeds the number of bits that carry information. Levoy turns the relation around: the dynamic range determines the precision required in the ADC and the useful number of bits in a RAW image (Noise and ISO, slide 23). He adds that a camera needs at least about 10 ADC bits for a smooth grey ramp to fill the 256 levels of an 8-bit JPEG after gamma correction, because otherwise the dark part of the ramp shows banding (Photons and Sensors, slide 22); gamma correction works through that trade.

Gain trades the bottom of the range against the top. Raising the gain (ISO) multiplies the digital numbers per electron. When the converter, not the well, sets the clipping point, the saturation capacity in electrons falls in proportion; the quantization noise in electrons falls too, and so may any read noise added after the amplifier. The image sensor article measures the net effect on a simulated 12-bit sensor: four times the gain costs 8.4 dB of dynamic range. Levoy’s advice for low noise is to lengthen the exposure first and raise the ISO only after that, short of saturating the highlights (Noise and ISO, slides 25–26).

Clipping happens at both ends. At the top, every pixel past saturation reads the maximum code, and the values it should have had are lost; Bankhead calls clipping during acquisition saturation, and warns that a pixel at the extreme value of its bit depth cannot be trusted not to have clipped (Bankhead, “Types & bit depths”). At the bottom, values below zero are clipped too, which is why EMVA 1288 asks for an offset just large enough that fewer than about 0.5% of dark pixels read zero (Section 6.5). A clipped region has no gradient and biases every average, as exposure and the image histogram show.

Exposure moves the window; it does not widen it. Doubling the exposure time doubles every signal, so the same scene lands one stop higher between the noise floor and saturation; the width of the window stays the sensor’s dynamic range, apart from the dark current that a longer exposure adds (Levoy’s D tD\,t). Capturing a scene wider than the window needs several windows, which is what bracketing and merging do in high dynamic range imaging.

Dynamic range is not low-light quality. Levoy stresses the difference: the SNR says how noisy an image will be at a particular light level, while the dynamic range is a single number that only says the darkest usable signal is barely distinguishable from noise; a phone sensor might have as large a dynamic range as an SLR and still give very noisy low-light images (Noise and ISO, slide 23). At the bright end, the best SNR a pixel can reach is μe.sat\sqrt{\mu_{e.\mathrm{sat}}} (EMVA 1288, Eq. 55), set by the same saturation capacity but a different quantity from the range.

Examples

Two examples, in one script, use only NumPy 2.3.5. Every quantity is in electrons; the quantum efficiency is folded into the signal, which, by the identity above, leaves every ratio unchanged. Decibels are 20log⁡1020 \log_{10}, except for ISETCam’s sensorDR, which uses 10log⁡1010 \log_{10}; stops and Levoy’s bits are log⁡2\log_2.

(a) One sensor, several definitions. The first part evaluates Levoy’s two worked examples and the simulated sensor of part (b) (full well 20,000 electrons, read noise 2 electrons, no dark current, 12 bits) under each definition: Levoy’s formula, which in electron units is also pixelDR; EMVA’s Eqs. 27–28; and sensorDR without offset noise. EMVA needs a gain and a saturation capacity, which Levoy’s slides do not give. The script takes the full well minus the dark charge as the saturation capacity in every definition, so that only the lower end differs, and it prints EMVA under two choices for quantization, none and a 12-bit converter that spans the full well. Two checks test the code against the sources: Levoy’s printed values (C1, to within 0.5% for the ratios and 0.05 for the bits), and the summary data sheet template in EMVA 1288’s Appendix G, whose sample values (σd\sigma_d = 30.8 electrons, 1/K1/K = 10.22 electrons per DN, μe.sat\mu_{e.\mathrm{sat}} = 40,951 electrons) lead to μe.min\mu_{e.\mathrm{min}} = 31.4 electrons, DR = 1303 and 62.3 dB (C2). Because the sheet’s inputs are rounded, C2 accepts a dynamic range anywhere in the interval that the inputs at the ends of their rounding produce, which the code prints. Two further checks follow from the formulas: EMVA’s range is smaller than Levoy’s for every row (C3), and sensorDR’s decibels are half of pixelDR’s (C4).

(b) Usable stops of a step wedge. The second part photographs a synthetic step wedge, 20 flat patches of 128 × 128 pixels each one stop darker than the last, so the wedge spans 2192^{19}:1. The simulated sensor draws Poisson shot noise, clips the charge at the full well, adds Gaussian read noise, and converts with a bb-bit ADC whose gain maps the full well to the top code, above a small offset (2b−82^{b-8} DN, from 1 DN at 8 bits to 64 DN at 14 bits); it has no dark current and no fixed-pattern noise. Three sensors (full well and read noise of 20,000 and 2, 20,000 and 10, and 5,000 and 2 electrons) are run at 8, 10, 12 and 14 bits and at two exposures, A with the brightest patch at four times the full well and B four times longer. Each patch is first flagged as clipped if more than 0.1% of its pixels read the top code (EMVA’s saturation rule) or more than 0.5% read zero (its offset rule); an unclipped patch is usable if its SNR, the mean above the mean of a dark frame divided by the patch’s standard deviation, floored at the rounding noise 1/12\sqrt{1/12} DN, is at least 1. The count NuN_u of usable patches is compared with EMVA’s prediction W=log⁡2(μe.sat/μe.min)W = \log_2(\mu_{e.\mathrm{sat}} / \mu_{e.\mathrm{min}}), with μe.min\mu_{e.\mathrm{min}} from Eq. 27 and μe.sat\mu_{e.\mathrm{sat}} the mean at which 0.1% of pixels reach the top code (a normal approximation, our reading of EMVA’s 0.1–0.2%), and with NpredN_{\text{pred}}, the number of patch means that lie between the two.

The hypotheses were written before any script ran. H1: where the quantization step 1/K1/K is at most the read noise, the counted stops agree with the prediction to within one stop, ∣Nu−W∣<1|N_u - W| < 1, for seed 0 and for the worst of seeds 0–4, and Nu=NpredN_u = N_{\text{pred}}. H2: at 8 bits quantization limits the range: for each sensor, WW and NuN_u at 14 bits exceed those at 8 bits by at least one stop. H3: exposure B shifts the usable window two patches darker without changing its width. H3 holds by construction, since a two-stop shift on a one-stop wedge reproduces the same patch means two patches later, so its part of the example is an illustration rather than a test. The script also prints data checks: neighbouring patches 2:1, the full well mapped to the top code, underflow in the dark frames, usable patches contiguous in every condition, exposure and seed, and no usable patch whose true mean is below 1 electron. The script runs in about 2 seconds, and two runs gave identical output.

import os
for v in ("OMP_NUM_THREADS", "OPENBLAS_NUM_THREADS", "MKL_NUM_THREADS"):
    os.environ[v] = "1"                       # one BLAS thread, set before NumPy loads
import numpy as np

# ---------- (a) One sensor, several definitions (all quantities in electrons) ----------
def dr_levoy(fw, dark, read):                 # Levoy: (full well - Dt) / sqrt(Dt + Nr^2)
    return (fw - dark) / np.sqrt(dark + read**2)

def emva_e_min(sigma_d, K=None):              # EMVA 1288 Eq. 27; K=None: no quantization
    q = 0.0 if K is None else (1 / 12) / K**2  # sigma_q^2 / K^2, sigma_q^2 = 1/12 DN^2
    return np.sqrt(sigma_d**2 + q + 0.25) + 0.5

def isetcam_sensor_db(fw, dark, read, dsnu=0.0):  # sensorDR: 10 log10, adds offset SD
    return 10 * np.log10((fw - dark) / np.sqrt(dark + read**2 + dsnu**2))

def units(r):                                 # ratio, dB (20 log10), stops (log2)
    return f"{r:9.2f}:1 {20 * np.log10(r):7.3f} dB {np.log2(r):7.3f} st"

# name, full well (e-), dark charge D*t (e-, t = 1 s), read noise (e-), 12-bit K (DN/e-)
cams = [("Retiga 4000R", 40000, 1.64, 12.0, 4095 / 40000),
        ("Aptina MT9P031", 8500, 25.0, 2.6, 4095 / 8500),
        ("simulated", 20000, 0.0, 2.0, (4095 - 16) / 20000)]
print("(a) definitions          ratio          dB (20 log10)  stops")
for name, fw, dt, nr, K12 in cams:
    sd = np.sqrt(dt + nr**2)                  # dark noise in electrons
    lev = dr_levoy(fw, dt, nr)
    print(f"{name}")
    print(f"  Levoy / ISETCam pixelDR {units(lev)}")
    print(f"  EMVA, no quantization   {units((fw - dt) / emva_e_min(sd))}")
    print(f"  EMVA, 12-bit ADC        {units((fw - dt) / emva_e_min(sd, K12))}")
    print(f"  ISETCam sensorDR        {isetcam_sensor_db(fw, dt, nr):9.3f} dB (10 log10)")
for name, printed, bits in (("Retiga 4000R", 3313, 11.7), ("Aptina MT9P031", 1500, 10.5)):
    fw, dt, nr = next(c[1:4] for c in cams if c[0] == name)
    r = dr_levoy(fw, dt, nr)
    print(f"C1 {name}: ratio {r:.2f} vs printed {printed} "
          f"({100 * (r / printed - 1):+.3f}%), log2 {np.log2(r):.3f} vs printed {bits}")

# EMVA 1288 Appendix G template sheet: sigma_d 30.8 e-, 1/K 10.22 e-/DN, mu_e.sat 40951 e-
e_min = emva_e_min(30.8, 1 / 10.22)
print(f"C2 template: mu_e.min {e_min:.4f} e- (sheet 31.4), DR {40951 / e_min:.2f} (1303), "
      f"{20 * np.log10(40951 / e_min):.4f} dB (62.3), SNRmax {np.sqrt(40951):.3f} (202), "
      f"{20 * np.log10(np.sqrt(40951)):.4f} dB (46.1)")
box = [(sat / emva_e_min(s, 1 / ik)) for s in (30.75, 30.85) for ik in (10.215, 10.225)
       for sat in (40950.5, 40951.5)]          # inputs at the ends of their rounding
print(f"   DR allowed by input rounding {min(box):.2f}..{max(box):.2f}, "
      f"dB {20 * np.log10(min(box)):.4f}..{20 * np.log10(max(box)):.4f}")

# ---------- (b) Usable stops of a one-stop step wedge through a simulated sensor ----------
P, N = 128, 20                                # patch size (pixels), number of patches
Z = 3.0902                                    # normal quantile for 0.1% of pixels clipped

def capture(rng, mu, fw, read, K, offset, ymax):
    """Photoelectrons (Poisson) -> full well -> read noise -> b-bit ADC with offset."""
    e = np.minimum(rng.poisson(mu), fw) + read * rng.standard_normal(mu.shape)
    return np.clip(np.round(K * e + offset), 0, ymax)

def usable(img, dark, ymax):
    """Per patch: flag clipping first, then test SNR = (mean - dark mean) / std >= 1."""
    ok, n_clip = [], 0
    for i in range(N):
        y = img[:, i * P:(i + 1) * P]
        clipped = np.mean(y == ymax) > 0.001 or np.mean(y == 0) > 0.005
        sig, sd = y.mean() - dark.mean(), y.std(ddof=1)
        snr = sig / max(sd, np.sqrt(1 / 12))  # std floored at quantization noise, DN
        n_clip += clipped
        ok.append(not clipped and snr >= 1)
    idx = np.flatnonzero(ok)
    return idx, n_clip

rows, checks, contiguous, below_1e = [], [], [], 0
print("\n(b)   FW  read  b   step  e_min   e_sat     W   N_pred | exposure A: N_u first-last "
      "clipped | B (x4): N_u first-last | A, seeds 0-4")
for fw, read in ((20000, 2.0), (20000, 10.0), (5000, 2.0)):
    for b in (8, 10, 12, 14):
        ymax = 2**b - 1
        offset = 2.0 ** (b - 8)               # 1 DN at 8 bits ... 64 DN at 14 bits
        K = (ymax - offset) / fw              # the full well maps to the largest code
        e_min = emva_e_min(read, K)           # EMVA Eq. 27 with sigma_d = read noise
        e_sat = fw                            # mean at which 0.1% of pixels clip
        for _ in range(100):
            e_sat = fw - 0.5 / K - Z * np.sqrt(e_sat + read**2 + 1 / (12 * K**2))
        W = np.log2(e_sat / e_min)
        res = {}
        for name, top in (("A", 4 * fw), ("B", 16 * fw)):
            mu = top * 2.0 ** -np.arange(N)   # patch means in electrons, one stop apart
            img = np.repeat(mu, P)[None, :].repeat(P, axis=0)
            n_pred = int(np.sum((mu >= e_min) & (mu <= e_sat)))
            runs = []
            for seed in range(5):
                rng = np.random.default_rng(seed)
                dark = capture(rng, np.zeros_like(img), fw, read, K, offset, ymax)
                runs.append(usable(capture(rng, img, fw, read, K, offset, ymax), dark, ymax))
                idx = runs[-1][0]
                contiguous.append(np.all(np.diff(idx) == 1))
                below_1e += int(np.sum(mu[idx] < 1))  # usable patches with mean < 1 e-
                if seed == 0:
                    checks.append((np.mean(dark == 0), np.allclose(mu[:-1] / mu[1:], 2),
                                   round(K * fw + offset) == ymax))
            res[name] = runs
        (iA, cA), (iB, _) = res["A"][0], res["B"][0]
        nA = [len(r[0]) for r in res["A"]]
        rows.append((fw, read, b, 1 / K, W, n_pred, len(iA), iA[0], len(iB), iB[0], nA))
        print(f"   {fw:5d} {read:5.1f} {b:2d} {1 / K:6.2f} {e_min:6.2f} {e_sat:7.1f} "
              f"{W:6.2f} {n_pred:5d}   | {len(iA):11d} {iA[0]:5d}-{iA[-1]:<3d} {cA:6d}  "
              f"| {len(iB):11d} {iB[0]:5d}-{iB[-1]:<3d} | {min(nA):5d}-{max(nA)}")

und, ratio2, scale = zip(*checks)
print(f"\ndata: wedge ratio 2^{np.log2(2.0 ** (N - 1)):.0f}, neighbours 2:1 {all(ratio2)}, "
      f"full well -> top code {all(scale)}, dark underflow max {max(und):.4f}")
print(f"usable patches contiguous (all conditions, exposures, seeds 0-4): {all(contiguous)}; "
      f"usable patches with true mean < 1 e-: {below_1e}")
for fw, read, b, step, W, n_pred, nA, fA, nB, fB, seeds in rows:
    if step <= read:                           # H1 applies: quantization step <= read noise
        print(f"H1 FW {fw} read {read} b {b}: |N_u - W| = {abs(nA - W):.2f} (seed 0), "
              f"{max(abs(n - W) for n in seeds):.2f} (worst of seeds 0-4); "
              f"N_u = N_pred: {nA == n_pred}")
for fw, read in ((20000, 2.0), (20000, 10.0), (5000, 2.0)):
    r8 = next(r for r in rows if r[:3] == (fw, read, 8))
    r14 = next(r for r in rows if r[:3] == (fw, read, 14))
    print(f"H2 FW {fw} read {read}: W(14) - W(8) = {r14[4] - r8[4]:.2f} stops, "
          f"N_u(14) - N_u(8) = {r14[6] - r8[6]}")
print("H3 every condition: N_u(B) = N_u(A):", all(r[8] == r[6] for r in rows),
      "and first(B) = first(A) + 2:", all(r[9] == r[7] + 2 for r in rows))

Output (NumPy 2.3.5):

(a) definitions          ratio          dB (20 log10)  stops
Retiga 4000R
  Levoy / ISETCam pixelDR   3314.38:1  70.408 dB  11.695 st
  EMVA, no quantization     3179.90:1  70.048 dB  11.635 st
  EMVA, 12-bit ADC          3099.86:1  69.827 dB  11.598 st
  ISETCam sensorDR           35.204 dB (10 log10)
Aptina MT9P031
  Levoy / ISETCam pixelDR   1503.83:1  63.544 dB  10.554 st
  EMVA, no quantization     1376.32:1  62.774 dB  10.427 st
  EMVA, 12-bit ADC          1369.28:1  62.730 dB  10.419 st
  ISETCam sensorDR           31.772 dB (10 log10)
simulated
  Levoy / ISETCam pixelDR  10000.00:1  80.000 dB  13.288 st
  EMVA, no quantization     7807.76:1  77.851 dB  12.931 st
  EMVA, 12-bit ADC          6665.15:1  76.476 dB  12.702 st
  ISETCam sensorDR           40.000 dB (10 log10)
C1 Retiga 4000R: ratio 3314.38 vs printed 3313 (+0.042%), log2 11.695 vs printed 11.7
C1 Aptina MT9P031: ratio 1503.83 vs printed 1500 (+0.255%), log2 10.554 vs printed 10.5
C2 template: mu_e.min 31.4450 e- (sheet 31.4), DR 1302.30 (1303), 62.2943 dB (62.3), SNRmax 202.364 (202), 46.1226 dB (46.1)
   DR allowed by input rounding 1300.23..1304.39, dB 62.2804..62.3082

(b)   FW  read  b   step  e_min   e_sat     W   N_pred | exposure A: N_u first-last clipped | B (x4): N_u first-last | A, seeds 0-4
   20000   2.0  8  78.74  23.32 19523.1   9.71     9   |           8     3-10       3  |           8     5-12  |     8-8
   20000   2.0 10  19.63   6.53 19557.6  11.55    11   |          10     3-12       3  |          10     5-14  |    10-10
   20000   2.0 12   4.90   3.00 19565.2  12.67    12   |          12     3-14       3  |          12     5-16  |    12-12
   20000   2.0 14   1.23   2.59 19567.1  12.88    12   |          12     3-14       3  |          12     5-16  |    12-12
   20000  10.0  8  78.74  25.34 19522.1   9.59     9   |           8     3-10       3  |           8     5-12  |     8-8
   20000  10.0 10  19.63  12.00 19556.6  10.67    10   |          10     3-12       3  |          10     5-14  |    10-10
   20000  10.0 12   4.90  10.61 19564.2  10.85    10   |          10     3-12       3  |          10     5-14  |    10-10
   20000  10.0 14   1.23  10.52 19566.0  10.86    10   |          10     3-12       3  |          10     5-14  |    10-10
    5000   2.0  8  19.69   6.54  4775.8   9.51     9   |           8     3-10       3  |           8     5-12  |     8-8
    5000   2.0 10   4.91   3.00  4783.7  10.64    10   |          10     3-12       3  |          10     5-14  |    10-10
    5000   2.0 12   1.23   2.59  4785.5  10.85    10   |          10     3-12       3  |          10     5-14  |    10-10
    5000   2.0 14   0.31   2.56  4786.0  10.87    10   |          10     3-12       3  |          10     5-14  |    10-10

data: wedge ratio 2^19, neighbours 2:1 True, full well -> top code True, dark underflow max 0.0000
usable patches contiguous (all conditions, exposures, seeds 0-4): True; usable patches with true mean < 1 e-: 0
H1 FW 20000 read 2.0 b 14: |N_u - W| = 0.88 (seed 0), 0.88 (worst of seeds 0-4); N_u = N_pred: True
H1 FW 20000 read 10.0 b 12: |N_u - W| = 0.85 (seed 0), 0.85 (worst of seeds 0-4); N_u = N_pred: True
H1 FW 20000 read 10.0 b 14: |N_u - W| = 0.86 (seed 0), 0.86 (worst of seeds 0-4); N_u = N_pred: True
H1 FW 5000 read 2.0 b 12: |N_u - W| = 0.85 (seed 0), 0.85 (worst of seeds 0-4); N_u = N_pred: True
H1 FW 5000 read 2.0 b 14: |N_u - W| = 0.87 (seed 0), 0.87 (worst of seeds 0-4); N_u = N_pred: True
H2 FW 20000 read 2.0: W(14) - W(8) = 3.17 stops, N_u(14) - N_u(8) = 4
H2 FW 20000 read 10.0: W(14) - W(8) = 1.27 stops, N_u(14) - N_u(8) = 2
H2 FW 5000 read 2.0: W(14) - W(8) = 1.36 stops, N_u(14) - N_u(8) = 2
H3 every condition: N_u(B) = N_u(A): True and first(B) = first(A) + 2: True

Definitions. For one and the same sensor, the definitions disagree by up to 0.14 stop for Levoy’s two cameras (Aptina: 10.554 against 10.419 stops) and by more than half a stop for the simulated one (13.288 stops for Levoy against 12.702 for EMVA with its 12-bit converter). EMVA’s range is smaller in every row under both quantization choices, so C3 held: its threshold σd2+σq2/K2+1/4+1/2\sqrt{\sigma_d^2 + \sigma_q^2/K^2 + 1/4} + 1/2 always exceeds the dark noise σd\sigma_d, and the difference matters most when σd\sigma_d is a few electrons, as in the simulated sensor, where 2 electrons of read noise give a threshold of about 2.6 electrons without quantization (20,000/7807.76). The 12-bit converter lowers EMVA’s figure further, by a stop fraction that grows with the step size 1/K1/K against the dark noise. sensorDR printed exactly half of pixelDR’s decibels in every row, so C4 held; its 40 dB for a 10,000:1 ratio would read as 100:1 to anyone who assumes the 20 log10 convention of a datasheet.

C1 held for the Retiga 4000R (ratio within 0.042%, 11.695 bits against the printed 11.7) and for the Aptina ratio (within 0.255%), but failed for the Aptina’s bits: the formula gives 10.554, 0.054 from the printed 10.5 against a tolerance of 0.05. Even the slide’s rounded ratio gives log⁡21500=10.55\log_2 1500 = 10.55. The plan’s computation of the reachable value had shown before the pilot that no correct computation meets this part, and the tolerance was kept and the failure reported. C2 held in every part: Eq. 27 gives μe.min\mu_{e.\mathrm{min}} = 31.4450 electrons (31.4 on the sheet), a dynamic range of 1302.30, which, like the sheet’s 1303, lies inside the interval 1300.23–1304.39 allowed by the rounding of the inputs, 62.2943 dB (62.3), and a maximum SNR of 202.364 (202) or 46.1226 dB (46.1).

Usable stops (H1: held in all five conditions). In the five conditions whose quantization step is at most the read noise, the count equals NpredN_{\text{pred}} and stays within 0.85–0.88 stop of WW, for seed 0 and for every seed from 0 to 4. The count is the lower one in every row because of the wedge’s grid: exposure A puts patch 2 exactly at the full well, where it clips, and the next patch at half the full well, so the counted window starts nearly a stop below the saturation capacity that WW uses, and at the bottom the last usable patch lies part of a stop above μe.min\mu_{e.\mathrm{min}}; in these rows the two gaps together exceed one stop. A one-stop wedge resolves the dynamic range only to one stop, and the hypothesis allowed for that. The three clipped patches in every row (patches 0–2) are flagged before their SNR is looked at.

Bit depth (H2: held for all three sensors). At 8 bits, μe.min\mu_{e.\mathrm{min}} rises to 23–25 electrons for the 20,000-electron sensors, whatever the read noise, because the step of 78.74 electrons makes the rounding noise (78.74/12≈22.778.74/\sqrt{12} \approx 22.7 electrons) the largest term in Eq. 27. The predicted range falls by 3.17 stops for the 2-electron sensor and by 1.27 and 1.36 stops for the other two, and the counts fall by 4, 2 and 2 patches. With 10 electrons of read noise the sensor itself already sets the floor near 10.5 electrons, so the converter costs less; the same is true of the small-well sensor, whose 8-bit step is a quarter as large.

The counts also fall one patch short of NpredN_{\text{pred}} in four conditions outside H1: at 8 bits for all three sensors, and at 10 bits for the 20,000-electron, 2-electron sensor. In each of them the missing patch is the one whose mean lies between μe.min\mu_{e.\mathrm{min}} and half a quantization step: at 8 bits and a 20,000-electron well, patch 11 holds 39.1 electrons, above μe.min\mu_{e.\mathrm{min}} = 23.32 but below half the 78.74-electron step. It follows from the construction that a flat patch whose noise is small against one step is not dithered: its pixels read only the dark code or the next one, and if a fraction pp of them reach the next code, the patch’s signal is pp DN and its standard deviation p(1−p)\sqrt{p(1-p)} DN, so its SNR, p/(1−p)\sqrt{p/(1-p)}, reaches 1 only when pp reaches one half, that is, when the mean passes half a step (our derivation). Eq. 27 treats the rounding as an independent noise of 1/121/12 DN², which is the right model when the noise, or the scene’s texture, spreads values over several codes; on smooth dark areas of a coarse converter the usable floor is about half a step, up to about 0.8 stop above Eq. 27’s when the rounding term dominates it (since 0.5/0.289≈1.70.5/0.289 \approx 1.7; our derivation). The floor that the code puts under the patch’s standard deviation, discussed below, does not cause this: near p=1/2p = 1/2 the standard deviation is about 0.5 DN, above the floor of 0.29 DN.

Exposure (H3: held in every condition). Exposure B moves the first usable patch from 3 to 5 and keeps the count in every row, as the construction requires. The usable window of a single exposure is a fixed number of stops wide, and exposure decides which stops of the scene fall into it.

Checks and process. Every data check held: neighbouring patches are 2:1, the full well maps to the top code, the dark frames’ underflow (seed 0) stays below the 0.5% limit, the usable patches are contiguous in every condition, exposure and seed, and no patch with a true mean below 1 electron is usable. The checks and tolerances were written, with the values a correct computation reaches on these data, before the pilot ran, with one exception decided then: C2 first required the dynamic range “within 0.5 of 1303”; a computation of the reachable value showed that the sheet’s rounded inputs give a value more than 0.5 below 1303 (1302.30 in the output), and the plan replaced the criterion, before the pilot, with “1303 lies inside the printed rounding interval, and the computed DR lies inside it”. The first pilot found a broken component. Its SNR rule set the SNR to infinity for a patch of standard deviation zero with a positive signal, and at 8 bits it counted dark patches whose pixels all read the offset code as usable whenever the dark frame’s mean fell a hair below that code. On the editor’s decision, as the repair of a broken component, the rule was replaced by the floor at the rounding noise 1/12\sqrt{1/12} DN, the check for contiguous usable patches was extended from seed 0 to every seed, and the count of usable patches below 1 electron was added. A second fault was repaired at the same time: the check that the full well maps to the top code compared floating-point numbers for equality and printed False at 14 bits, and now compares integers. The repaired script was piloted once more, that pilot was fixed in advance as final, and it is the first run shown here; the hypotheses and criteria did not change. Between the two pilots the printed outcomes changed in these places: the seed ranges at 8 bits, which had included counts above 8 for the 20,000-electron, 10-electron sensor and the 5,000-electron sensor, the full-well check, which had printed False, and the contiguity check, which had covered seed 0 only on the data line and now has a line of its own, covering every seed, together with the added count of usable patches below 1 electron. Every other entry of the table, C1–C4 and H1–H3 printed the same.

The example departs from the batch plan’s description in eight ways, each logged before the pilot: (1) both ISETCam functions are printed, since sensorDR uses 10log⁡1010 \log_{10} at this commit while the plan expected 20log⁡1020 \log_{10}, which holds for pixelDR only; (2) EMVA for Levoy’s cameras is printed under two quantization choices, with the full well minus the dark charge as the saturation capacity, since neither gain nor saturation capacity is given; (3) the Appendix G template check C2 was added to test the Eq. 27 code against the standard’s own numbers; (4) NpredN_{\text{pred}}, the range over seeds 0–4, the clipped count and the data checks were added to the printout; (5) H1’s condition “a step at most about the read noise” was made exact as 1/K≤σr1/K \le \sigma_r, which is the Kσd≥1K\sigma_d \ge 1 rule of EMVA 1288’s first two releases, quoted in Appendix C.4; (6) the patches are 128 × 128 pixels rather than 64 × 64, to halve the sampling error of the SNR; (7) Levoy’s bits and stops are printed once, being the same number; (8) the simulated sensor in part (b) has no dark current, PRNU or DSNU.

Common Misconceptions

  • “A 14-bit camera has 14 stops of dynamic range.” The bit depth bounds the range only when quantization dominates the noise; otherwise read noise and the saturation capacity set it, and the converter’s extra bits sample the noise. In the example, the 20,000-electron sensors give about 10.9 or 12.9 stops at 14 bits, depending on their read noise.
  • “Decibels are decibels.” A range quoted with 10log⁡1010 \log_{10} is half the number of the same ratio with 20log⁡1020 \log_{10}; ISETCam’s sensorDR and pixelDR differ in exactly this way at the commit read here. EMVA 1288 specifies 20log⁡1020 \log_{10}.
  • “The full well is the top of the range.” EMVA 1288 measures a saturation capacity at which the digital output starts to clip, usually below the full well (Section 2.7), and the bottom is a threshold at SNR 1, not the read noise. Datasheets that follow other definitions report other numbers for the same sensor.
  • “High dynamic range means a clean image in low light.” Dynamic range says nothing about how noisy a low-light image is (Levoy, Noise and ISO, slide 23); the SNR at the light level that matters does.
  • “Exposing longer widens the range.” It moves the window up the scene’s brightness scale; the width stays the same, and dark current makes it slightly narrower.
  • “An 8-bit file can hold only 255:1.” A nonlinear encoding spreads its codes over a wider range of light, at the cost of coarser steps; gamma correction shows the trade. It follows that the range a file can hold depends on its transfer function, not only on its number of bits.

Where It Is Used

Dynamic range is one of the numbers used to choose a camera for inspection or robotics, and EMVA 1288 datasheets make it comparable between vendors when they follow the same definition. Exposure control decides which part of a scene falls inside the sensor’s range, and its failures show as clipped or noisy regions in the image histogram. When the scene is wider than the sensor, high dynamic range imaging merges bracketed exposures or uses sensors with several gains or multi-slope responses, and tone mapping brings the result back into a display’s range. The noise models of image noise and image denoising describe the floor of the range, and the photon transfer measurement in image sensor estimates both ends.

Related

  • High Dynamic Range Imaging

    Capturing scenes whose brightness range exceeds what one exposure can record, by bracketing exposures, recovering the camera response and a radiance map, merging with noise-aware weights and removing ghosts, or with single-shot HDR sensors and learned single-image reconstruction, and compressing the result for display by tone mapping or exposure fusion.

  • Tone Mapping

    Compressing a high-dynamic-range radiance map into the range a display or print can show, with global curves such as Reinhard's photographic operator, local operators such as automatic dodging-and-burning, and base–detail decompositions such as Durand and Dorsey's bilateral-filter method, and why local operators produce halos.

  • Image Noise

    The random variation in recorded pixel values that does not come from the scene, its physical sources in the camera, and how it is modeled and measured.

  • Gamma Correction

    The nonlinear transfer functions that map light to stored code values and code values back to light, such as sRGB, BT.709 and BT.1886, and why they decide how many visible steps an 8-bit image has.

  • Image Formation

    How light leaving a scene becomes an array of pixel values, through illumination and reflectance, the lens, the sensor and its colour filters, sampling and quantization, and the camera's own processing.

  • Image Histogram

    The count of pixels at each intensity or color level, an empirical distribution that summarizes exposure and contrast and underlies thresholding, equalization, and histogram-based recognition and tracking.

References

  1. European Machine Vision Association (2021). EMVA Standard 1288: Standard for Characterization of Image Sensors and Cameras, Release 4.0 Linear.
  2. Levoy, M. (2013). Noise and ISO. Lecture slides, CS 178: Digital Photography, Stanford University, Spring 2013 (cited by slide number).
  3. Levoy, M. (2013). Photons and Sensors. Lecture slides, CS 178: Digital Photography, Stanford University, Spring 2013 (cited by slide number).
  4. ISETCam (Stanford Center for Image Systems Engineering). pixelDR.m, at commit 3e0253421752eb72f2d1353327dffd0d433abdaf (MIT licence; source and header comments read).
  5. ISETCam (Stanford Center for Image Systems Engineering). sensorDR.m, at commit 3e0253421752eb72f2d1353327dffd0d433abdaf (MIT licence; source and header comments read).
  6. Bankhead, P. (2022–2025). Introduction to Bioimage Analysis. Online book (CC BY 4.0), chapter “Types & bit depths”.