Steer the day white point from what the frames render - #19
Conversation
The daylight render sat khaki for months because nothing watched the output: day gains were an open-loop constant, and no constant can be right. libcamera infers a colour temperature from the manual gains and swaps its colour matrix as that estimate moves, so rendered colour responds super-linearly to a gain change -- measured live on this camera, a 16% red-gain cut moved the rendered red by 27% -- and the gains that suit clear sky render overcast grey as green-tinged khaki. Close the loop instead. The lores buffer the brightness metrics already read carries the U/V chroma planes; its near-neutral pixels -- grey cloud, grey water, the scene's own grey card -- are selected there and reported as two linear-domain ratios that read 1.0 when greys render grey. The controller multiplies a trim on the day white point by each ratio raised to a small exponent: proportional on the log scale, no CCM calibration to be wrong about, stable while strength times the CCM's slope stays well under one, and moving well below the cross-fade's own smoothing, so nothing steps. The trim is clamped to +/-max_trim around the configured gains -- the loop absorbs weather and CCM drift, it does not fight a sunset -- and updates are gated to day frames, because at night there is nothing neutral to meter and an aurora is the one thing this must never white-balance away. It rides the day endpoint of the existing cross-fade only, so night colour is exactly the configured value, always. A one-line state file survives restarts, sparing the ten-minute re-learn. Off by default; existing configs and the replay golden masters are byte-identical. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 50 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThe change adds opt-in daytime white-balance feedback. Lo-res neutral-pixel metrics drive bounded daylight trims, which persist across restarts and appear in diagnostics. Night and aurora colour remain unchanged. Tests and documentation cover configuration, metrics, control, and persistence. ChangesDaylight white-balance feedback
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant ImageCapture
participant ExposureController
participant daemon
ImageCapture->>ExposureController: Provide lo-res WB metrics
ExposureController->>ExposureController: Update bounded daytime trim
ExposureController-->>daemon: Expose wb_trim
daemon->>daemon: Persist data/wb_trim.json
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
raspilapse/daemon.py (1)
359-404: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd daemon-level tests for WB trim persistence. Cover disabled feedback, missing and malformed state files, clamped restoration, thresholded writes, and the
os.replacepayload.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@raspilapse/daemon.py` around lines 359 - 404, The daemon lacks tests for WB trim persistence. Add daemon-level tests covering _seed_wb_trim and _persist_wb_trim for disabled feedback, missing and malformed state files, clamped restoration, the 0.002 write threshold, and atomic os.replace payload contents, using mocks or temporary paths without changing production behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CHANGELOG.md`:
- Around line 12-24: Replace the inaccurate per-frame correction wording with
the actual default bound of approximately -3.4% to +3.5%, accounting for the
accepted 0.5..2.0 ratios and the ratio ** 0.05 update. Apply this wording update
in CHANGELOG.md lines 12-24, README.md lines 158-161, and docs/EXPOSURE.md lines
164-180; retain any qualification that configured higher strength can produce
larger movements.
---
Nitpick comments:
In `@raspilapse/daemon.py`:
- Around line 359-404: The daemon lacks tests for WB trim persistence. Add
daemon-level tests covering _seed_wb_trim and _persist_wb_trim for disabled
feedback, missing and malformed state files, clamped restoration, the 0.002
write threshold, and atomic os.replace payload contents, using mocks or
temporary paths without changing production behavior.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 9c2978a3-53c7-4996-96a8-f4771888584c
📒 Files selected for processing (9)
CHANGELOG.mdREADME.mddocs/CONFIG-REFERENCE.ymldocs/EXPOSURE.mdraspilapse/camera/capture.pyraspilapse/camera/exposure.pyraspilapse/daemon.pytests/test_exposure.pytests/test_wb_stats.py
Both from review. The docs claimed the trim moves "well under 1% per frame", which is how it behaves against a real cast but not what the code bounds: the input clamp accepts ratios up to 2:1, and 2 ** 0.05 is a 3.5% step. Say both numbers -- the typical and the cap -- instead of a soft phrase that reads as a guarantee. The daemon's half of the trim's restart survival -- seeding from the state file, writing it back only past the threshold -- ran only in production. Cover it: restore, clamp on the way in, malformed and half-missing files, the disabled loop neither reading nor writing, the 0.002 write threshold in both directions, and one full round trip. The state path is patched per test so the suite can never read a running camera's trim. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI's black --check rejected the persistence tests: three short calls wrapped across lines that fit in one. Black is now installed locally, so the next hand-formatting drift gets caught before the push. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Problem
The Spjutvika camera's daylight frames carried a green-tinged khaki cast — grey storm clouds rendered sepia. Measured on live output: neutral areas at G/R 0.93, G/B 1.05 in linear terms (a* +2.5, b* +4.7).
Diagnosis
Day WB is (correctly) manual to avoid AWB flicker, but the gains were an open-loop constant — and no constant can be right. libcamera infers a colour temperature from the manual gains and swaps its CCM as that estimate moves, so rendered colour responds super-linearly to gain changes (measured: a 16% red-gain cut moved rendered red 27%). Gains that suit one weather render another as a cast. The AWB reference shot couldn't help: its fixed 0.1 s exposure blows out white in daylight, so its learned gains were garbage.
Fix: close the loop
day_mode.wb_feedback(off by default) steers a trim on the day white point from what the frames actually render:data/wb_trim.jsonspares the ten-minute re-learn.Verification
{"wb_trim_r": 1.0006, "wb_trim_b": 1.0018}) and holds it as weather changes.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation