calibration: receptor-aware FLAG fraction QA gate#44
Merged
Conversation
_check_flag_fraction() was misreading axis 0 of bandpass .b FLAG arrays as antennas. Real DSA-110 .b shape is (rows, receptors, channels) = (antennas*spws, 2, 48). Treating axis 0 as antennas inflated the "dead antenna" count and caused strict QA to falsely fail. Aggregate per (antenna, receptor) by joining FLAG with ANTENNA1. Real 3C48 .b table now reports 1/124,416 = 8e-6 flagged working samples, five orders of magnitude below the 5% strict threshold. Add unit tests covering: realistic shape, dead receptor exclusion, both-receptors-dead, axis-0-not-antennas regression, and partial flagging. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Update _check_flag_fraction docstring to say "receptors" not "antennas" (matches the new aggregation behavior). - Document the receptor-axis heuristic: CASA polarization axes are always ≤4; channel axes are always >4. - Replace duplicate set(antenna_ids) computation with shared unique_antennas variable. - Add effective_flag_fraction == 0 assertion to test_both_receptors_dead_one_antenna for symmetry with the single-dead case. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The comment "nrow = number of antennas" was the exact buggy assumption the receptor-aware fix corrects. Keeping it next to the new correct explanation would actively mislead future readers. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR narrows the calibration QA logic for bandpass tables so _check_flag_fraction() stops treating calibration-table rows as antennas and instead aggregates flags by (antenna, receptor) using ANTENNA1. In the broader calibration pipeline, that prevents false strict-QA failures on otherwise good production bandpass solves.
Changes:
- Reworked bandpass flag-fraction QA in
calibration.pyto exclude fully flagged antenna/receptor pairs instead of misreading row axis 0 as antennas. - Added
_flag_fraction_excluding_dead_receptors()to compute effective flag fraction and dead-receptor statistics from row-major FLAG arrays plusANTENNA1. - Added a focused unit test file covering realistic bandpass shapes, dead-receptor exclusion, the original axis-0 regression, and partial-flagging behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
dsa110_continuum/calibration/calibration.py |
Updates the calibration QA gate to aggregate FLAG values by antenna/receptor and report dead-receptor-aware statistics. |
tests/test_calibration_flag_fraction.py |
Adds unit tests for the new flag-fraction helper and the original row-axis regression scenario. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+8
to
+10
| from dsa110_continuum.calibration.calibration import ( | ||
| _flag_fraction_excluding_dead_receptors, | ||
| ) |
Comment on lines
+524
to
+525
| effective_flag_fraction = flag_stats["effective_flag_fraction"] | ||
| n_dead = flag_stats["dead_receptor_count"] |
Comment on lines
+618
to
+626
| effective_flag_fraction = ( | ||
| working_flagged / working_total if working_total else float(np.mean(flags)) | ||
| ) | ||
| dead_antennas = {antenna_id for antenna_id, _ in dead_receptors} | ||
| return { | ||
| "effective_flag_fraction": float(effective_flag_fraction), | ||
| "dead_receptor_count": len(dead_receptors), | ||
| "dead_antenna_count": len(dead_antennas), | ||
| "working_receptor_count": len(unique_antennas) * receptor_count - len(dead_receptors), |
jakobtfaber
added a commit
that referenced
this pull request
May 4, 2026
…ass tables Add validation script and results for PR #44's receptor-aware FLAG fraction fix. Script replays both old (buggy axis-0-as-antennas) and new (per-receptor aggregation) logic on real 2026-01-25 and 2026-02-15 bandpass tables. Both show effective_flag_fraction drops from buggy 4.9% to correct 1.04% (35 dead antennas, 63 dead receptors, 171 working receptors with 1,368/131,328 flagged samples). Both pass 5% strict QA gate under
This was referenced May 4, 2026
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.
Summary
Fixes the long-standing
_check_flag_fraction()bug that misread axis 0 of bandpass.bcalibration tables as antennas. Real CASA shape is(rows, receptors, channels) = (1872, 2, 48)for 117 antennas × 16 SPWs × 2 polarizations × 48 channels — rows are antenna×SPW solutions, NOT antennas. Treating axis 0 as antennas produced false "464 dead antennas" log lines and inflated effective flag fraction enough to falsely fail strict QA on pristine data.The fix joins FLAG rows with the
ANTENNA1column and aggregates per(antenna, receptor)pair via the new_flag_fraction_excluding_dead_receptors()helper.Why this matters
Validated on real production bandpass tables at
/stage/dsa110-contimg/ms/:2026-01-25T22:26:05_0~23.b2026-02-15T22:26:05_0~23.bThe buggy 4.93% was perilously close to the 5% strict threshold — slightly worse data would have caused false QA failures across production dates, not just the 3C48 smoke. This was a latent threshold-margin bug across all dates, not a 3C48-specific issue.
The original 3C48 smoke (#38) reported "464 dead antennas" + false strict-gate failure on data that was actually pristine (
8e-6flagged after the fix).Changes
dsa110_continuum/calibration/calibration.py_check_flag_fraction(): aggregates per (antenna, receptor) via the new helper; logs now mention "dead receptors across N antennas"._flag_fraction_excluding_dead_receptors(flags, antenna_ids, *, dead_threshold=0.99): pure-numpy helper. Returns dict witheffective_flag_fraction,dead_receptor_count,dead_antenna_count,working_receptor_count,working_flagged,working_total.tests/test_calibration_flag_fraction.py(new, 5 tests, all passing)(1872, 2, 48)shape, no dead receptorsWhat was deliberately NOT included
The original change lived in a stash that mixed this fix with a wholesale
dsa110_contimg → dsa110_continuumimport-rename across ~50 unrelated files. This PR cherry-picks only the FLAG-fix hunks. The import migration belongs in a separate, independently-reviewable PR.Test plan
.btables (script:outputs/2026-05-04-flag-fraction-fix-validation/)_check_flag_fractiondocstring + helper reflect new semanticsRelated
project_bandpass_flag_fraction_qa_bug_fix.md🤖 Generated with Claude Code