Skip to content

ngmix masked pixels: neighbours and defects as separate questions - #922

Open
cailmdaley wants to merge 36 commits into
developfrom
feat/masked-pixels
Open

cailmdaley wants to merge 36 commits into
developfrom
feat/masked-pixels

Conversation

@cailmdaley

@cailmdaley cailmdaley commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Every stamp we fit with ngmix has pixels we can't use as they are: light from a neighbouring object, or a defect (bad column, cosmic ray, bleed trail, chip or tile edge). Metacal shears the image in Fourier space, where a weight map can't enter, so whatever we put in those pixels is sheared and spread into the fit. This PR fixes one treatment for defects, keeps a single setting for neighbours, and adds the cuts that drop an epoch when a defect sits too close to the object.

What ngmix sees

pixel classes
One epoch run through prepare_ngmix_weights. Top: the image ngmix fits. Bottom: its weights (dark = zero; violet = zero weight with the light kept). (a–c) A synthetic blend under each BLEND_HANDLING. (d–e) A real DR6 blend with a real bad column: tile 202.301, object 4783, exposure 2086786p, CCD 24.

Pixel BLEND_HANDLING = noisefill (default) uberseg
Neighbour footprint zero weight, filled with noise pixels nearer a neighbour than the target get zero weight; their light stays
Defect: zero exposure weight, nonzero flag, bad background RMS, or beyond the tile edge zero weight; narrow runs interpolated, the rest noise-filled same

Neighbours. SExtractor marks other objects' footprints with −1e30 in each tile stamp, and we apply that one mask to every epoch. noisefill replaces those pixels with noise. uberseg (as in DES) gives each pixel to the nearest segmentation footprint and keeps the neighbour's light, which is real sky that metacal shears along with the target. Which default to use is decided by the blended-simulation comparison in #813.

Off-tile pixels are also −1e30 in the tile stamp, but the exposure there holds the object's own light, cut off by the tile edge. So they are defects. We find them as the unbroken runs of fully −1e30 rows and columns that start at the stamp border. This matches the true off-tile region for 210 of 212 edge objects on a simulated tile (to ±1 px), and for 32 of 32 on DR6.

Defects

defect gallery
Real DR6 defects (tile 202.301, exposure 2086786p) under the default. Green outline: interpolated. Orange: noise fallback. Blue: neighbour footprint.

A defect run at most 3 px across, with clean pixels on both sides, is filled by Clough–Tocher interpolation from the surrounding pixels. Wider holes, edge bands, and runs that end on a noise-filled neighbour get noise at the background RMS instead.

Interpolated pixels keep zero weight. A one-sided zero-weight hole pulls the model fit (c₁ = −1.3e-3 for a column 8 px from a 0.5″ galaxy). So we also zero the weight of each interpolated pixel's three quarter-turn copies, keeping their light. The holes then cancel (c₁ = 2e-6). These copies are the violet lines.

Interpolation is the only fill because noise fill near the object is biased in a size-dependent way: a 0.7″ galaxy through a 0.9″ PSF needs a 13–14 px veto under noise fill, but only 7 px with interpolation. The interpolated fill is unbiased, but it is about 1.6× noisier than the background across a 3 px gap; that is covered by the bias scan below.

