Added noise

This commit is contained in:
connor committed 2026-10-16 15:05:04 -04:00
1 parent 970551b4e1
commit 1ed95982ad
14 files changed
+687 -22

No files matched your search

+97 -4
View File
@@ -356,7 +356,8 @@ For the example camera, pi * A * t / (4 N^2) is 1.48e-16 m^2 sr s.
## From electrons to DN: the detector
Code: `camera.rs` (`Detector::dn`); `gpu/sensor.rs`, `shaders/sensor.wgsl`.
Code: `camera.rs` (`Detector::dn`, `Detector::variance`); `gpu/sensor.rs`,
`shaders/sensor.wgsl`.
- **Full well.** The maximum number of electrons a pixel holds. Beyond it
the pixel saturates and reads its maximum value whatever the light.
@@ -371,7 +372,8 @@ Code: `camera.rs` (`Detector::dn`); `gpu/sensor.rs`, `shaders/sensor.wgsl`.
So DN = bias + min(electrons, full well) / gain, rounded and clipped to the
ADC range. The renderer does this on the GPU for every pixel and channel,
as the last step of a frame, and reads back only the resulting integers.
Noise, not modeled yet, enters between the electrons and the gain.
From camera tier C1, noise enters between the electrons and the gain (see
Noise below).
Two consequences of these numbers show up in every image:
@@ -411,7 +413,98 @@ exactly, on the GPU and on lavapipe.
Picking the exposure time is the same trade a photographer makes. A longer
exposure saturates the sunlit target. A shorter one leaves the side of the
target lit only by Earthshine lost in noise once noise is modeled.
target lit only by Earthshine lost in noise.
### Noise
Code: `Detector::variance`; `sensor.wgsl` (`NOISE`).
A real image is noisy, and the noise decides what can be detected at all.
From camera tier C1 the renderer adds three kinds, following the EMVA 1288
standard's model of a camera:
- **Shot noise.** Light arrives as photons at random times, so a pixel
expecting N electrons collects a Poisson-distributed count with mean N and
standard deviation sqrt(N). It is a property of light, not of the camera:
a perfect detector still has it. A pixel expecting 10,000 electrons varies
by 100 (1 %); one expecting 100 varies by 10 (10 %).
- **Dark current.** The detector makes electrons of its own from heat,
`Detector::dark_current` per second, which add to the light's and carry
shot noise of their own. It grows with temperature, roughly doubling every
6 to 8 degrees C; the value given is for the detector's operating
temperature.
- **Read noise.** Reading a pixel out adds a few electrons of random error,
`Detector::read_noise` RMS, to every pixel in every frame, light or not.
In order: the light's electrons and the dark electrons together are drawn
as one Poisson count (a sum of Poisson counts is itself one); the count is
clipped to the full well; read noise is added; then the gain, bias and
rounding to DN as before. Below a mean of 50 electrons the count is drawn
exactly. Above it a Gaussian of the same mean and variance takes its place,
which a Poisson count's shape is already close to and which costs the same
at any level. With read noise the DN can fall below the bias, which is what
the bias is for.
Each pixel's variance in electrons^2 is then
variance = (electrons + dark current x exposure) + read noise^2 + gain^2 / 12
where the last term is the rounding to whole DN. Divided by gain^2 it is in
DN^2. Plotted against the mean signal in DN, the variance is a straight line
with slope 1 / gain and intercept (read noise^2 + dark shot noise) / gain^2:
the photon transfer curve, the standard way to measure a camera's gain and
read noise from flat fields alone.
For the example camera (5 electrons read noise, 20 electrons per second dark
current, gain 5) at 0.1 ms, the signal-to-noise ratio, signal over the
square root of the variance, is:
| What | Electrons | SNR |
|---|---|---|
| A white surface facing the Sun, green channel | 11,139 | 105 |
| Earth, albedo 0.3 | 3,342 | 58 |
| A white surface lit by 1 % of sunlight, as Earthshine can | 111 | 9.5 |
| A V = 0 star, over the 4 pixels it covers | 56 | 4.4 |
| A V = 4 star, over 4 pixels | 1.4 | 0.13 |
So at the exposures a sunlit target needs, the brightest stars are just
detectable and the rest are lost in read noise. At 50 ms, a star tracker's
exposure, a V = 6 star reaches an SNR of 7.5.
Dark current hardly matters at these exposures: 20 electrons per second is
0.002 electrons in 0.1 ms. It matters for long ones: 1 electron in 50 ms,
100 in 5 s, with 10 electrons of shot noise.
**Reproducible randomness.** The noise comes from a counter-based generator:
a hash (Jarzynski and Olano's pcg4d) of the pixel and channel, the frame
number and a 64-bit seed (`RendererOptions::noise_seed`), turned into
uniform numbers, then into Gaussians (Box-Muller) or a Poisson count (by
inverting its distribution). Nothing is carried from one pixel or frame to
the next, so every value is computed independently on the GPU. The renderer
numbers its frames from 0 (`Frame::number`), and
`Renderer::set_frame_number` renders a frame again with exactly the same
noise. Different frame numbers or seeds give independent noise. The hash is
identical on every device, but the logarithms and cosines that shape it may
differ in the last bit, so the GPU and lavapipe agree statistically rather
than pixel for pixel.
**Noise-free images.** A detector with no read noise, no dark current and
shot noise off (`Detector::noiseless`) renders C1 without noise, exactly as
before noise existed, for ground truth and ML labels. The renderer then
compiles the noise out entirely. At C0 there is never noise.
Measured, identically on the GPU and lavapipe: flat fields at six levels
from 100 to 16,000 electrons, two frames each, give half the variance of
their difference within 2 % of the formula (0.982 to 1.008 of it, against a
sampling error of 0.6 %), and the photon transfer curve's slope is 1 / gain
to 0.1 %. A dark frame of 100 electrons reads 20.005 DN above the bias, with
its variance within 0.6 %. Counts below 50 match the Poisson probabilities
count by count. Consecutive frames, different seeds and neighboring pixels
are uncorrelated (|r| at most 0.005 over 196,608 values). 400 faint stars of
300 electrons each vary as predicted to 0.4 %, an SNR of 7.35. Noise costs nothing measurable on an
RTX 3060 (0.476 ms for the sensor pass at 1920 x 1080 with or without it,
1.89 ms at 3840 x 2160); on lavapipe it adds 1.5 ms at 1920 x 1080 and
9.6 ms at 3840 x 2160.
## How shapes become pixels
@@ -1461,7 +1554,7 @@ is a run of the script with `--gaia 16`, at twice the size.
- Earthshine on the Moon's night side
- The diffraction pattern's rings, and a lens's measured point spread function
- Noise: photon shot noise, read noise, dark current, pixel-to-pixel variation
- Pixel-to-pixel variation in response and dark current (PRNU, DSNU)
- Saturation spilling into neighbors (blooming)
- Non-Lambertian materials (BRDFs), glints, and the full Moon's edge as bright
as its center