feat(sqm): self-calibrating SQM — raw-green photometry, per-frame black level, B−V color term, wing correction#532
Merged
Merged
Conversation
…k level, B-V color term, wing correction SQM previously wandered 0.7-2.4 mag with auto-exposure and shifted with focus. Redesign makes it self-calibrating; plate solving is untouched. - Photometry on the raw green channel (linear), solve stays on the processed image; skip the cycle if the raw frame is unavailable - Black level from a rolling joint fit bg = P0 + rate*exposure over the auto-exposure stream (PedestalEstimator); dark-current slope discarded - Color term V - T*(B-V) per matched star (Hipparcos B-V by HIP id); per-sensor T: 0.8 bare imx462/imx290, 0.0 HQ (IR-cut), imx296 unmeasured - Wing correction: the 5px aperture misses 25-38% of star flux in the lens-halo wings; WingEstimator measures the enclosed fraction via ring-profile slope cutoff, smooths it, applies -2.5*log10(f) to mzero - Remove dead noise-floor wiring from the SQM path: the include_noise_floor_details block never ran, its output key never matched what the solver read, and the estimator was rebuilt per call. shared_state.noise_floor() has always held the 10.0 default; the camera SNR controller keeps running on that. NoiseFloorEstimator stays as a sweep-wizard diagnostic. - Rejected: per-sensor zero-point constant, per-frame adaptive apertures, low-percentile annulus background (all re-introduce exposure or focus dependence; see docs/adr/0020) Validation on 6 exposure sweeps (imx462 + HQ): exposure-flat and focus-flat (std 0.07-0.22), within +/-0.07 mag of the reference meter on 4 of 6 nights with no tuned constants. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VaxurSrCzxrsfJrSjfA5bo
… clamp Flux-weighted mzero hands most of the vote to the 1-2 brightest matched stars — exactly the ones prone to systematic error. On the 2026-07-12 sweep a single near-saturated red giant (B-V 1.47) dragged mzero 0.5 mag and produced a 0.65 mag SQM outlier while the median star agreed with the good nights to 0.03 mag. - mzero is now the median of per-star zero points - stars peaking above 70% of full scale are excluded (CMOS response bends well before hard clip; 75-90% peaks already read low) — same threshold feeds the wing estimator, keeping f and mzero consistent - B-V lookups clamped to [-0.5, 1.2], the range the linear colour term was fitted on Sweep validation: imx462 night-to-night spread 0.63 -> 0.06 mag across two focus states and a Milky Way field; within-sweep std improves to 0.05-0.12; exposure slopes stay flat. The remaining offset vs the reference meter is now a stable per-sensor constant (imx462 +0.43, hq +0.07) consistent with NIR sky emission in the bare sensor passband - to be set by a proper calibration campaign, not from three sweeps. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VaxurSrCzxrsfJrSjfA5bo
The colour term matches the stars to the sensor passband, so the sky is measured in that passband too. A bare sensor sees NIR sky emission (airglow, LED/sodium LP beyond 700nm) that a V-band SQM meter does not, so its sky reads genuinely brighter. sqm_band_offset converts the final SQM back to the meter scale: imx462/imx290 +0.43, hq +0.07, imx296 0.0 (unmeasured). Earlier a similar constant was removed because it papered over 0.6 mag of variable systematics. With the black level, colour term, wing correction and robust mzero in place the residual is now genuinely constant (night-to-night spread 0.06 mag across focus states and star fields), so a per-sensor value is justified. Values remain provisional (3 sweeps imx462, 2 trusted sweeps hq); refine with a multi-night side-by-side campaign against a reference meter aimed at the camera field. Sweep validation with the offset applied: imx462 -0.03/+0.03/+0.01 vs meter; hq +0.03/-0.03 on trusted nights. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VaxurSrCzxrsfJrSjfA5bo
The raw-green SQM pedestal used a placeholder bias_offset of 50 ADU (# TODO: measure), ~188 ADU below the true black level, so on-sky SQM read ~2.5-3 mag too bright (e.g. 15.5 instead of ~18.6). Raw-green black level measured three independent ways, all ~238: - dark-frame calibration (CAL menu, capped): 238.0 - on-sky exposure-sweep intercept (PedestalEstimator P0): 238.6 - rolling estimator fit on the live stream: 238.8 Black level is a per-sensor-model constant (fixed gain 30, libcamera pipeline), so this ships correct to every unit. imx290 shares the sensor/optics.
The per-frame PedestalEstimator supplied a pedestal_override that shadowed the profile/calibration bias_offset. Now that the black level is a measured per-model constant (238 ADU, shipped in the profile and refinable per-unit via the CAL wizard), the estimator only adds risk: under live steady auto-exposure it never gets the >=2x exposure span it needs, and the one time it does condition (a transient ramp) it can fit a low intercept and drive SQM too bright. Its slope is sky-dominated so it can't recover dark current either. Remove the override from the solver; SQM now always uses the profile/calibration bias_offset.
The solver dispatches align-queue commands by type (isinstance(command, ReloadSqmCalibration)), and align.py sends typed objects. The SQM calibration wizard instead enqueued a raw list ['reload_sqm_calibration'], which matched nothing and fell through to 'Unknown solver command' -- so the wizard's reload never fired and new calibration only took effect on a full restart. Send the typed command.
The raw-green photometry path skipped every cycle on the imx462/imx290: their cropped green frame (~490px) tripped a hardcoded 512px floor, so SQM never updated despite good plate-solves. Lower the floor so only missing or malformed frames are rejected; photometry runs at the green frame's own scale. The menubar also rendered the 20.15 default as if it were a reading. Only show a value once one has actually been measured (last_update set). (ported from the nixos branch) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VaxurSrCzxrsfJrSjfA5bo
A monotonic exposure ramp lets temporal sky drift (dawn twilight, passing haze) alias into an apparent exposure dependence when a sweep is analysed — both 2026-07-15 sweeps show it (fake slopes to -0.44 mag/dex from a ~4% sky-rate creep). Random order decorrelates time from exposure; file names carry the exposure so analysis is unaffected. Also record the tested-and-rejected faint-star mzero cut in ADR 0020. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VaxurSrCzxrsfJrSjfA5bo
Sweeps carried no record of the constants they were reduced with. Save the calibration-aware bias_offset, band offset and colour coefficient (and pass camera_type from the sweep capture path, which never did). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VaxurSrCzxrsfJrSjfA5bo
…nstant The offset is (sensor passband, fixed) x (sky spectrum, environmental). Calibrated under LP-dominated suburban sky where it measures constant to ~0.05 mag; expect a different, wandering value under airglow-dominated dark skies. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VaxurSrCzxrsfJrSjfA5bo
The instrument is stable within a night; its only weak number is the absolute scale, which depends on the night sky spectrum through the per-sensor band offset (calibrated for one sky regime). The anchor measures exactly that unknown: aim the reference meter at the camera field, enter its reading on the new ANCHOR marking-menu entry of the SQM screen, and the session is shifted by delta = reference - current. - UISQMAnchor: meter-value entry (sweep-wizard input pattern), shows the live PiFinder value, 0 clears the anchor; delta lives in shared state only (resets on restart - tonight's spectrum is tonight's) - solver applies the delta to sqm_final and sqm_altitude_corrected and reports it in details; SQM screen shows "anch +/-x.xx" when active - every anchor event is appended to ~/PiFinder_data/sqm_anchors.jsonl (reference, measured, deltas, camera, location) - accumulating the per-site band-offset calibration dataset as a side effect of use - marking menu reworked: ANCHOR (everyday action), CAL (noise constants), SWEEP (diagnostic; label was CORRECT which now belongs to the anchor) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VaxurSrCzxrsfJrSjfA5bo
The archived imx296 sweep (100 frames, ref 17.8-17.9) replaces three placeholders with measurements: - bias_offset 32 -> 60: the on-sky exposure-ramp intercept is 60.3, matching the Sony-standard black level (240 @ 12-bit = 60 @ 10-bit, same convention as the imx462 at 238). The old 32 was a mis-measurement and biased the background subtraction. - color_coefficient 0 -> +0.21 (460 stars, mzero vs B-V). Small because the Pregius mono QE falls through the NIR, unlike the STARVIS colour sensors. - sqm_band_offset 0 -> -0.46, PROVISIONAL: one moonlit sweep, one hand reference; the mono band reads darker than the meter. The session anchor absorbs the uncertainty until more nights exist. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VaxurSrCzxrsfJrSjfA5bo
SQM photometry is raw-only; the 8-bit processed image is for solving and display. The PNG path was unreachable in production and measured strictly worse (0.6-2.4 mag within-sweep swings, errors to 1.7 mag, frames failing photometry outright) at identical CPU cost. - solver: create_sqm_calculator always builds the raw calculator; update_sqm loses image_processed/use_raw_green and the processed fallback branch - camera_profiles: drop the four _processed profiles and the sqm_use_raw_green flag - CAL wizard: captures, analyzes, verifies and saves the RAW calibration only; verification runs the raw pipeline on the raw sky frames (green extraction + centroid rescale + 70% saturation cut) - SQM screen CAL indicator keys off the raw calibration file - sweep metadata records the raw camera type - SNR auto-exposure uses the controller defaults (its from_camera_profile existed only to consume _processed profiles; 8-bit defaults 15/30/100 are equivalent) - noise-floor tests run on a synthetic registered profile instead of imx296_processed Sweep regression after the deletion is bit-identical (18.275/std 0.070 on the 2026-07-11 reference sweep). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VaxurSrCzxrsfJrSjfA5bo
… module Surface the reference-meter session feature as "SQM Correct" (matching the CORRECT marking-menu action). Renames UISQMAnchor->UISQMCorrect (sqm_anchor.py->sqm_correct.py), shared-state sqm_anchor_delta-> sqm_correct_delta, solver._apply_sqm_anchor->_apply_sqm_correct, and the per-event log sqm_anchors.jsonl->sqm_corrects.jsonl. Delete the orphaned UISQMCorrection (sqm_correction.py): upstream removed its menu launcher in brickbots#374 (2026-03), leaving it unreachable dead code. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VaxurSrCzxrsfJrSjfA5bo
…tadata - sqm.py: dark_current_rate/read_noise default to 0.0 so a None (from an incomplete calibration file) can't crash the whole SQM calculation on a diagnostic-only noise term. - sqm_sweep.py: always write the sqm/reference block and guard each optional enrichment section independently so one failing step no longer discards the entire detailed-metadata record.
The solver built the calculator eagerly at startup from shared_state.camera_type(), which still holds the 'imx296' default until the camera process finishes libcamera init and reports the real sensor. The calculator then kept the wrong profile for the whole session, so photometry subtracted the imx296 pedestal (bias_offset 60) instead of the real sensor's (238 imx462 / 256 hq), inflating the sky background by ~2-3 mag: devices read SQM ~15-16 under an ~18 sky (measured: imx296 profile 14.67 vs hq 18.12 on the same frame). Create the calculator on first use instead. The camera process records its real type before capturing its first frame, and a solve requires a captured frame, so first use is guaranteed to see the real camera type. The ReloadSqmCalibration command now just invalidates the calculator so the next solve rebuilds it with fresh calibration, leaving a single creation site.
Offline analysis of every referenced HQ sweep (2025-11-16 x2, 2025-11-18, 2026-07-16; full pipeline incl. per-sweep wing correction) shows a stable -0.32 mag residual vs the hand-held reference meter (median of -0.28, -0.08, -0.46, -0.36; spread 0.14). Raise the band offset 0.07 -> 0.39 to absorb it. Method validated on imx462: the same offline pipeline reproduces on-device SQM exactly and leaves imx462 residuals at +0.02 +/- 0.04 with its existing +0.43 offset. Analysis artifacts: myPiFinder/support/dumps/analysis/20260716.
…fsets When a star's wings sit below the ring noise, the wing-boundary search cuts at the first ring — at worst the aperture itself, where total==core makes the enclosed fraction exactly 1.0 regardless of the optics. Faint fields then fill the rolling window with these tautological 'perfect aperture' samples and the wing correction collapses (measured: same-night HQ sweeps at f 0.92 vs 0.71, a 0.27 mag SQM split; 20% of the faint sweep's samples were exactly degenerate). Require the boundary to sit at least min_wing_radius out: a closer boundary means the wings were not resolved for that star — a statement about the star's brightness, not the optics — so the sample is dropped instead of recorded as f~1. The threshold is one angular cut (~530 arcsec) expressed in each sensor's photometry-image pixels (CameraProfile.wing_min_radius_px: 7 imx462/imx290 @490px green, 11 hq @760px, 15 imx296 @1088px mono), wired in the solver when the SQM calculator is created. Validated offline against all 14 archived sweeps (local cedar pipeline that reproduces on-device SQM exactly): the same-night HQ pair converges from f 0.92/0.71 to 0.64/0.67, and per-sweep f spreads tighten on both sensors. The floor removes samples that previously diluted the correction, so wcorr rises and both band offsets are recalibrated with it (they absorb the estimator's mean and must move as a package): - imx462/imx290: 0.43 -> 0.31 (6 referenced sweeps, residuals +/-0.10) - hq: 0.39 -> 0.32 (3 independent references; the shared 2025-11-16 reading sits +0.3 off on both its sweeps — suspect reference) Analysis artifacts: myPiFinder/support/dumps/analysis/20260716.
…ection; centroid-excluded annuli Three defects found live on-sky (mr2, 2026-07-17), each verified against the raw frames before and after: 1. ROTATION (the 3-magnitude bug): the camera process rotates the solve/ display image (90 or 270 deg CCW depending on screen_direction / camera_rotation) but stores the raw frame unrotated. The raw-photometry SQM path mapped solve-image centroids straight onto the raw, so every star aperture landed on the wrong sky and SQM read ~15-16 under an ~18 sky on every device with a rotated display (which is all of them). The camera now publishes its rotation (SharedState.solve_image_rotation) and update_sqm counter-rotates matched + detected centroids onto the raw grid (_derotate_centroids, validated against PIL rotation for 0/90/180/ 270 and arbitrary angles). Measured live: matched-star flux recovered ~4x; SQM 15.4 -> 17.6 agreeing with the offline pipeline to 0.05. 2. Wing correction was an artifact: the stacked curve of growth of 21 bright live stars shows the imx462 PSF fully enclosed by r~4 (FWHM 2.6px) -- no wings. The per-star boundary search integrated noise and reported f~0.72-0.75, a spurious +0.3-0.5 mag correction whose variance polluted the calibration. WingEstimator now median-stacks aperture-normalized bright stars per frame and reads the enclosed fraction off the stack's growth-curve plateau: wingless optics measure f=1.0 (correction 0); hq's mild focus-dependent wings (f 0.87-1.0) are measured honestly. min_wing_radius and CameraProfile.wing_min_radius_px are gone. 3. Background annuli in dense fields: every *detected* centroid (cedar finds them all; the parameter was previously unused) is now masked out of each star's background annulus, with a 3-sigma clip as backstop for undetected sources. A neighbour star can no longer inflate the local sky estimate. Band offsets recalibrated against all referenced clear-night sweeps with this pipeline: imx462/imx290 0.64 (residuals +/-0.06 over 6 sweeps), hq 0.63 (3 independent references). Cloudy-night sweeps excluded and documented: star-calibrated SQM under bright clouds reads ~0.7-0.9 low vs a radiometric meter because the zero point is measured through cloud extinction while the glow largely is not. Analysis artifacts: myPiFinder/support/dumps/analysis/20260716.
The mono sweep (2025-10-31) rerun through the final pipeline (growth-curve aperture correction: f=0.985, wingless like its siblings; centroid-excluded annuli) computes 17.45 against the recorded 17.8-17.9 hand-held reference. Refit: -0.46 -> -0.06. The +0.40 shift matches the phantom-wing retirement on imx462/hq (+0.33/+0.31), and a near-zero offset is physically consistent with the Pregius mono passband being the closest of the three sensors to the meter's. Single moonlit reference night: confidence +/-0.2; re-anchor with Session Correct or a reference sweep when this camera flies again.
…mp logging The titlebar SQM runs under PID auto-exposure (10 ms - 1 s), and the sweep ramps showed the frame zero point drifting ~-0.2 mag/decade of exposure. Per-star tracking across the ramps (860 star tracks) exonerates the sensor: response is linear to ±1% below 0.2 full-scale, no compression. The drift is population composition: matched-star sets slide ~4 magnitudes across the exposure range, and per-star zero points carry a mild magnitude dependence, so every exposure-dependent selection (all-star median, top-K, brighter half) inherits drift. Three changes: - mzero is now the median over stars in a fixed catalog-magnitude band (3.5-6.5, fallback to all stars below 5 in band): the same physical stars vote at every exposure. Measured drift on the imx462 ramps: -0.10 -> -0.01 mag/decade. hq keeps a ~-0.15 residual consistent with its real focus-dependent wings (tracked per session by the growth estimator). - Background annulus moved 6-14 -> 10-18, outside the PSF-wing zone that inflated bright stars' local sky at long exposure (~35% of the drift); safe against neighbours now that all detected centroids are masked out of annuli. - Sensor die temperature (picamera2 SensorTemperature) recorded into last_image_metadata and sweep metadata, diagnostic only: the residual black level wanders ±2 ADU night-to-night and temperature is the prime suspect; normal sweeps now collect the correlation data. Band offsets refit against all referenced clear-night sweeps with this pipeline (they absorb the annulus/selection level shifts): imx462/imx290 0.61, hq 0.60, imx296 -0.10. Clear-night residuals: imx462 ±0.08 (6 refs), hq -0.16..+0.18 (3 refs, the shared 2025-11-16 reading remains the outlier). Cloud bias unchanged (-0.4..-0.6, physics).
…tion Publish SQM as a fixed-calibration radiometer (factory zero point x measured sky flux) at 1 Hz from every camera frame, no plate solve required. Stellar photometry becomes a 10-second transmission diagnostic that establishes, maintains and audits the calibration: - sqm/radiometer.py: camera-side sparse-central-median sample; 12-sample/15 s accumulator; factory radiometric zero point + FOV per sensor profile. - sqm/clouds.py: cloud/dew discrimination. Cloud dims stars AND brightens a light-polluted sky (radiometer immune, no correction); dew/optics dim stars and sky together (stellar deficit compensates the radiometer once a clear session baseline exists). Asymmetric median baselines, factory-seeded. - sqm/black_level.py: sensor pedestal tracked from the sky-vs-exposure intercept of ordinary frames (the true black level wanders +/-2 ADU night to night); fed at 1 Hz, gated on transmission stability, zero-touch only. - sqm/gaia_ref.py + astro_data/hip_gaia_g.npz: Gaia G reference band with BP-RP trim for bare sensors (24-29% less star scatter than Hipparcos V). - Robust stellar zero point: fixed catalog-magnitude band + 3-MAD rejection; centroid-excluded background annuli; growth-curve aperture correction. - Calibration wizard rework: temporal read noise, multi-exposure dark fit, exact frame/solve pairing, typed reload command, transient exposures. Archive replay (16 sweeps, 800 frames): imx462 MAE 0.05 mag over six SQM-L-referenced clear nights; bright-cloud errors 0.35-0.84 collapse to under 0.10 with zero corrections on clear skies; 800/800 frames published including 318 without usable stars.
Each sweep image pair now gets img_NNN_XXms_metadata.json carrying the driver-applied exposure/gain, sensor die temperature, sensor black levels, Lux, colour gains, and raw-frame ADU statistics (mean/median/std/percentiles, saturated fraction). frame_metadata.json aggregates the same records. Also gate the sweep UI COMPLETE transition on sweep_metadata.json existing: the camera process writes it a few seconds after the last image, and enriching before that silently skipped the SQM/solve/noise-floor details on every sweep.
Each tracker (BlackLevelTracker, CloudEstimator, WingEstimator, RadiometerAccumulator) gains dump(): config, derived stats, and the full window sample contents. The solver publishes these into sqm_details as window_black_level / window_clouds / window_wings / window_radiometer on every update, and each sweep image JSON snapshots the whole sqm_details, so a sweep records the pipeline state frame by frame.
…erization Plan B for the stuck-pedestal incident (2026-07-17): - BlackLevelTracker: an accepted pedestal is now a lease (max_age_seconds, default 900 s) instead of a latch — without a fresh accepting refit it expires and callers fall back to the profile constant, the same state every session boots in. Regression test replays the incident. - max_intercept_stderr tightened 1.0 -> 0.6 ADU: honest ramp fits on the 27-sweep archive run 0.10-0.69; the cloud-poisoned fit passed at 0.97. Calibration wizard (dark-frame path the imx296 tester will run): - persists a per-frame report JSON (median/MAD/percentiles, requested AND driver-reported exposure, sensor temp) beside the fitted scalars — the ramp shape is where short-exposure pedestal elevation shows, and three fitted numbers cannot preserve it - dark-current fit uses driver-reported exposures (the imx477 delivers intermittent half-exposure frames; a requested-exposure fit is silently corrupted) - dark ladder extended from one decade to near sensor minimum, covering the elevation zone (imx462: +2.9 ADU at 25 ms, 0 at ~300 ms) Sweep instrumentation: - per-image records now carry the full driver metadata dict (new sensors report different keys) and the live camera-side radiometer sample (the sqm_details snapshot freezes while the solver starves mid-sweep) - camera_pi keeps last_frame_metadata; both image_metadata sites publish actual_exposure_us
…mat stragglers mypy: the B-V cache globals are Optional, so assignments cannot narrow them across statements; parse into locals and use those for savez/len. format: 4 files the CI format session was silently reformatting on every run (timeentry, three tests).
Telemetry captured only IMU and solve streams — blind to the radiometric side. A third event type records the camera-side radiometer sample at ~1 Hz: t (capture epoch, s), exp (driver-reported exposure, s), bg (background median, ADU), mad (median absolute deviation, ADU), grad (quadrant gradient, ADU), seq (frame sequence). Dedupes on sequence; replay players skip unknown event types, so old builds read new files. A full-night radio stream answers what sweep snapshots cannot: how auto-exposure actually behaves over a session (lever-arm availability for the black-level fit), a continuous sky-steadiness timeline, and real-night input for replaying pedestal estimators.
The marking menu down slot is the one readable at the eyepiece without moving your head — it goes to SWEEP, the everyday reference-meter action. SQM Correct is deleted rather than kept: a magnitude-additive session knob silently absorbs ADU-space, brightness-dependent errors (tonight: a stuck pedestal), masking exactly the fault class it appears to fix, and staling whenever the layers beneath it change. Its legitimate output — the unit-vs-reference-meter difference — is already recorded as evidence by every sweep run with a reference reading. The feature never shipped upstream (fork-only), so now is the cheapest moment to remove it. Removed: UISQMCorrect screen, menu entry and corrected indicator, _apply_sqm_correct and both solver call sites, the sqm_correct_delta shared state, and all test references. CONTEXT.md records the decision.
brickbots
added a commit
to mrosseel/PiFinder
that referenced
this pull request
Jul 18, 2026
…lision Both branches independently added the same raw tetra3 output field to SuccessfulSolve/PointingEstimate: main's SQM redesign (brickbots#532) as matched_catID, this branch as matched_catalog_ids. Resolved to main's matched_catID — it mirrors the raw tetra3 solution key, consistent with the sibling fields matched_centroids/matched_stars, and is already consumed by the SQM calibration UI on main. The focus-side consumers (ui/preview.py, focus tests) now read matched_catID; focus.py's internal parameter names are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
brickbots
added a commit
that referenced
this pull request
Jul 18, 2026
…rsion (#542) PR #532 (SQM redesign) also touched how the solver hands images to cedar-detect, and those changes broke the shared-memory solve path: the request now passes reopen_shmem, but the checked-in cedar_detect_pb2 bindings (tetra3 submodule @ 38c3f48f, unchanged by the PR) have no such field on Image, so every shmem ExtractCentroids call raises ValueError: Protocol message Image has no "reopen_shmem" field before any RPC is made — and since that is not a grpc.RpcError, the inline-image fallback never engages. None of these changes were required for the SQM functionality, so this restores the cedar-detect/tetra3 handoff exactly as it was in the commit before the merge (b5b1688^): - drop the _alloc_shmem override that tracked fresh segments - drop reopen_shmem from the shmem CentroidsRequest Image - drop the added shmem-fallback warning log - load the tetra3 pattern database by explicit path again instead of by name The SQM-related solver changes from #532 (raw-green photometry, radiometer cadence, matched_catID passthrough, lazy calculator creation) are untouched, as is the pre-existing stale-shmem cleanup. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
brickbots
added a commit
that referenced
this pull request
Jul 18, 2026
PR #532 renamed the camera loop's test-mode flag (debug -> test_mode_on) but moved its initialization inside the capture loop, resetting it to False every frame, and dropped the command handler that flipped it when the UI sends "debug" on the camera queue (Tools > Test Mode, and console key 0). Test mode therefore did nothing: the camera kept publishing real captures instead of the disk test image. Hoist the initialization back above the loop and reinstate the "debug" command toggle. The test-mode branch itself (including #532's set_sqm_radiometer_sample(None) guard) is unchanged. Verified headlessly: before activation the Focus preview cycles the debug camera's three frames (including the starless one); after Tools > Test Mode it stays pinned to test_images/pifinder_debug_02.png on both the Focus and Align (Day) screens, and the solver solves it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
brickbots
added a commit
that referenced
this pull request
Jul 19, 2026
PR #532 renamed the camera loop's test-mode flag (debug -> test_mode_on) but moved its initialization inside the capture loop, resetting it to False every frame, and dropped the command handler that flipped it when the UI sends "debug" on the camera queue (Tools > Test Mode, and console key 0). Test mode therefore did nothing: the camera kept publishing real captures instead of the disk test image. Hoist the initialization back above the loop and reinstate the "debug" command toggle. The test-mode branch itself (including #532's set_sqm_radiometer_sample(None) guard) is unchanged. Verified headlessly: before activation the Focus preview cycles the debug camera's three frames (including the starless one); after Tools > Test Mode it stays pinned to test_images/pifinder_debug_02.png on both the Focus and Align (Day) screens, and the solver solves it. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
SQM wandered 0.7–2.4 mag within a night as auto-exposure moved, and shifted with focus and sensor swaps. Three root causes: a static pedestal constant that never matches the true black level, photometry on the clipped/8-bit processed image, and catalog V magnitudes fed to sensors whose passband isn't V.
Fix (self-calibrating; plate solving untouched)
PedestalEstimatorjoint-fitsbg = P0 + rate·exposureover the auto-exposure stream; the intercept is the true black level, the dark-current slope is discarded.V − T·(B−V)per matched star (Hipparcos B−V by HIP id). Per-sensorT: 0.8 measured for bare imx462/imx290, 0.0 for HQ (IR-cut), imx296 unmeasured placeholder.WingEstimatormeasures the enclosed fraction via ring-profile slope cutoff on bright stars, smooths it in a rolling window, and applies−2.5·log10(f)to mzero.==> during the night, atmospheric conditions, focus and lens dewing can 'enlarge' the halo around a star, this method measures this halo on the 'good' images, smooths this measured radius over 20 images and then applies this radius to get better photometric results.
Rejected alternatives (per-sensor zero-point constant, per-frame adaptive apertures, low-percentile annulus background, IR-cut hardware) documented in
docs/adr/0020.Validation
Six exposure sweeps (3× imx462, 3× HQ), full production code path:
Two nights read ~0.6 mag from the reference for reasons star photometry can't see (reference pointing/haze suspected); absolute anchoring needs a side-by-side campaign.
Notes
docs/ax/sqm/CONTEXT.md; decision record indocs/adr/0020-sqm-raw-green-photometry-redesign.md.🤖 Generated with Claude Code