FF files: store the average at full 16-bit precision; fix average rounding and stdpixel variance - #964
Draft
Cybis320 wants to merge 6 commits into
Draft
FF files: store the average at full 16-bit precision; fix average rounding and stdpixel variance#964Cybis320 wants to merge 6 commits into
Cybis320 wants to merge 6 commits into
Conversation
The 8-bit avepixel floors away the sub-ADU precision of the 256-frame mean: on real stations the intrinsic noise of the trimmed mean is 0.28-0.41 ADU, at or below the 1 ADU quantization step, which costs 0.5-1.8 mag of faint-star photometric depth on the average image and biases it -0.5 ADU. The compressor now also outputs the average in 8.8 fixed point (uint16, 1/256 ADU), written as the AVEPIXEL plane with an AVEFRAC header keyword when ff_avepixel16 is enabled (default true, fits only). Readers derive the legacy 8-bit view (bit-identical to the old floored average) so every existing consumer is unaffected, and extractStarsFF uses the full-precision plane when present. Also fixes reading any BZERO-scaled FF (e.g. native 16-bit cameras) under modern astropy, which refuses explicit memmap on scaled HDUs.
The three measurement paths that read the FF average (star extraction from the displayed image, star centroiding in SkyFit mode, and the manual-reduction photometry background) now prefer the 16-bit fixed-point plane, falling back to the 8-bit avepixel identically when absent. The photometry background median can then resolve sub-ADU levels, removing the quantization staircase (measured -0.23 ADU on the 8-bit path vs 0.00 on the full-precision path in a synthetic scene). Display paths intentionally keep the 8-bit view.
The 8-bit average was floor-truncated (C division semantics), biasing it -0.5 ADU, and the truncated mean was then reused in the variance term (acc*mean instead of acc**2/n), inflating stdpixel by an extra ~mean*frac(mean) ADU**2 per pixel - measured +39-48% at typical sky levels and up to +288% on quiet cameras, contrary to the removed NOTE claiming the difference does not matter. The 8-bit average plane is now derived from the rounded fixed-point mean ((ave16 + 128) >> 8, matching how readers derive the 8-bit view), and the variance uses the correct sample formula computed in double precision, with the standard deviation rounded instead of floored. Verified against an exact numpy reference including saturation and low-noise edge cases; residual std error is only the uint8 output rounding. Note stdpixel drops ~30-40% from the old inflated values, which makes k1-based detection thresholds effectively more sensitive.
The top-4-only trim biases the mean low by ~0.04 sigma for symmetric noise (-0.18 ADU at sigma 4.5); the bottom 4 values are now trimmed as well (mean over 248 frames, variance divisor 247), removing the bias (measured +0.001 ADU). Averaging gamma-encoded samples is biased low by Jensen's inequality (~gamma*(1-gamma)*(sigma/mean)^2/2 of the level; measured -0.24 ADU at gamma 0.6, sky 40). When the configured camera gamma is not 1, frames are now decoded through a LUT, averaged in the linear domain, and the mean is re-encoded, so the stored plane stays in the gamma-encoded domain all consumers expect and their own gamma correction recovers the true linear mean. The gamma used is recorded in the AVEGAMMA header keyword for provenance. gamma=1 keeps the exact integer path.
Audit follow-up to the CompressionCy fixes - the same class of error existed elsewhere: - FFMimickInterface (video/image inputs) replicated both compressor bugs: floored average (-0.48 ADU) and the truncated-mean variance term (stdpixel +26-146%). Now rounded mean, correct sample variance in double precision, rounded std (measured -0.02 ADU, -1%). - applyFlat and gammaCorrectionImage floor-truncated on the cast back to integer types (-0.5 ADU per application); now rounded. - binImage 'avg' floored the binned average; float images (flats) were truncated to uint16 before binning; uint8 'sum' binning wrapped modulo 256. Now rounded, float-preserving, and promoted to uint16. - MakeFlat floor-truncated flats (multiplicative field-dependent photometric error) and wrapped >8-bit darks modulo 256; now rounded, and darks keep a wide enough dtype. - Measured values written truncated instead of rounded: CALSTARS intensity/amplitude/background, FTPdetectinfo level/background, detection and SkyFit2 manual-reduction intensity sums. - Astra background subtraction cast both float32 operands to int32 right before subtracting; FR-file avepixel reconstruction floored. - thresholdImg no longer raises on float average images. Not changed (reported for separate work): non-FF detection subtracts an uncalibrated avepixel from calibrated frames, and applyDark's cv2.subtract clips negatives for integer inputs only, making meteor sums depend on whether gamma correction is active.
The synthetic FF used for video, .vid, and image-sequence inputs now matches the corrected compressor: the trim is symmetric (the min frame is removed along with the max, balancing the trim bias), the average is computed in the linear domain when a camera gamma is configured (decoded via LUT, averaged, re-encoded; measured +0.007 ADU vs -0.202 for encoded-domain averaging of a gamma 0.6 scene through the production FrameInterface path), and a full-precision 8.8 fixed-point avepixel16 plane is exposed for 8-bit content, so the existing avepixel16 consumers (SkyFit2 photometry paths) work on non-FF inputs with no further changes. The gamma white point and the fixed-point gate use the camera bit depth passed at construction rather than the output dtype, which some FrameInterface callers only finalize after the first frame; the gate also checks the actual data range so sum-binned frames cannot wrap. The fewer-than-4-frames path keeps a plain rounded average.
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.
Motivation
The FF average plane discards most of the information the 256-frame mean actually contains, and the discarded fraction corrupts more than just the average:
stdpixel/sqrt(252)≈ 0.2–0.4 ADU on real stations (measured on USV001 and ES001D archives) — at or below the 1 ADU quantization step. On USV001, the majority of sky pixels are quantization-dominated.maxpixel − avepixelrun correspondingly hot.acc*meaninstead ofacc²/n), inflating stdpixel by an extra ≈mean × frac(mean)ADU² per pixel: measured +39–48% at typical sky levels and up to +288% on quiet cameras. The old NOTE claiming the non-standard calculation "does not matter" was quantitatively false.Changes
compressFramesadditionally returns the trimmed mean in 8.8 fixed point (uint16, units of 1/256 ADU, rounded). Withff_avepixel16: true(new config option, FITS only) it is written as theAVEPIXELHDU with anAVEFRAC=8header keyword.FFfits.readkeeps it inff.avepixel16and derives the legacy 8-bitff.avepixelview by rounding off the fractional bits, so every existing consumer keeps working unchanged. Legacy files,.bin, and native 16-bit camera files are unaffected (covered by tests).(ave16 + 128) >> 8— zero-mean rounding (measured bias +0.01 ADU vs −0.494 before), one shared derivation between compressor and readers.extractStarsFF(CALSTARS) and SkyFit2's three measurement paths (star extraction, centroiding, manual-photometry background) preferavepixel16when present, with identical fallback otherwise.FFfits.readnow probes and falls back to a plain read for scaled files (all planes are copied out regardless).Measured results
Tests/TestFFAvepixel16.py).thresholdFF, MakeFlat, StackFFs,reconstructFrame, andadjustLevelsproduce identical results between new-format and legacy copies of the same data.Cost and compatibility
k1-based detection thresholds become effectively more sensitive; field testing will show whetherk1/j1need a compensating migration or the correct values can stand.ff_avepixel16: falserestores fully legacy output.Update: correctness of the average/std computations themselves, and an audit of the same error class
Beyond storing more bits, the computations had four systematic errors, all fixed here (each validated against exact numpy references and, where noted, measured):
acc*meanterm instead ofacc²/n, inflating stdpixel by ~mean*frac(mean)ADU² — +39–48% at typical sky, up to +288% on quiet cameras. Now the correct sample variance in double precision. Note: corrected stdpixel is ~30–40% lower, sok1-based thresholds become effectively more sensitive — the main field-testing question of this draft.AVEGAMMArecords the gamma used; γ=1 keeps the exact integer path.A codebase audit for the same class (floor-instead-of-round, premature narrow-dtype truncation) found and fixed:
FFMimickInterface(video/image-sequence inputs) replicated both compressor bugs: −0.48 ADU average bias, stdpixel +26–146% → detection/photometry on non-FF inputs. Fixed (measured −0.02 ADU, −1%), and subsequently brought to full parity with the compressor: symmetric max/min trim, linear-domain averaging with the configured gamma (measured +0.007 ADU vs −0.202 encoded-domain through the production FrameInterface path), and anavepixel16fixed-point plane for 8-bit content, so the SkyFit2 precision paths work on video inputs with no further changes. The gamma white point and fixed-point gate come fromconfig.bit_depth(known at construction) rather than the late-bound output dtype, and the gate checks the data range so sum-binned frames cannot wrap.applyFlatandgammaCorrectionImagefloored on the cast back to integer types (−0.5 ADU per application).binImage: floored 'avg'; float flats truncated to uint16 before binning; uint8 'sum' binning wrapped mod 256.MakeFlat: flats floor-truncated (a field-dependent multiplicative photometric error, since the flat divides every image); >8-bit darks silently wrapped modulo 256.Reported but deliberately not changed here (each needs its own validation):
preprocessFFdark/flat-corrects maxpixel and avepixel but never stdpixel (both FF and non-FF paths), sok1*stdpixelthresholds are mis-scaled wherever the flat deviates from unity (~30% too sensitive in strongly vignetted corners);applyDark'scv2.subtractclips negatives for integer inputs only, so meteor sums currently depend on whether gamma correction is active;FFMimickInterfacestill averages in the encoded domain. (An earlier revision of this description claimed non-FF detection subtracts an uncalibrated average from calibrated frames - that was wrong:preprocessFFruns after every chunk load.)🤖 Generated with Claude Code