Symmetrising neighbour holes (#815) was tried and rejected. With no neighbour light it cancels the pull (c₁ from −7.8e-3 to 2e-6). With real neighbour light, though, the one-sided hole was partly offsetting that light's pull. Symmetrising removes the offset and makes c₁ worse: from 1.1e-3 to 1.2e-2 for a neighbour at 0.1× the target's flux, 10 px away. It also leaves only 25 weighted pixels for a neighbour at 7 px. It is recorded as excluded in astra.yaml.

Which epochs we keep

An epoch is dropped when:

  • a noise-filled defect pixel lies closer than 13 px to the object centre, or an interpolated one closer than 7 px;
  • or more than 10% of the stamp is defect.
  • or the epoch fails to prepare (e.g. the interpolant can't reach a defect). It is logged, counted in NGMIX_N_EPOCH_FAILED, and the object is fitted on its remaining epochs.

Neighbour pixels never count towards these cuts. The neighbour mask is the same in every epoch, so counting it would remove whole blended objects (31% of a simulated tile).

Each radius is the smallest at which every tested defect keeps |m| < 1% and |c| < 5e-4. The defects are columns, 3 px bleeds, single pixels, edge bands and a 4-column cluster too wide to interpolate. The cases cover 0.3″, 0.5″ and 0.7″ galaxies, and round and elliptical PSFs. The 0.7″ galaxy through a 0.9″ PSF sets both radii. Interpolated 3 px bleeds there need 7 px (c = 6.9e-4 at 6 px, 2.5e-4 at 7 px). Noise-filled defects need 13 px: a 4-column cluster gives m₁ = −6.6% at 10 px and −0.67% at 13 px. CFHT observes at a fixed orientation, so detector defects line up the same way in every epoch, and their biases add rather than average away.

bias vs defect distance
Worst-axis |m| and |c| from a single defect at distance d (tests/helpers/defect_response, 6 seeds). Solid: the production treatment. Dashed: noise fill, shown as the counterfactual for narrow defects. Hollow: epochs the 10% cut drops anyway. Dotted: the 7 px and 13 px veto radii.

Why the masked fraction is 10%, as in DES. The cut matters for bias only through edge bands, which reach close to the object while covering a large part of the stamp: at 10% every edge band closer than about 20 px is dropped. Far-field masks are otherwise harmless (|m| ≤ 0.05% up to 58% of the stamp). On real tile 202.301 the cut is cheap. Compared with 1/3, for interior galaxy-like objects, epochs dropped go from 3.4% to 5.1% and objects left with no epoch from 0.35% to 0.55%. What 10% adds is CCD-edge and off-tile bands. Column clusters sit at 2–8% of a stamp, and only 4 epochs cross 10%.

Technical notes

  • Settings. BLEND_HANDLING (noisefill | uberseg). The veto radii (13 px, 7 px) and the masked fraction (10%) are constants in ngmix.py, and each is recorded with its excluded alternatives in astra.yaml.

  • What changes for existing runs.

    • Narrow defects are now interpolated rather than noise-filled.
    • The masked-fraction cut used to count every −1e30 marker. It now counts flagged, zero-weight and bad-RMS pixels, and ignores neighbours.
    • The central veto is new.
    • The masked-fraction cut is 10% rather than 1/3.
    • Under uberseg, defects used to keep their raw values; they are now filled.

    Both arms of A/B validation of off-by-default shape-measurement options #813 must run on this code.

  • Which pixels are interpolated is decided once, by interpolated_defects, and shared by the veto and the fill. A non-finite interpolant raises an error and the object is lost. This never happened in a fuzz test of 4,000 random stamps, or in 104,069 real epochs on tile 202.301 (294,675 interpolated pixels).

  • Object loss (measured with the earlier 1/3 cut and 10 px veto). On a simulated tile of 21,851 objects, the cuts lose 89 (0.41%). 88 of those lie outside the tile's deduplication box and inside a neighbouring tile's, so the merged catalogue drops them from this tile anyway. The 89th is a neighbour footprint that fills whole rows out to the stamp border. Its pixels are read as off-tile, which is the known approximation.

  • Saturation. The survey flag map marks only the first pixel of each saturated run, so most saturated pixels reach ngmix unflagged. This is separate from this PR; see Saturated pixels reach ngmix unflagged: survey flag bit 8 marks only the first pixel of each saturated run #943.

  • CI. tests/science now runs once, in its own step.

  • Not included. Multi-object fitting (MOF), which remains a later option.

  • Evidence.

    • Figure scripts: scripts/validation/masked_pixels/.
    • Bias tests: tests/science/test_defect_recovery.py.
    • Review measurements, on candide: /n17data/cdaley/scratch/922_review/ and /n17data/cdaley/scratch/review922/.

Supersedes #915 and #916. Closes #815. Part of #602.

🤖 Generated with Claude Code

https://claude.ai/code/session_01N4HLdzDyc3CiPLrgJ48GVo

cailmdaley and others added 8 commits September 26, 2026 07:07
…defects

Defects (nonzero flag, zero weight, invalid RMS) must be zero-weighted and
noise-filled under every BLEND_HANDLING, with neighbour pixels left raw
under uberseg. The masked-fraction cut must count that same raw set, and an
epoch with a defect closer than EPOCH_CENTRAL_DEFECT_RADIUS to the stamp
centre must be dropped, at a strict boundary and at the configured radius.
The runner must pass both cut options on, the HSM centroid must ignore raw
defect values, each epoch must keep its own OFFSET, and every tile must log
its epoch-cut tally.

The science test recovers the full 2x2 response for columns, 3-px bleeds,
single pixels and edge bands at the veto radius, on 0.3" and 0.5" galaxies
through round and elliptical 0.7" PSFs, and requires |c1|, |c2| < 5e-4 and
|m11|, |m22| < 1%. Flagged pixels in the simulation hold a hot value, so an
unfilled defect shows up. A column two pixels inside the radius is the
positive control.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QAc1ywyd9Sbbra6po2iuaA
…ral ones

Metacal shears the whole stamp without reading the weights, so a defect
left raw under uberseg reached the fit. Every defect (nonzero flag, zero
weight, invalid RMS) is now zero-weighted and noise-filled whatever
BLEND_HANDLING is; uberseg only zeroes the weight of neighbour-side pixels
and keeps their light. The option that meant "no neighbour treatment" is
renamed from noisefill to none, and the retired name is rejected.

The defect set is not symmetrized: at the veto radius the one-sided fill
leaves |c| <= 3e-4, while a four-fold mask would quadruple m (-2.7% against
-0.64% for a 3-px bleed at 10 px on a 0.5" galaxy) and, through an elliptical
PSF, still leave c1 = 7.3e-4.

An epoch is dropped when a defect lies closer than
EPOCH_CENTRAL_DEFECT_RADIUS (default 10 px) to the stamp centre, the
smallest radius at which columns, 3-px bleeds, single pixels and edge bands
give |m11|, |m22| < 1% and |c1|, |c2| < 5e-4 on 0.3" and 0.5" galaxies
through a 0.7" PSF. The masked-fraction cut counts the same raw defect set;
EPOCH_MASKED_FRACTION_CUT (default 1/3) makes it configurable. Each tile
logs "epoch cuts: considered=N masked_fraction=A central_veto=B
objects_emptied=C". The HSM centroid is measured on the filled image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QAc1ywyd9Sbbra6po2iuaA
prepare_postage_stamps already holds the object's galaxy vignet dict, so the
per-epoch OFFSET is read from it instead of a second sqlitedict lookup. The
pixel scale sets only the centroid-prior width; the comments that also
credited it with a noise window are corrected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QAc1ywyd9Sbbra6po2iuaA
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The interpolation fills only short defect runs (at most 3 px along a row or
column, with clean pixels at both ends); anything wider, and runs reaching
the stamp border, stays noise-filled. The interpolant must reproduce linear
planes without reading defect values, commute with quarter turns of the
stamp, and apply one operator to the science and metacal noise images. The
weight is zero on the defects and on the quarter-turn orbit of the
interpolated pixels, whose light stays. Each defect is vetoed at the radius
of its own fill: EPOCH_INTERPOLATED_DEFECT_RADIUS for interpolated pixels,
EPOCH_CENTRAL_DEFECT_RADIUS for noise-filled ones. The runner and Ngmix pass
DEFECT_FILL to the epoch cuts and to metacal; noise stays the default.

The science test recovers the full response matrix for interpolated columns,
full and finite 3-px bleeds and single pixels at the interpolated radius, on
0.3" and 0.5" galaxies through round and elliptical 0.7" PSFs and on a 0.7"
galaxy through a 0.9" PSF, and for noise-filled wide defects at the noise
radius. A bleed three pixels inside the radius is the positive control.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QAc1ywyd9Sbbra6po2iuaA
… veto

DEFECT_FILL = interpolate fills short defect runs (at most 3 px along a row
or column, with clean pixels at both ends: columns, 3-px bleeds, single
pixels) with a Clough-Tocher interpolant of the clean pixels within 4 px.
The interpolant is averaged over the four quarter turns of the stamp, and
each turn's triangulation serves the science image and the metacal noise
image alike. Wider holes and runs reaching the stamp border keep the noise
fill. noise stays the default and is unchanged.

Interpolation restores the object's light, so the remaining bias is the
hole in the likelihood: a one-sided zero-weight column 8 px from a 0.5"
galaxy still gives c1 = -1.3e-3. The weight is therefore also zero on the
quarter-turn orbit of the interpolated pixels (their light stays), which
brings that column to c1 = +2e-6. Symmetrizing the fill instead would trade
true light for interpolated light over four times the area.

Each defect pixel is vetoed at the radius of its own fill:
EPOCH_INTERPOLATED_DEFECT_RADIUS (default 7 px) for interpolated pixels and
EPOCH_CENTRAL_DEFECT_RADIUS (10 px) for noise-filled ones. 7 px is the
smallest radius at which interpolated columns, 3-px bleeds and single pixels
give |m11|, |m22| < 1% and |c1|, |c2| < 5e-4 on 0.3", 0.5" and 0.7" galaxies,
round and elliptical PSFs; it does not scale with galaxy size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QAc1ywyd9Sbbra6po2iuaA
Brings the decision record to what this branch does:
- central_defect_veto: committed option fixed_radii (was disabled); the
  10 px EPOCH_CENTRAL_DEFECT_RADIUS is pinned by a Values entry on its
  module constant, now tagged.
- epoch_masked_fraction_cut: the cut counts the defect set against
  EPOCH_MASKED_FRACTION_CUT (no longer [HARDCODED] on flags).
- defect_fill: defects are noise-filled under every BLEND_HANDLING; the
  docstring [LINT] this branch fixes is dropped.
- blend_handling: the no-treatment option is BLEND_HANDLING = none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings develop (#875's decision record) in through the updated base, and
amends the record for this branch:
- defect_fill: the interpolate option is implemented (DEFECT_FILL =
  interpolate); noise stays the committed fill.
- central_defect_veto: EPOCH_INTERPOLATED_DEFECT_RADIUS = 7 joins the
  Values, on its now-tagged module constant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cailmdaley added a commit that referenced this pull request Sep 28, 2026
Brings the tile segmentation map for UberSeg into #922, so the masked-pixels
work sits in one place. feat/uberseg-seg-map is built on #897
(feat/unions-catalogue-tile-detection), so this provisionally carries #897's
content too (UNIONS catalogue detection, TILE_UNIQUE_ID, ngmix row chunks)
until #897 lands on develop.

No textual conflicts: ngmix.py's row-selection chunking (#897) and the epoch
cuts, defect fill and constants (#915/#916) touch disjoint hunks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cailmdaley and others added 7 commits September 29, 2026 16:11
Conflicts: the ngmix runner imports both the epoch-cut defaults and
write_empty_tile_output; Ngmix.process logs the epoch-cut tally and then
calls log_run_health. Develop's run-health test stubs prepare_postage_stamps
with the epoch-cut kwargs and tally and sets the epoch-cut attributes; the
defect-fill process tests give their vignettes an OFFSET and their logger
warning/error methods.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The neighbour option keeps develop's name, noisefill, in the code, the runner
config key, tests, astra.yaml and the committed universe; the name none is
dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SExtractor's -1e30 markers in the tile VIGNET form their own per-epoch mask
(stamp.neighbours): a footprint 3 px from the centre, or covering 45% of the
stamp, drops no epoch; noisefill zero-weights and noise-fills exactly the
marked pixels, bit for bit as develop does; uberseg leaves them raw and
weighted; a flagged column near the centre is still vetoed; and
do_ngmix_metacal hands each epoch's mask to make_ngmix_observation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
prepare_postage_stamps wrote the tile VIGNET's -1e30 markers (other
detections' footprints) into each epoch's flag stamp as 2**10, so
defect_mask counted them: they fed the masked-fraction cut and the central
veto. Every epoch shares the tile VIGNET, so a neighbour within
EPOCH_CENTRAL_DEFECT_RADIUS dropped every epoch of the object.

The markers are now their own per-epoch mask, stamp.neighbours
(MegaCam-flipped with the epoch), threaded through do_ngmix_metacal and
make_ngmix_observation to prepare_ngmix_weights. The epoch cuts count
genuine defects only. Under noisefill the marked pixels get weight 0 and
the defects' noise realisation, the output noisefill gave when they arrived
as flags; under uberseg they are ignored and the seg-based weight handles
neighbours. Defects are treated identically under both. Docstrings, @sc
contracts, runner comments and the astra.yaml decisions say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SExtractor writes -1e30 into the tile VIGNET beyond the tile's edge as well
as on other detections' footprints. Beyond the edge the epoch holds the
object's own light, cut off, so treating those pixels as neighbours exempted
edge objects from both epoch cuts and measured them with a noise-filled band
near their centre.

split_tile_markers classifies the markers geometrically on the flipped tile
VIGNET: the union of stamp rows and columns that are entirely -1e30 (the
off-image part of a rectangle clip) is off-tile, the rest is the neighbour
mask. Off-tile pixels are flagged OFF_TILE_FLAG (2**10) in the epoch's flag
stamp, so under both blend handlings they are zero-weighted, filled, and
counted by the masked-fraction cut and the central veto. Docstrings, @sc
contracts, runner comments and astra.yaml say so.

Tests: an object 3 px from the tile edge loses every epoch; a band 12 px
away passes the default cuts and is vetoed at radius 13; a corner's L-shaped
off-tile region is flagged exactly and a border-touching neighbour stays a
neighbour; off-tile pixels are filled under both blend handlings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under DEFECT_FILL = interpolate and BLEND_HANDLING = noisefill, the
Clough-Tocher support included marked neighbour pixels that noisefill
replaces with noise, so a defect beside a bright neighbour was filled with
light the image no longer contains. The support now excludes ~clean (the
defects and the removed neighbour pixels); the NaN fallback to the noise
fill covers degenerate supports. Under uberseg the neighbour light stays in
the image and the support is unchanged. interpolate_defects names its
second argument `excluded`.

Test: a flagged pixel next to a 1e4 marked neighbour interpolates to the
kept-pixel interpolant (1.4, against 3102 with the neighbour in the
support) under noisefill, and from the raw neighbour light under uberseg.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fill_defects used np.where, which promotes a float32 stamp to float64;
develop's noisefill kept the galaxy stamp's dtype. The filled image is cast
back to the input dtype, so noisefill stays bit-identical to develop on
float32 stamps. The develop-equivalence test gains a float32 case and
checks dtypes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cailmdaley and others added 3 commits September 30, 2026 01:57
…amp border

Every entirely -1e30 stamp row or column counted as off-tile. In an edge
stamp, a neighbour footprint that covers the on-image part of a row (a
wide DR6 footprint running from the tile edge) completes that row, so the
neighbour's pixels became OFF_TILE_FLAG defects: counted by the epoch
cuts, interpolated under DEFECT_FILL = interpolate, filled under uberseg.
Only the runs of marked rows and columns that start at a stamp border are
off-tile now.

Tests build tile VIGNETs with the catalogue-mode converter: on the real
202.301 DR6 patch every stamp splits exactly into off-image pixels and
seg-map neighbour pixels, and the edge case above keeps its footprint in
the neighbour mask through prepare_postage_stamps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BbT81hykcvtuefzB3FhYvY
… converter

The neighbour-marking rationale and the converter's BIG comment describe
the markers as ngmix neighbours under flag 2**10; ngmix splits them into
off-tile defects and a neighbour mask. The masking text names the tile
VIGNET, not SExtractor, as the markers' carrier, since catalogue mode
paints them from the DR6 segmentation map.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BbT81hykcvtuefzB3FhYvY
@cailmdaley cailmdaley changed the title ngmix masked pixels: independent neighbour and defect handling (UberSeg, noise fill, interpolation, epoch cuts) ngmix masked pixels: neighbours and defects as separate questions Sep 30, 2026
@cailmdaley
cailmdaley marked this pull request as ready for review September 30, 2026 01:09
@cailmdaley

Copy link
Copy Markdown
Contributor Author

Defect: a neighbour that covers a full border row or column of the stamp is read as off-tile.

split_tile_markers (ngmix.py:1754) treats any unbroken run of all −1e30 border rows or columns as off-tile. A large neighbour can cover a full border row of a stamp that sits entirely on the tile. Those pixels then get noise-filled, or count against the masked fraction, when they should get UberSeg weighting.

Measured on tile 202.301, with our own SExtractor run: 45 of 35,621 fully on-tile objects (0.13%) are affected. The median is 301 misread pixels per stamp, and a third of the affected objects have at least 20% of their stamp misread. The affected objects are all in blends, so the selection is small but blend-correlated.

Fix: decide off-tile from the stamp's geometry (tile dimensions plus the vignet origin), or under UberSeg from SEG_VIGNET, instead of from −1e30 alone. This doesn't block the current campaign.

Claude Opus 5.5 on behalf of Cail, from the nibi Surveyor's check

cailmdaley added a commit to cailmdaley/shapepipe that referenced this pull request Oct 2, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JkM3cmXPuuSrNAf7vJde9e
cailmdaley and others added 2 commits October 2, 2026 17:26
Follow #933: the tile VIGNET's -1e30 markers come from SExtractor alone;
the catalogue converter and its neighbour-marking decision are gone, and
the off-tile split tests cut SExtractor-style stamps from a segmentation
map directly. Follow #927: no PIXEL_SCALE option; the centroid prior uses
each object's own pixel scale. Tests that build Ngmix or drive process()
carry the epoch-cut settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JkM3cmXPuuSrNAf7vJde9e
…l DR6 blend, and a defect-type gallery

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4HLdzDyc3CiPLrgJ48GVo
cailmdaley added a commit to cailmdaley/shapepipe that referenced this pull request Oct 2, 2026
cailmdaley and others added 5 commits October 3, 2026 00:05
…lasses it covers

prepare_ngmix_weights sorts each stamp pixel into clean, light-replaced
(noise-filled defects, noisefill neighbours) and light-kept-at-zero-weight
(interpolated defects, the uberseg neighbour side). One step zeroes the
weight on the quarter-turn copies, about the stamp centre, of the
light-kept holes that SYMMETRIZE_WEIGHTS names:

- interpolated (default): the interpolated defects, as before;
- interpolated_and_neighbours: also the uberseg neighbour side (#815);
- none.

Light-replaced pixels are never symmetrized. Under noisefill + noise
every value is a no-op, so the defaults are unchanged.

uberseg_weight becomes uberseg_mask, returning the neighbour-side pixels,
so the weight map is built once from the pixel classes.

Measured with defect_response (6 seeds, 0.5" galaxy, 0.7" PSF, neighbour
at 10 px): a lightless neighbour footprint gives c1 = -7.8e-3 under
uberseg and 2e-6 symmetrized, but with neighbour light (0.1x flux) c1
goes from +1.1e-3 to +1.2e-2, because the one-sided hole partly offsets
the neighbour light in the target's cell. Symmetrizing also keeps 117 of
1580 weighted pixels (97% of S/N) at 10 px and 25 (71%) at 7 px. Hence
the neighbour side stays opt-in. tests/science/test_weight_symmetrization.py
guards both results; defect_response gains a neighbour and the fit S/N.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4HLdzDyc3CiPLrgJ48GVo
…fill's interpolated set

Short defect runs are always interpolated and the rest noise-filled;
DEFECT_FILL, SYMMETRIZE_WEIGHTS and the three epoch-cut keys are gone,
and the radii and the masked fraction are module constants.
central_defect_vetoes takes the interpolated set prepare_ngmix_weights
uses, removed noisefill neighbours included, so a run that ends on a
neighbour is vetoed at the noise-fill radius it gets. A non-finite
interpolant raises instead of falling back to noise. Off-tile pixels
get zero weight instead of a flag sentinel. Only interpolated holes are
weight-symmetrized; the #815 neighbour-side option is dropped.

The defect science tests merge into tests/science/test_defect_recovery.py,
and the module tests lose their option plumbing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4HLdzDyc3CiPLrgJ48GVo
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4HLdzDyc3CiPLrgJ48GVo
… the set

interpolate_defects fits one Clough-Tocher interpolant instead of averaging
four quarter-turned ones. For the defects the veto keeps the average moves
c by at most 5e-5 and m by at most 4e-4 (test_defect_recovery cases), and
the quarter-turn weight copies already make the likelihood symmetric.

interpolated_defects(defect, neighbour, blend_handling) is the one rule
for which defect pixels are interpolated; prepare_postage_stamps (the
central veto) and prepare_ngmix_weights (the fill) both call it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4HLdzDyc3CiPLrgJ48GVo
pixel_classes compares noisefill and uberseg; defect_gallery shows raw,
image and weight under the default. Both take the interpolated set from
interpolated_defects, and violet marks only pixels zeroed by the
quarter-turn symmetrization. dr6_epochs gives off-tile pixels zero weight
and stores the off_tile mask.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4HLdzDyc3CiPLrgJ48GVo
cailmdaley added a commit to cailmdaley/shapepipe that referenced this pull request Oct 2, 2026
…ct type, noise fill as counterfactual

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4HLdzDyc3CiPLrgJ48GVo
cailmdaley added a commit to cailmdaley/shapepipe that referenced this pull request Oct 3, 2026
…ill counterfactual

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4HLdzDyc3CiPLrgJ48GVo
cailmdaley and others added 2 commits October 3, 2026 02:25
EPOCH_MASKED_FRACTION_CUT = 0.10 (was 1/3). The central veto is the bias
control; far-field defects bias nothing measured up to 58% of the stamp,
so the fraction cut is set for DES Y3/Y6 comparability and as a margin
against real-data effects the sims do not model. On tile 202.301 it drops
5.08% of epochs (3.38% at 1/3) and empties 0.55% of objects (0.35%); the
extra drops are all CCD-edge or off-tile bands 10-20 px from the object.

astra.yaml: ten_percent is the default, one_third excluded with the
reason; universes/committed.yaml follows. Tests: the defect-set cut test
uses 9.8%/11.8% epochs; corner and edge-neighbour classification tests use
geometries under 10%; the off-tile central-veto test lifts the fraction
cut to isolate the veto; the far-field recovery cases no longer assert
against the cut and are documented as the evidence it is not a bias
control.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cailmdaley added a commit to cailmdaley/shapepipe that referenced this pull request Oct 3, 2026
…-fraction cut

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4HLdzDyc3CiPLrgJ48GVo
cailmdaley added a commit that referenced this pull request Oct 3, 2026
…cut) into integration/smk-g13

Brings #922 from 0226edf to b0ce5af: one-orientation defect
interpolant and interpolated_defects() as the one rule for the veto and
the fill (6c75fd4), EPOCH_MASKED_FRACTION_CUT = 0.10 with astra's
ten_percent default (3bd49d3), and validation figures.

One conflict, test_a_neighbour_completing_rows_beside_the_tile_edge_
stays_a_neighbour: #922's geometry (under 10%) with #925's
cut_stamps-based _marked_stamps helper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dj4HnkNqqPkVCeDeUGEGuz
…ough a 0.9" PSF

A noise-filled 4-column cluster (8% of the stamp, kept by the 10% cut)
on that galaxy gives m11 = -6.6% at 10 px and first passes at 13 px.
The science test checks the cluster and the edge band there; the
bias-vs-distance script gains the cluster.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4HLdzDyc3CiPLrgJ48GVo
cailmdaley added a commit to cailmdaley/shapepipe that referenced this pull request Oct 3, 2026
… the 4-column cluster

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4HLdzDyc3CiPLrgJ48GVo
cailmdaley added a commit to cailmdaley/shapepipe that referenced this pull request Oct 3, 2026
@cailmdaley
cailmdaley marked this pull request as draft October 3, 2026 01:01
cailmdaley and others added 2 commits October 3, 2026 18:41
…s frame

One function, defect_weighting_masks, decides how interpolated defects enter
the image and the weight map: fourfold_zero (the PR's design, still the
default), des_y6 (pizza-cutter: mask OR its k=1 quarter turn, interpolated at
full weight), hole and full. The option reaches ngmix from the run config:
`defect_weighting` -> SP_DEFECT_WEIGHTING (tile_ngmix) -> DEFECT_WEIGHTING in
config_tile_Ng_template.ini, validated at parse time against the module's
DEFECT_WEIGHTINGS; tile_vignets carries it too so a change reruns the whole
shape group. Params pin updated (tile_ngmix only): a campaign boundary.

Under every option the metacal noise image is now interpolated in the frame
ngmix's fixnoise shears it in (rot90 k=1, back with k=3), by the science
image's operator. Interpolated in the detector frame, its interpolated pixels
sat a quarter turn from the science image's and left an S/N-dependent
additive bias (c1 = 2.7e-3 for a 3-px bleed at 7 px on the 0.7"/0.9" galaxy
at S/N 30). A test pins ngmix's k=1/k=3 behaviourally.

astra: weight_symmetrization becomes shape_measurement.defect_weighting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4HLdzDyc3CiPLrgJ48GVo
Over the epochs handed to the fit, each object now carries n_epoch_interp
(epochs with an interpolated defect pixel), min_dist_interp and
min_dist_noisefill (smallest distance in pixels from the stamp centre to an
interpolated / noise-filled defect pixel; -1 when there is none). They read
only masks, so distance-split null tests are catalogue queries. make_cat
writes them as NGMIX_N_EPOCH_INTERP, NGMIX_MIN_DIST_INTERP and
NGMIX_MIN_DIST_NOISEFILL (never-fit rows: 0, -1, -1), and both final_cat.param
files list them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4HLdzDyc3CiPLrgJ48GVo
@cailmdaley
cailmdaley marked this pull request as ready for review October 5, 2026 02:40
Comment thread workflow/config/cfis/config_tile_Ng_template.ini Outdated
Comment thread astra.yaml
@martinkilbinger
martinkilbinger self-requested a review October 5, 2026 07:38
cailmdaley and others added 2 commits October 5, 2026 14:20
params_pin.json regenerated with --update-params-pin on the merged tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012yfkBvkzPv33iJxXZ6jAxd
The defect_fill rationale in astra.yaml now says what a defect is, why its
image value matters under metacal, what the fill does, and why. The ngmix
template and config.yaml name the default defect weighting, fourfold_zero.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012yfkBvkzPv33iJxXZ6jAxd
cailmdaley added a commit that referenced this pull request Oct 5, 2026
#922 was rebuilt after this branch took it, so the two carry different
histories of the defect fill. Resolved to #922's current design
(interpolation is the only fill, DEFECT_WEIGHTING, 10% masked-fraction cut,
13 px noise-fill veto) with this branch's SEG_VIGNET UberSeg on top:

- ngmix.py: no seg_cat_path; Raises names blend_handling/defect_weighting.
- committed.yaml: defect_fill interpolate, blend_handling uberseg,
  defect_weighting fourfold_zero, epoch_masked_fraction_cut ten_percent.
- tile.smk: tile_ngmix exports SP_DEFECT_WEIGHTING and the blend variables.
- astra.yaml: #922's defect_fill rationale; this branch's uberseg-default
  blend_handling text.
- tests: drop the DEFECT_FILL tests #922 removed; the committed-universe
  test checks the fill is the noisefill one under the committed blend
  handling; _marked_stamps keeps cut_stamps with #922's 20-px-edge geometry;
  test_sextractor_seg_vignet uses uberseg_mask; test_dag normalizes the
  run-dir hash in the node-local store name (develop's LOCAL_TAG), which
  differs between the campaigns these tests compare.
- params pins regenerated with --update-params-pin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012yfkBvkzPv33iJxXZ6jAxd
cailmdaley and others added 2 commits October 5, 2026 15:34
…aches it

A copy wedged in a corner of the clean region (an edge band beside the
stamp edge) lies outside the support's hull, so the fill came back NaN and
the finiteness guard dropped every epoch of the object. Such copies now
keep their light, like copies on already-replaced pixels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012yfkBvkzPv33iJxXZ6jAxd
An exception while building one epoch's ngmix observation (for example the
non-finite interpolant guard in prepare_ngmix_weights) used to reach the
per-object except, losing every epoch of the object. do_ngmix_metacal now
catches it per epoch, drops that epoch and fits the object on the rest; the
existing epoch logic (N_EPOCH, masked-fraction cut) decides the rest. An
object whose every epoch fails still raises.

The drop is kept: Postage_stamp.epoch_failures holds (epoch name, exception),
process() logs each at warning level with tile, object and epoch, adds them to
the "epoch cuts" tally as failed=, and writes the count as a new per-object
column n_epoch_failed -> NGMIX_N_EPOCH_FAILED (final_cat.param for cfis and
cfis_image_sims; never-fit rows 0). astra.yaml epoch_masked_fraction_cut
records the rule; contract failed-epoch-dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012yfkBvkzPv33iJxXZ6jAxd
cailmdaley added a commit that referenced this pull request Oct 5, 2026
…seg-vignet

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012yfkBvkzPv33iJxXZ6jAxd

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A/B: 4-fold weight symmetrisation — shape impact and S/N cost

2 participants