diff --git a/docs/images/raw12/examples/staeffelsberg/dark_compressed.png b/docs/images/raw12/examples/staeffelsberg/dark_compressed.png new file mode 100644 index 0000000..ddc25f9 Binary files /dev/null and b/docs/images/raw12/examples/staeffelsberg/dark_compressed.png differ diff --git a/docs/images/raw12/examples/staeffelsberg/dark_crop.png b/docs/images/raw12/examples/staeffelsberg/dark_crop.png new file mode 100644 index 0000000..b9f4461 Binary files /dev/null and b/docs/images/raw12/examples/staeffelsberg/dark_crop.png differ diff --git a/docs/images/raw12/examples/staeffelsberg/mid_crop.png b/docs/images/raw12/examples/staeffelsberg/mid_crop.png new file mode 100644 index 0000000..47fff86 Binary files /dev/null and b/docs/images/raw12/examples/staeffelsberg/mid_crop.png differ diff --git a/docs/images/raw12/examples/staeffelsberg/noise_vs_signal.png b/docs/images/raw12/examples/staeffelsberg/noise_vs_signal.png new file mode 100644 index 0000000..11adac1 Binary files /dev/null and b/docs/images/raw12/examples/staeffelsberg/noise_vs_signal.png differ diff --git a/docs/images/raw12/examples/staeffelsberg/overview.jpg b/docs/images/raw12/examples/staeffelsberg/overview.jpg new file mode 100644 index 0000000..ab023a7 Binary files /dev/null and b/docs/images/raw12/examples/staeffelsberg/overview.jpg differ diff --git a/docs/images/raw12/examples/staeffelsberg/results.json b/docs/images/raw12/examples/staeffelsberg/results.json new file mode 100644 index 0000000..2d8cf08 --- /dev/null +++ b/docs/images/raw12/examples/staeffelsberg/results.json @@ -0,0 +1,474 @@ +{ + "frame": { + "width": 5328, + "height": 3040, + "green_range_12bit": [ + 128, + 2792 + ], + "share_of_frame": { + "dark (< 300 DN)": 33.2, + "mid (300-1000 DN)": 38.5, + "bright (>= 1000 DN)": 28.3 + } + }, + "noise_calibration": 1.089, + "noise": { + "12-bit (this frame)": [ + [ + 156, + 5.4 + ], + [ + 184, + 6.2 + ], + [ + 230, + 7.9 + ], + [ + 274, + 10.4 + ], + [ + 336, + 14.8 + ], + [ + 454, + 11.2 + ], + [ + 568, + 12.4 + ], + [ + 738, + 13.9 + ], + [ + 1299, + 16.2 + ], + [ + 1596, + 18.3 + ], + [ + 2259, + 20.5 + ], + [ + 2551, + 21.6 + ] + ], + "8-bit, same frame (12-bit >> 4)": [ + [ + 156, + 6.4 + ], + [ + 184, + 7.8 + ], + [ + 230, + 9.3 + ], + [ + 274, + 11.4 + ], + [ + 336, + 15.6 + ], + [ + 454, + 12.3 + ], + [ + 568, + 13.4 + ], + [ + 738, + 14.8 + ], + [ + 1300, + 16.9 + ], + [ + 1597, + 18.9 + ], + [ + 2259, + 21.0 + ], + [ + 2551, + 22.2 + ] + ], + "camera RGB8 (other capture)": [ + [ + 155, + 9.6 + ], + [ + 182, + 10.5 + ], + [ + 228, + 12.2 + ], + [ + 272, + 14.6 + ], + [ + 351, + 15.1 + ], + [ + 428, + 15.8 + ], + [ + 602, + 18.3 + ], + [ + 748, + 23.1 + ], + [ + 1272, + 24.2 + ], + [ + 1681, + 28.3 + ], + [ + 2285, + 32.0 + ], + [ + 2442, + 34.5 + ] + ], + "4 Oct frame, 0 dB": [ + [ + 661, + 17.0 + ], + [ + 893, + 21.3 + ], + [ + 1189, + 27.8 + ], + [ + 1666, + 31.6 + ], + [ + 2177, + 36.6 + ], + [ + 2587, + 37.3 + ], + [ + 3024, + 41.4 + ] + ], + "2 Oct frame, \u2248 15 dB": [ + [ + 671, + 32.9 + ], + [ + 860, + 37.8 + ], + [ + 1208, + 46.5 + ], + [ + 1603, + 55.1 + ], + [ + 2180, + 66.0 + ], + [ + 2601, + 72.1 + ], + [ + 3054, + 77.1 + ] + ] + }, + "dark_crop": { + "x": 40, + "y": 360, + "size": 400, + "mean_green_12bit": 194.1, + "green_range_12bit": [ + 129, + 557 + ], + "distinct_green_levels": { + "12-bit": 272, + "8-bit same frame": 25, + "camera RGB8": 22 + } + }, + "mid_crop": { + "x": 3800, + "y": 1300, + "size": 400, + "mean_green_12bit": 1606.3 + }, + "sizes_mb": { + "12-bit d0": 38.58, + "12-bit d0.3": 3.4, + "12-bit d0.5": 2.13, + "12-bit d1.0": 0.92, + "8-bit d0": 15.53, + "8-bit d0.3": 2.51, + "8-bit d0.5": 1.54, + "8-bit d1.0": 0.63, + "8-bit values, 12-bit file d0": 39.04, + "8-bit values, 12-bit file d0.3": 3.5, + "8-bit values, 12-bit file d0.5": 2.19, + "8-bit values, 12-bit file d1.0": 0.94, + "camera RGB8 d0": 13.54, + "camera RGB8 d0.3": 2.64, + "camera RGB8 d0.5": 1.65, + "camera RGB8 d1.0": 0.66, + "camera RGB8 today e4 d0.8": 0.92 + }, + "errors": [ + { + "source": "12-bit", + "distance": 0, + "size_mb": 38.58, + "dark (< 300 DN)": { + "rms_px": 0.0, + "rms_avg": 0.0 + }, + "mid (300-1000 DN)": { + "rms_px": 0.0, + "rms_avg": 0.0 + }, + "bright (>= 1000 DN)": { + "rms_px": 0.0, + "rms_avg": 0.0 + } + }, + { + "source": "12-bit", + "distance": 0.3, + "size_mb": 3.4, + "dark (< 300 DN)": { + "rms_px": 12.28, + "rms_avg": 0.61 + }, + "mid (300-1000 DN)": { + "rms_px": 18.0, + "rms_avg": 0.94 + }, + "bright (>= 1000 DN)": { + "rms_px": 35.8, + "rms_avg": 2.53 + } + }, + { + "source": "12-bit", + "distance": 0.5, + "size_mb": 2.13, + "dark (< 300 DN)": { + "rms_px": 14.29, + "rms_avg": 0.92 + }, + "mid (300-1000 DN)": { + "rms_px": 20.81, + "rms_avg": 1.42 + }, + "bright (>= 1000 DN)": { + "rms_px": 39.92, + "rms_avg": 3.79 + } + }, + { + "source": "12-bit", + "distance": 1.0, + "size_mb": 0.92, + "dark (< 300 DN)": { + "rms_px": 16.77, + "rms_avg": 1.64 + }, + "mid (300-1000 DN)": { + "rms_px": 24.29, + "rms_avg": 2.56 + }, + "bright (>= 1000 DN)": { + "rms_px": 43.02, + "rms_avg": 5.98 + } + }, + { + "source": "8-bit", + "distance": 0, + "size_mb": 15.53, + "dark (< 300 DN)": { + "rms_px": 4.6, + "rms_avg": 0.64 + }, + "mid (300-1000 DN)": { + "rms_px": 4.61, + "rms_avg": 0.63 + }, + "bright (>= 1000 DN)": { + "rms_px": 4.61, + "rms_avg": 0.63 + } + }, + { + "source": "8-bit", + "distance": 0.3, + "size_mb": 2.51, + "dark (< 300 DN)": { + "rms_px": 17.35, + "rms_avg": 1.6 + }, + "mid (300-1000 DN)": { + "rms_px": 21.66, + "rms_avg": 1.53 + }, + "bright (>= 1000 DN)": { + "rms_px": 29.53, + "rms_avg": 1.59 + } + }, + { + "source": "8-bit", + "distance": 0.5, + "size_mb": 1.54, + "dark (< 300 DN)": { + "rms_px": 18.59, + "rms_avg": 2.18 + }, + "mid (300-1000 DN)": { + "rms_px": 23.9, + "rms_avg": 2.13 + }, + "bright (>= 1000 DN)": { + "rms_px": 34.86, + "rms_avg": 2.29 + } + }, + { + "source": "8-bit", + "distance": 1.0, + "size_mb": 0.63, + "dark (< 300 DN)": { + "rms_px": 20.45, + "rms_avg": 3.7 + }, + "mid (300-1000 DN)": { + "rms_px": 26.71, + "rms_avg": 3.44 + }, + "bright (>= 1000 DN)": { + "rms_px": 41.05, + "rms_avg": 3.99 + } + }, + { + "source": "8-bit values, 12-bit file", + "distance": 0, + "size_mb": 39.04, + "dark (< 300 DN)": { + "rms_px": 4.6, + "rms_avg": 0.64 + }, + "mid (300-1000 DN)": { + "rms_px": 4.61, + "rms_avg": 0.63 + }, + "bright (>= 1000 DN)": { + "rms_px": 4.61, + "rms_avg": 0.63 + } + }, + { + "source": "8-bit values, 12-bit file", + "distance": 0.3, + "size_mb": 3.5, + "dark (< 300 DN)": { + "rms_px": 12.72, + "rms_avg": 0.88 + }, + "mid (300-1000 DN)": { + "rms_px": 18.29, + "rms_avg": 1.14 + }, + "bright (>= 1000 DN)": { + "rms_px": 35.88, + "rms_avg": 2.61 + } + }, + { + "source": "8-bit values, 12-bit file", + "distance": 0.5, + "size_mb": 2.19, + "dark (< 300 DN)": { + "rms_px": 14.55, + "rms_avg": 1.12 + }, + "mid (300-1000 DN)": { + "rms_px": 20.99, + "rms_avg": 1.57 + }, + "bright (>= 1000 DN)": { + "rms_px": 39.96, + "rms_avg": 3.83 + } + }, + { + "source": "8-bit values, 12-bit file", + "distance": 1.0, + "size_mb": 0.94, + "dark (< 300 DN)": { + "rms_px": 16.87, + "rms_avg": 1.76 + }, + "mid (300-1000 DN)": { + "rms_px": 24.36, + "rms_avg": 2.62 + }, + "bright (>= 1000 DN)": { + "rms_px": 43.03, + "rms_avg": 6.01 + } + } + ] +} \ No newline at end of file diff --git a/docs/raw12-pipeline-measurements.md b/docs/raw12-pipeline-measurements.md index 05e4965..f1e2c69 100644 --- a/docs/raw12-pipeline-measurements.md +++ b/docs/raw12-pipeline-measurements.md @@ -25,12 +25,16 @@ tests; waiting for exposure and gain to stop changing never did. The extra bits themselves add less than expected. In the daylight scene measured, the sensor's own noise was 6–17 times larger than the error from rounding to 8 bits, which raised the total noise by at most 1.5%; and JPEG XL compression at distance 1.0 adds about 13 times more error than that -rounding. 12 bits are expected to make a meaningful difference only where part of the image is dark -while the gain is low, as in high-contrast scenes (a bright sky over shaded forest or a backlit ridge -at dawn or dusk), where faint smoke over dark terrain is a small change in a dark value; and only if -those values are saved with little or no loss. These conditions were not measured. The 12-bit format -costs little against 8-bit raw (same file size, about 10% more processing time), but the camera -runs at 9.86 instead of 14.75 fps. +rounding. A dark, high-contrast scene from the Stäffelsberg tower (shaded forest under a hazy sky) +was measured as well: there the noise in the dark third of the frame is below one 8-bit step, and +rounding to 8 bits raises it by 10–26%, but averaged over an area the 8-bit values still keep the +local brightness to 0.6 DN (12-bit scale). The larger difference comes from the encoder: saved at +distance 1.0, an 8-bit file breaks the shaded forest into flat blocks and keeps its local brightness +2.3 times less accurately than a 12-bit file, and today's RGB8 file shows the same blocks at the same +size as the 12-bit file. The same 8-bit values saved as a 12-bit file behave almost like 12-bit data, +so the gain comes mostly from the file's declared bit depth, not from the four extra bits. The +12-bit format costs little against 8-bit raw (about 10% more processing time; the same file size in +daylight, 46% larger in the dark scene), but the camera runs at 9.86 instead of 14.75 fps. ## Units and abbreviations @@ -340,6 +344,7 @@ session.* | 4 Oct 16:20–16:41 | 111 | camera tests | 21 | tower 111's own camera connected but idle | | 4 Oct 16:41–18:19 | 111 | processing, pile-up, heat | 98 | | | 4 Oct 23:03–23:32 | 121 and 111 | processing on the shared frame, both towers simultaneously | 29 | no camera needed | +| 7 Oct, morning | Stäffelsberg | one `BayerRG12p` and one RGB8 frame of a dark, high-contrast view (Section 3.9.2) | – | hazy; gain and exposure not recorded | --- @@ -617,6 +622,13 @@ Full-size `.jxl` files are kept outside the repository in `tools/camera-bench/ex ### 3.9 Information content of the 12-bit data +The extra four bits were examined in two scenes: the daylight scene of the main campaign (2 and +4 October, Section 3.9.1), where noise is large, and a dark, high-contrast scene photographed from +the Stäffelsberg tower on 7 October (Section 3.9.2), where large parts of the frame are dark at low +noise. Section 3.9.3 combines the two. + +#### 3.9.1 Daylight scene (2 and 4 October) + ![Noise vs brightness](images/raw12/noise_vs_signal.png) ***Figure 12.** Sensor noise σ (DN, 12-bit scale) against signal level (DN, 12-bit scale), measured @@ -656,45 +668,195 @@ and the dropped bits hold real scene detail. has a PSNR of 36.6 dB (Table 16), an RMS error of about 61 DN, which is 13 times the rounding error; even at distance 0.3 (40.3 dB) it is about 40 DN, and raw planes at distance 0.5 (47.2 dB) about 18 DN. Much of this error is noise the encoder discards, and JPEG XL spends relatively more -precision on dark tones, so the error in shadows is smaller than these averages; it was not measured -separately. Still, with lossy files the compression, not the bit depth, decides how much fine tonal -detail survives. Correspondingly, 12-bit and 8-bit raw give the same file size at distance 1.0 -(2.65 and 2.66 MB, Table 14). - -**Where 12 bits are expected to matter.** The noise falls with the signal, while the 8-bit step -stays at 16 DN. Extrapolating Figure 12, the noise drops below one 8-bit step at about 300 DN at -0 dB gain, or 100 DN at ≈ 15 dB. The 8-bit data is linear (no gamma curve), so this region, the -darkest 2–7% of the range, holds only 6–19 of the 256 8-bit levels. Gain multiplies the noise, so at -high gain the noise exceeds one 8-bit step again almost everywhere. Table 17b combines these -observations. - -***Table 17b.** Situations in which 12-bit data is expected to differ from 8-bit data. Signal in DN -on the 12-bit scale (0–4095). "Measured" refers to this campaign.* - -| Situation | Signal (DN), gain (dB) | Noise against one 8-bit step | Expected effect of 12 bits | Measured | -|---|---|---|---|---| -| Daylight, mid-tones and highlights (lit terrain, sky) | 700–4095, 0–15 | 1.6–5 steps | none measurable: total noise +0.2–1.5% | yes | -| Shadows in a high-contrast scene at low gain: shaded forest under a bright sky, backlit ridges, valleys at dawn and dusk | below ≈ 300 at 0 dB, ≈ 100 at 15 dB | below 1 step | 8 bits merge neighbouring levels; 12 bits keep small changes in dark areas, such as thin smoke over dark terrain | no (extrapolated) | -| Uniformly dark scene at high gain: night, gain up to 48 dB | any, high (up to 48) | above 1 step (gain amplifies noise) | little, except in the very darkest values | no | -| Strong processing of dark areas: brightening by 3 stops (×8) or more, contrast stretching | dark areas, any | – | 12 bits delay visible banding | 2 stops (×4) only: no difference (Figure 13) | -| Saving lossy at distance 1.0 | any | compression error ≈ 61 DN ≫ rounding 4.6 DN | largely lost to compression; kept in lossless files | yes, overall PSNR only | - -The case most relevant to fire detection is the second: in a scene exposed for a bright sky, smoke -rising in front of shaded forest is a small brightness increase in an already dark area, at low gain. -Whether 12 bits help there, and whether that benefit survives distance 1.0, requires a test on such a -scene (a dawn or dusk frame with dark terrain under a bright sky, compared as in Figures 12 and 13). +precision on dark tones, so the error in shadows is smaller than these averages (measured on the +dark scene in Table 17d). Still, with lossy files the compression, not the bit depth, decides how +much fine tonal detail survives. Correspondingly, 12-bit and 8-bit raw give the same file size at +distance 1.0 on this scene (2.65 and 2.66 MB, Table 14). ![8 vs 12 bit at 15 dB](images/raw12/examples/121/shadow_8_vs_12.png) ![8 vs 12 bit at 0 dB](images/raw12/examples/lowgain/shadow_8_vs_12.png) -***Figure 13.** The same darker crop brightened by 2 stops (pixel values multiplied by 4), saved as -8-bit (left; the 12-bit value shifted down by 4 bits, i.e. divided by 16) and as 12-bit (right): (a, -top) ≈ 15 dB gain, 227 distinct green levels in 8-bit against 3138 in 12-bit; (b, bottom) 0 dB gain. -No visible difference: the noise is larger than the extra levels.* +***Figure 13a.** The darkest textured crop of the daylight scene (mean ≈ 810 DN) brightened by +2 stops (pixel values multiplied by 4), saved as 8-bit (left; the 12-bit value shifted down by +4 bits, i.e. divided by 16) and as 12-bit (right): top, ≈ 15 dB gain, 227 distinct green levels in +8-bit against 3138 in 12-bit; bottom, 0 dB gain. No visible difference: the noise is larger than the +extra levels.* At 0 dB gain the same settings produce 25–30% smaller files and about 3 dB higher PSNR (less noise to encode): 12-bit colour at d1.0 is 1.9 MB, PSNR 40.0 dB, SSIM 0.987. +#### 3.9.2 Dark, high-contrast scene (Stäffelsberg, 7 October) + +**Scene and method.** On the morning of 7 October 2026, an Alvium 1800 U-1620c (5328 × 3040) on the +Stäffelsberg tower took two frames of the same view, one after the other: hazy sky and a distant valley above a +slope of shaded forest (Figure 13b). One was taken as `BayerRG12p`, converted to colour by the camera's +viewer program and saved as a 16-bit TIFF (the 12-bit values in the top 12 bits); the other as the camera's +RGB8 (today's format), saved as PNG. Gain and exposure were not recorded with the files. The 12-bit +frame spans 128–2792 DN (green), with nothing clipped: a third of it is darker than 300 DN, the +region where Section 3.9.1 expected 12 bits to matter. + +The camera's RGB8 frame is a separate capture (trees move between frames) made by the camera's own +colour processing, so it is compared by eye and by statistics only. For an exact comparison of bit +depth, an **8-bit version of the same frame** was made by keeping the top 8 of the 12 bits (the +12-bit value divided by 16, rounded down, as `BayerRG8` delivers), and compared pixel by pixel with +the 12-bit original. The 12-bit frame was converted to colour by the viewer, not by the tower's +edge-aware conversion, so its noise is not directly comparable with Figure 12 (measured on the raw +green pixels); for comparison, the October frames' lossless 12-bit colour files were measured in +the same way. Compression used libjxl 0.12.0 on the laptop with the tower's settings (effort 3; +12-bit and 8-bit input as declared by their bit depth), single runs. Script: +`tools/camera-bench/compare_8_vs_12.py`; results in +`docs/images/raw12/examples/staeffelsberg/results.json`. + +![Stäffelsberg overview](images/raw12/examples/staeffelsberg/overview.jpg) + +***Figure 13b.** The Stäffelsberg 12-bit frame, shown upright (the frame is rotated by 90° so that +the sky is at the top), at a quarter of full size and with a display brightness curve (sRGB) so that +the forest is visible. Blue: the dark crop of Figures 13d and 13f (shaded forest, mean 194 DN). +Red: the mid-tone crop of Figure 13e (distant valley in haze, mean 1606 DN).* + +**Noise.** The noise in each colour image was measured as the standard deviation of each pixel +around its local average (a Gaussian average over about 2 pixels), in 24 × 24-pixel patches grouped +by brightness, taking the quietest 10% of each group as in Section 2.11; the result was calibrated +on generated noise of known σ. + +![Noise vs brightness, Stäffelsberg](images/raw12/examples/staeffelsberg/noise_vs_signal.png) + +***Figure 13c.** Noise σ (DN, 12-bit scale) against signal (DN, 12-bit scale, green) in the colour +images: the Stäffelsberg 12-bit frame (blue), its 8-bit version (orange), the camera's RGB8 frame of +the same view (purple, another capture, multiplied by 16 to the 12-bit scale), and the October 0 dB +and ≈ 15 dB frames measured the same way (dashed). Dashed line: one 8-bit step (16 DN); dotted line: +the RMS error of rounding to 8 bits (4.6 DN). The bump at ≈ 340 DN is scene texture that remains +even in the quietest patches.* + +***Table 17c.** Noise σ (DN, 12-bit scale) in the Stäffelsberg colour images at selected signal +levels (DN, 12-bit scale, green). In 8-bit steps: the 12-bit noise divided by 16; below 1, an 8-bit +value cannot record the variation the sensor delivers. Increase: how much rounding to 8 bits raises +the noise. Camera RGB8: another capture, the camera's own colour processing.* + +| Signal (DN) | 12-bit noise (DN) | In 8-bit steps | 8-bit version, same frame (DN) | Increase from rounding | Camera RGB8 (DN) | +|---|---|---|---|---|---| +| ≈ 160 | 5.4 | 0.34 | 6.4 | +19% | 9.6 | +| ≈ 185 | 6.2 | 0.39 | 7.8 | +26% | 10.5 | +| ≈ 230 | 7.9 | 0.49 | 9.3 | +18% | 12.2 | +| ≈ 275 | 10.4 | 0.65 | 11.4 | +10% | 14.6 | +| ≈ 450 | 11.2 | 0.70 | 12.3 | +10% | 15.8 | +| ≈ 740 | 13.9 | 0.87 | 14.8 | +6% | 23.1 | +| ≈ 1300 | 16.2 | 1.01 | 16.9 | +4% | 24.2 | +| ≈ 2250 | 20.5 | 1.28 | 21.0 | +2% | 32.0 | + +In this frame the noise stays below one 8-bit step up to about 1300 DN, which covers about 70% of +the frame. At ≈ 700 DN the October frames, measured the same way, have 17 DN (0 dB) and 33 DN +(≈ 15 dB) against 14 DN here, and they contain nothing darker. Rounding to 8 bits raises the noise +in the forest by 10–26%, against at most 1.5% in the daylight scene. The camera's RGB8 frame is +1.4–1.8 times noisier than the 12-bit frame at every brightness, probably from the camera's own +colour processing; this is a difference of the camera's conversion, not of bit depth. + +**Levels and visibility.** The 400 × 400-pixel dark crop (green 129–557 DN) uses 272 distinct green +levels in 12-bit, 25 in its 8-bit version and 22 in the camera's RGB8. + +![Dark crop, 8 vs 12 bit](images/raw12/examples/staeffelsberg/dark_crop.png) + +***Figure 13d.** The dark crop (shaded forest, mean 194 DN) at 100%, brightened by 2 stops (pixel +values ×4, top) and 4 stops (×16, bottom). Left: the camera's RGB8 frame (another capture, taken +separately; branches moved). Middle: the 8-bit version of the 12-bit frame (top 8 bits). Right: +the 12-bit frame. Not compressed.* + +![Mid-tone crop, 8 vs 12 bit](images/raw12/examples/staeffelsberg/mid_crop.png) + +***Figure 13e.** Control: the mid-tone crop (distant valley in haze, mean 1606 DN, noise about one +8-bit step) with its contrast stretched 4 times around the crop's mean, 8-bit version (left) and +12-bit (right). Not compressed. No visible difference.* + +Uncompressed, the 8-bit forest is slightly grainier than the 12-bit one at +4 stops, with no banding +(Figure 13d): even at 0.3–0.5 of a step the noise still dithers the rounding. Averaged over about +200 pixels (a Gaussian of 4 pixels), the 8-bit version differs from the 12-bit frame by 0.6 DN RMS +in every brightness range (Table 17d): the local brightness, which is what a faint, even haze such as +thin smoke changes, survives rounding to within less than a tenth of an 8-bit step. + +**Compression.** Each version was saved as JPEG XL and compared with the 12-bit frame in three +brightness regions: dark (below 300 DN, 33% of the frame), mid (300–1000 DN, 39%) and bright +(1000 DN and above, 28%). Two errors are given: per pixel, which includes noise the encoder +discards; and of the local average, after averaging both images over about 200 pixels, which +measures lost scene brightness rather than lost noise. A third version tests the encoder rather than +the data: the same 8-bit values, stored as a 12-bit file (each value × 16 + 8). + +***Table 17d.** File size (MB) and RMS error (DN, 12-bit scale) against the 12-bit frame, per +brightness region, for the 12-bit frame, its 8-bit version, and its 8-bit values stored in a 12-bit +file, at JPEG XL distance 0 (lossless), 0.3, 0.5 and 1.0, effort 3. Per pixel: the error at each +pixel. Local average: the error after averaging both images over about 200 pixels (Gaussian, +σ = 4 pixels). Today's file (camera RGB8, d0.8, effort 4) is 0.92 MB; the camera RGB8 frame at d1.0 +is 0.66 MB (another capture, so no error can be computed).* + +| File | Distance | Size (MB) | Per pixel, dark / mid / bright (DN) | Local average, dark / mid / bright (DN) | +|---|---|---|---|---| +| 12-bit | lossless | 38.6 | 0 / 0 / 0 | 0 / 0 / 0 | +| 12-bit | 0.3 | 3.40 | 12.3 / 18.0 / 35.8 | 0.6 / 0.9 / 2.5 | +| 12-bit | 0.5 | 2.13 | 14.3 / 20.8 / 39.9 | 0.9 / 1.4 / 3.8 | +| **12-bit** | **1.0** | **0.92** | **16.8 / 24.3 / 43.0** | **1.6 / 2.6 / 6.0** | +| 8-bit | lossless | 15.5 | 4.6 / 4.6 / 4.6 | 0.6 / 0.6 / 0.6 | +| 8-bit | 0.3 | 2.51 | 17.4 / 21.7 / 29.5 | 1.6 / 1.5 / 1.6 | +| 8-bit | 0.5 | 1.54 | 18.6 / 23.9 / 34.9 | 2.2 / 2.1 / 2.3 | +| **8-bit** | **1.0** | **0.63** | **20.4 / 26.7 / 41.0** | **3.7 / 3.4 / 4.0** | +| 8-bit values, 12-bit file | lossless | 39.0 | 4.6 / 4.6 / 4.6 | 0.6 / 0.6 / 0.6 | +| 8-bit values, 12-bit file | 0.3 | 3.50 | 12.7 / 18.3 / 35.9 | 0.9 / 1.1 / 2.6 | +| 8-bit values, 12-bit file | 0.5 | 2.19 | 14.6 / 21.0 / 40.0 | 1.1 / 1.6 / 3.8 | +| **8-bit values, 12-bit file** | **1.0** | **0.94** | **16.9 / 24.4 / 43.0** | **1.8 / 2.6 / 6.0** | + +![Dark crop after compression](images/raw12/examples/staeffelsberg/dark_compressed.png) + +***Figure 13f.** The dark crop of Figure 13d after saving, brightened by 4 stops (×16): (a) the +camera's RGB8 frame saved as today (distance 0.8, effort 4; another capture); (b) the 8-bit version +saved as an 8-bit file at distance 1.0; (c) the same 8-bit values saved as a 12-bit file at distance +1.0; (d) the 12-bit frame at distance 1.0 (the proposed setting). Effort 3 unless stated.* + +At distance 1.0, the 8-bit file loses the shadow detail: the forest breaks into flat blocks +(Figure 13f b), and the local brightness in the dark region is off by 3.7 DN against 1.6 DN for the +12-bit file. Today's RGB8 file shows the same blocks (Figure 13f a), although it is as large as the +12-bit file (0.92 MB). In the bright region the order reverses (4.0 against 6.0 DN): an 8-bit file +gets about the same error at every brightness, a 12-bit file a smaller error in the dark and a larger +one in the bright. + +This is a property of the encoder, not of the four extra bits: the same 8-bit values saved as a +12-bit file behave almost exactly like the 12-bit frame (1.8 against 1.6 DN in the dark, 0.94 against +0.92 MB, Figure 13f c). How the bits are spent depends on the bit depth the file declares. As a +consequence, on this scene the 12-bit file at distance 1.0 is 46% larger than the 8-bit file +(0.92 against 0.63 MB), whereas in the daylight scene the two were the same size (Table 14); dark +frames are in any case far smaller than the 2.65 MB of the daylight frame used for the storage +estimate (Section 3.12). + +In summary, for this dark scene: (1) the four extra bits themselves change little after saving: +1.6 against 1.8 DN of local-brightness error in the dark at distance 1.0, and 0.6 DN when 8-bit +values are kept losslessly; (2) saving as a 12-bit file keeps about 2.3 times more accurate shadow +brightness than saving as an 8-bit file at the same distance, and visibly more shadow detail than +today's RGB8 file at the same size; (3) per pixel, rounding to 8 bits raises the noise in the dark +by 10–26%, which matters only for lossless or near-lossless files. + +#### 3.9.3 Where 12 bits matter + +The noise falls with the signal, while the 8-bit step stays at 16 DN. In the daylight scene the +noise was above one 8-bit step everywhere (Figure 12); in the Stäffelsberg scene it was below one +step up to about 1300 DN, over 70% of the frame (Figure 13c). Gain multiplies the noise, so at high +gain the noise exceeds one 8-bit step again almost everywhere. Table 17b combines these +observations. + +***Table 17b.** Situations in which 12-bit data differs from 8-bit data. Signal in DN on the 12-bit +scale (0–4095). "Measured" refers to this campaign.* + +| Situation | Signal (DN), gain (dB) | Noise against one 8-bit step | Effect of 12 bits | Measured | +|---|---|---|---|---| +| Daylight, mid-tones and highlights (lit terrain, sky) | 700–4095, 0–15 | 1.6–5 steps | none measurable: total noise +0.2–1.5% | yes (3.9.1) | +| Shadows in a high-contrast scene at low gain: shaded forest under a bright sky, backlit ridges, valleys at dawn and dusk | below ≈ 1300 in the Stäffelsberg frame | 0.3–1 step | 25 instead of 272 levels in the dark crop, +10–26% noise per pixel; local brightness kept by 8-bit values to 0.6 DN; no visible banding at +4 stops | yes (3.9.2) | +| Uniformly dark scene at high gain: night, gain up to 48 dB | any, high (up to 48) | above 1 step (gain amplifies noise) | little, except in the very darkest values | no | +| Strong processing of dark areas: brightening by 3 stops (×8) or more, contrast stretching | dark areas, any | – | 12 bits delay visible banding | +2 stops in daylight, +4 stops at Stäffelsberg: no banding, 8-bit slightly grainier (Figures 13a, 13d) | +| Saving lossy at distance 1.0 | any | compression error ≈ 17–61 DN per pixel ≫ rounding 4.6 DN | an 8-bit file loses shadow detail (blocks) and 2.3 times more local brightness in the dark than a 12-bit file; caused by the declared file bit depth, not by the missing bits | yes (3.9.2, Table 17d) | + +The case most relevant to fire detection is the second: smoke rising in front of shaded forest is a +small brightness increase in an already dark area, at low gain. The Stäffelsberg frame is such a +scene. There, the proposed 12-bit file at distance 1.0 keeps the local brightness of the shaded +forest to 1.6 DN RMS and its texture visibly better than today's RGB8 file of the same size; most of +that gain comes from saving a 12-bit file, and would also be available to 8-bit data saved as a +12-bit file. Whether these differences change smoke detection was not measured. + ### 3.10 Saving throughput and pile-up ![Saves per second, same frame](images/raw12/pace_common.png) @@ -797,7 +959,7 @@ The towers have 11–22 GB of free storage, so images must leave the tower conti | Processing | Unpack + colour conversion 38 ms (IOTA) / 50 ms (Delta); a 12-bit save 518 / 820 ms, slightly faster than today's RGB8 save (567 / 872 ms). | 3.5 | | Computers | The Delta takes 1.6 times as long on the identical frame. | 3.5 | | Quality | d1.0: 2.65 MB, SSIM 0.973. Raw planes keep sensor values much closer (47 vs 39 dB at 6 MB). | 3.7 | -| 12 bits | In daylight, rounding to 8 bits adds at most 1.5% to the noise, and compression at d1.0 adds 13 times more error than rounding. 12 bits are expected to matter only in dark areas at low gain (high-contrast scenes), and only in lightly compressed files; not measured. | 3.9 | +| 12 bits | In daylight, rounding to 8 bits adds at most 1.5% to the noise, and compression at d1.0 adds 13 times more error than rounding. In a dark, high-contrast scene (Stäffelsberg) rounding adds 10–26% noise in the shadows; at d1.0 an 8-bit file loses shadow detail and 2.3 times more shadow brightness than a 12-bit file, mostly because of the file's declared bit depth, not the extra bits. Today's RGB8 file shows the same loss. | 3.9 | | Throughput | IOTA keeps up with a frame every 0.7 s with one worker; the Delta needs two. | 3.10 | | Heat | One frame every 2 s: IOTA 53 °C / 5.1 W, Delta 66 °C / 5.7 W. | 3.11 | | Storage | 12-bit colour d1.0: 2.7 MB per frame, below today's 3.3–4.0 MB. | 3.12 | @@ -831,6 +993,10 @@ between neighbouring waypoints, which decides the real pace, remains to be measu - One scene on two afternoons, at ≈ 15 dB gain in the main runs, plus one 0 dB frame. Night conditions were not measured. +- The dark-scene comparison (Section 3.9.2) rests on one pair of frames, without recorded gain and + exposure; its 12-bit frame was converted to colour by the camera's viewer, not by the tower, and + compressed on a laptop (libjxl 0.12.0) rather than on a tower (0.11). Its 8-bit version is the + 12-bit colour image cut to 8 bits, not a `BayerRG8` frame converted to colour. - Auto-exposure steps were forced on a still camera, not caused by real slews. Real waypoint-to- waypoint changes are usually much smaller than ×16, so real settling should be shorter. - The tower program was stopped, so its own load (camera stream, control loop) is not included; the @@ -854,9 +1020,11 @@ between neighbouring waypoints, which decides the real pace, remains to be measu 4. **Keep a waypoint's frame when brightness is in the camera's target band and steady for 3 frames**, with a time limit. 5. **12-bit versus raw 8-bit is a choice, not a cost.** `BayerRG8` (14.75 fps) settles about 1 s - faster in daylight and loses nothing measurable there; `BayerRG12p` is expected to help in dark areas - of high-contrast scenes at low gain, such as shaded terrain under a bright sky at dawn or dusk - (Section 3.9), which remains to be tested. The pixel format is part of the camera profile, so the program should accept both. + faster in daylight and loses nothing measurable there. In the dark areas of a high-contrast scene + (Section 3.9.2), a 12-bit file at distance 1.0 keeps clearly more shadow detail than an 8-bit + file, but mainly because of how the file is encoded: if `BayerRG8` is used, its colour image + should be saved as a 12-bit (or 16-bit) JPEG XL file, which keeps nearly the same shadow detail. + The pixel format is part of the camera profile, so the program should accept both. 6. **Store raw 12-bit planes losslessly (17–18 MB per frame) only if the detection pipeline needs exact sensor values**; it requires about 6 times the storage. @@ -867,7 +1035,8 @@ between neighbouring waypoints, which decides the real pace, remains to be measu 1. ISO 19567-2:2019. *Photography — Digital cameras — Texture reproduction measurements — Part 2: Measurements using dead leaves pattern.* International Organization for Standardization. 2. ISO/IEC 18181. *Information technology — JPEG XL image coding system*; reference implementation - libjxl, versions 0.11.0 and 0.11.1, https://github.com/libjxl/libjxl. + libjxl, versions 0.11.0 and 0.11.1 (towers) and 0.12.0 (dark-scene comparison, Section 3.9.2), + https://github.com/libjxl/libjxl. 3. OpenCV, versions 4.5.4 and 4.10: `cvtColor` colour conversions, Bayer demosaicing (bilinear and edge-aware), https://docs.opencv.org. 4. EMVA. *GenICam Pixel Format Naming Convention (PFNC)* and *GenICam Standard Features Naming @@ -892,6 +1061,10 @@ between neighbouring waypoints, which decides the real pace, remains to be measu `tools/camera-bench/examples-*`. - Figures: `plot_results.py`, `plot_common.py` (charts), `make_figures.py` (example crops); quality scores: `compare_examples.py`. +- Dark-scene comparison (Section 3.9.2): `compare_8_vs_12.py`; figures and + [results.json](images/raw12/examples/staeffelsberg/results.json) in + `docs/images/raw12/examples/staeffelsberg/`. The two source frames (`BayerRG12p.tif`, 97 MB; + `RGB8.png`, 17 MB) are kept outside the repository, in `8bit_vs_12bit_test/` next to it. ## Appendix B. Processing times on each tower's own frame diff --git a/docs/raw12-pipeline-measurements_llm_context.md b/docs/raw12-pipeline-measurements_llm_context.md index c644f61..4eec8b9 100644 --- a/docs/raw12-pipeline-measurements_llm_context.md +++ b/docs/raw12-pipeline-measurements_llm_context.md @@ -4,7 +4,7 @@ This file gives an AI assistant the background needed to discuss the report [raw12-pipeline-measurements.md](raw12-pipeline-measurements.md): what was asked, how the tests were run, what the numbers mean, what was decided, which points have been questioned, and what is still open. Read the report itself for the full tables and figures; this file explains around them. -State as of 2026-10-05, branch `raw12-capture`. +State as of 2026-10-07, branch `raw12-capture` (dark-scene comparison added 2026-10-07). ## 1. The system in one paragraph @@ -151,9 +151,26 @@ have 11–22 GB free, so images must leave the tower continuously. backlit ridges under a bright sky at dawn or dusk; thin smoke over dark terrain is the relevant fire-detection case. At night with high gain, noise is amplified above the step again, so little benefit. Strong shadow lifting (≥ 3 stops) untested; 2 stops showed no difference. -- None of the dark cases were measured (the darkest area in the scene was ≈ 700 DN; a dusk attempt - on 121 needed 24 dB and was discarded). Proposed test: a dawn/dusk frame with dark terrain under a - bright sky, compared as in Figures 12 and 13. +- The October scene had nothing darker than ≈ 700 DN (a dusk attempt on 121 needed 24 dB and was + discarded). On 7 Oct the user took a dark, high-contrast pair at the Stäffelsberg tower (shaded + forest under hazy sky, same 1620c model): `BayerRG12p` converted to colour by the camera viewer and + saved as 16-bit TIFF (12-bit values << 4), and a separate RGB8 capture as PNG; files in + `../8bit_vs_12bit_test/` (outside the repo). Gain/exposure not recorded. Report Section 3.9.2, + script `tools/camera-bench/compare_8_vs_12.py`, results.json in + `docs/images/raw12/examples/staeffelsberg/`. +- Stäffelsberg findings: 33% of the frame < 300 DN; noise in the colour image (std around a local + mean, quietest 10% of 24 × 24 patches, calibrated) 5–15 DN below ≈ 1300 DN, i.e. below one 8-bit + step over ~70% of the frame. Rounding to 8 bits: +10–26% noise in the forest; dark crop 272 vs 25 + green levels (camera RGB8 22); local-average error (Gaussian σ 4 px) only 0.6 DN; no visible + banding at +4 stops, 8-bit slightly grainier. Camera RGB8 1.4–1.8× noisier than the 12-bit colour + image (camera's own processing). +- Compression is where it shows: at d1.0 the 8-bit file has 3.7 DN local-average error in the dark + vs 1.6 DN for 12-bit (bright: 4.0 vs 6.0), visibly blocky shadows; today's RGB8 d0.8 e4 file + (0.92 MB) is equally blocky. The same 8-bit values stored in a 12-bit file give 1.8 DN and + 0.94 MB: the effect is libjxl spending bits by declared bit depth, not the four extra bits. Sizes + on this scene at d1.0: 12-bit 0.92 MB, 8-bit 0.63 MB (+46%; equal in daylight). Implication: if + BayerRG8 were ever used, save its colour as a 12/16-bit JPEG XL. +- Not yet tested: real smoke; night at high gain; the tower's own de-Bayer on a dark frame. - 12-bit vs 8-bit raw costs little in saving (same size, ~10% more time) but the camera runs at 9.86 vs 14.75 fps, ~1 s slower settling in daylight. The main gain over today is frame rate, not bit depth. @@ -178,7 +195,7 @@ N workers, bounded queue; scheduler save gate pauses the scan when the queue is Not done yet: exporting the new camera profiles on a tower (the 1620c is currently on tower 111 and must be moved back to 121 first); field verification with real slews; checking that downstream -consumers decode 12-bit files; the dawn/dusk 12-bit test. The branch is deployed on tower 111, +consumers decode 12-bit files. The branch is deployed on tower 111, whose boot service still passes old `--init/--start` flags and needs `scripts/install-tower`. ## 10. Report structure (for orientation) @@ -187,7 +204,8 @@ Abstract; Units and abbreviations; 1 Introduction (1.1 question, 1.2 pixel forma mosaic vs raw planes, Figures 1a/1b, Table 1; 1.3 earlier work); 2 Materials and methods (2.1–2.12, Tables 2–8, Figure 2); 3 Results (3.1 frame rate, 3.2 auto-exposure, 3.3 keep rules, 3.4 white balance, 3.5 processing per step, 3.6 effort and threads, 3.7 quality setting, 3.8 example images, -3.9 information content of 12-bit data with Tables 17a/17b, 3.10 throughput, 3.11 heat, +3.9 information content of 12-bit data (3.9.1 daylight, 3.9.2 Stäffelsberg dark scene, 3.9.3 +summary; Figures 12, 13a–13f, Tables 17a–17d), 3.10 throughput, 3.11 heat, 3.12 storage; Figures 3–17, Tables 9–20); 4 Discussion (summary Table 21, pace Table 22, limitations); 5 Conclusions and six recommendations; References [1]–[7]; Appendix A raw data and tools; Appendix B per-tower processing times. Figures live in `docs/images/raw12/`. diff --git a/tools/camera-bench/README.md b/tools/camera-bench/README.md index 85eab3a..3a02448 100644 --- a/tools/camera-bench/README.md +++ b/tools/camera-bench/README.md @@ -22,6 +22,7 @@ Not part of the build: CMake only compiles the tools it names. | `run_common_frame.sh` | the processing tests on one shared frame (`common-frame/`, git-ignored, rebuilt from 121's lossless 12-bit example), so towers compare exactly (~25–40 min) | | `plot_common.py` | the shared-frame tables and charts (per-step time, size per quality setting, saves per second); laptop venv | | `compare_examples.py` | scores every 12-bit example against the lossless 12-bit colour image (PSNR, SSIM); laptop venv | +| `compare_8_vs_12.py` | 8-bit against 12-bit on a dark scene: noise, levels, JPEG XL error per brightness region, crop figures (2026-10-07); laptop venv | | `make_figures.py` | example-image figures (crops, difference maps, 8 vs 12-bit shadow) from the example `.jxl` files; laptop venv | | `plot_results.py` | every chart and table of `docs/raw12-pipeline-measurements.md` from the results folders; laptop venv | | `results-*/` | raw results of each run, one folder per tower and day | diff --git a/tools/camera-bench/compare_8_vs_12.py b/tools/camera-bench/compare_8_vs_12.py new file mode 100644 index 0000000..e6b9b38 --- /dev/null +++ b/tools/camera-bench/compare_8_vs_12.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python3 +"""compare_8_vs_12.py - 8-bit against 12-bit on a dark, high-contrast scene. + + ~/.venvs/fgc-plots/bin/python compare_8_vs_12.py \ + [