Skip to content

UberSeg neighbour masking (Erin Sheldon) — config-selectable, validated against noise-fill #776

Description

@cailmdaley

What

Add a config-selectable UberSeg neighbour treatment to the ngmix module, alongside the current noise-fill, and decide how the segmentation map reaches the per-epoch stamps.

Today a neighbour sharing a galaxy's postage stamp is handled by filling the neighbour's pixels with noise (prepare_ngmix_weights): the per-epoch flag vignet marks them and they are replaced by a Gaussian realisation before the stamp reaches ngmix. The 2026-06-23 tomography call flagged this as suspect — including blended (FLAGS=2) objects produces B-modes, while discarding them is a strong, non-metacalibratable selection bias. UberSeg (Sheldon's MEDS/ngmix treatment) is the principled alternative: mask (weight → 0) every stamp pixel closer to a neighbour than to the central object, rather than synthesising noise over it.

Implementation status

The option is implemented additively, default unchanged, in PR #770:

  • BLEND_HANDLING = {noisefill, uberseg}, default noisefill, read like CENTROID_SOURCE. With the default the behaviour is byte-for-byte identical to today (asserted in tests).
  • uberseg_weight(weight, seg, object_number) reimplements Sheldon's meds._uberseg.uberseg_tree nearest-segment Voronoi partition with a scipy cKDTree. We reimplement rather than depend on meds: the astronomy MEDS is esheldon/meds (not the PyPI meds, which is an unrelated package), and import meds drags the full fitsio/esutil stack for a single C tree. The surviving central core is a single connected, roughly circular region — emergent geometry of the partition, faithful to MEDS get_uberseg (no separate circular aperture).
  • Plumbed runner → Ngmix → do_ngmix_metacal → make_ngmix_observation → prepare_ngmix_weights; Postage_stamp gains a per-epoch segs list.
  • Unit tests: synthetic two-object stamp (mask geometry, single-connected core, matches brute-force nearest-segment), noisefill byte-identity, uberseg leaves the image untouched while hard-masking the weight.

⚠️ Open decision — the segmentation-map source

UberSeg needs the segmentation map with object IDs at each stamp, plus the central object's NUMBER. ShapePipe passes only a binary flag vignet to ngmix today, cut per-exposure (multi-epoch). The segmentation map with NUMBERs is the coadd's (the tile SExtractor detection) — but ngmix fits per-epoch exposure stamps, in different pixel frames. MEDS itself resolves this by interpolating the coadd seg into each single-epoch frame (interpolate_coadd_seg). So the source is genuinely forked:

  1. Extend vignetmaker to cut a coadd SEGMENTATION vignet (the tile SExtractor already has the CHECKIMAGE machinery; it currently emits only BACKGROUND) and reproject it into each epoch frame via the per-epoch WCS — Sheldon-faithful, but needs nearest-neighbour resampling of an integer seg map.
  2. Approximate from the existing per-epoch binary flag vignet — no reprojection, but the binary flag carries no per-object IDs, so the "closer to neighbour than to central" distance transform cannot be computed faithfully (it conflates neighbours with cosmic rays / bad pixels).

PR #770 implements everything not gated by this (the algorithm, the BLEND_HANDLING plumbing, the consumption seam, the unit tests); selecting uberseg without a seg map raises with a pointer here. This decision wants the shape-measurement experts (Axel Guinot, Martin Kilbinger, Fabian Hervas Peters): which seg source, and is per-epoch reprojection of the coadd seg the right path?

Science note for the A/B validation

Following the explicit design intent (and Axel's "leave neighbour objects on the image" proposal), the uberseg path zeroes the weight and leaves the image untouched — it does not noise-fill the masked region. The open consideration: metacal shears the image in Fourier space using all pixels regardless of weight, so residual neighbour flux can leak through the shear even at weight 0. This is exactly what the noise-fill-vs-uberseg A/B B-mode comparison (on a FLAGS=2-inclusive sample, via the Paper II / sp_validation B-mode machinery) must measure — and what Axel's weight-symmetrisation follow-on (out of scope here) would address. Fabian validates the science before any production default flip.

Scope fence

Add the option, its tests, the consumption seam; resolve the seg source; then validate. Not in scope: changing the noise-fill path, flipping the production default, the weight-symmetrisation / cosmic-ray-only-noise refinement, or the get_noise-vs-sigma_mad noise-estimate question (that belongs with #769).

Implemented (un-gated parts) by PR #770 (Closes #776). Part of ShapePipe longer-term improvements (#773) — not blocking the ngmix v2.0 merge.

— Claude, on behalf of Cail

Activity

  1. cailmdaley commented on Jun 30, 2026

    @cailmdaley
    ContributorAuthor

    @aguinot @martinkilbinger @fabianhervaspeters — the one open decision here is the segmentation-map source (see Open decision above): extend vignetmaker to cut + reproject a coadd SEGMENTATION vignet per epoch (Sheldon-faithful, à la MEDS interpolate_coadd_seg) vs approximate from the existing binary flag vignet (no per-object IDs). Which is the right path, and is per-epoch reprojection of the coadd seg the way to go? PR #770 implements everything not gated by this. — Claude on behalf of Cail

  2. aguinot commented on Jun 30, 2026

    @aguinot
    Contributor

    Hello,
    I think a lot of effects are being mixed up here and I'll my best to clarify it. I'll separate this 2 groups: shearing & fitting.

    Shearing:
    What I was implemented is to replace objects by noise. This is not necessary and is not a problem at all. Neighbouring objects leaking is perfectly AS LONG AS THEY ARE AT THE SAME REDSHIFT. This is alos what metadetect does. If they are at different redshift it is an issue that even metadetect cannot solve and requires simulations. So this part of the code is not necessary, I am not convince it would raise a bias per say though. Now the thing to be careful of when doing metacal is to avoid discontinuity. In that sense, neighbours are fine but saturated objects, dead pixels, cosmic rays, etc are not. Anything that create a jump in the image conitnuum is a problem when doing the FFT. Here you have 2 options:

    • Interpolated over: this works from small masks. Replacing by noise like I was doing might be fine too.
    • Mask: ususally the bright stars are mask using an apodized mask to avoid discontinuity.
      Given that we are doing metacal, the second is not necessary as we can reject at the catalogue level objects around bright, saturated stars. The first should be easy as long as we have good single exposure mask (don't use the coadd maks here!). Replacing by noise is likely the easier and faster. You could also assume that this is negligible and simply ignore it.

    fitting
    This is were handling neighbour is important and there are several ways to do it. The easiest is to use weighted moments (with a fixed weight function!). With this method most of the light coming from the neighbour will be rejected and the fit should be ok for most cases. Now if you are doing model fitting (or adaptive moment), the fit can be influence by neighbouring objects and light in general. In this case objects need to be masked. This is already the case, but the problem by sticking strickly to the segmentation map is that you introduce asymetric weight which could influence the fit. Th ubersegmentation partially solve this by setting to 0 a fraction of the image instead of localized group of pixels. On top of that you can symmetrize the weights (4-fold rotation) which completely remove the "pulling" from a single side. This is the least biased solution. The other solution is to ignore all that and assume that your image simulation is realistic enough to reproduce those effects and calibrate. But the mentality in what I described is to reduce as much as possible the residual bias that needs to be calibrated. Finally, regarding which segmentation map to use. Do not use the single exposure not reliable and if it is not the same in each epoch you might end up with weird effects going on. Personally I would just use the coadd one. In our case the interpolation would be overkill and will probably lead to a few pixels difference because we don't have field rotation (our coadd are always "aligned" with the single exposures). So I would keep it simple with the coadd seg map and no interpolation. It might be good to enlarge it a bit to be safe (scipy has function to do that with binary mask). You also don't need the id of the segmentation map technically and you can extract it from the coadd stamps (but you could also get the actual segmap for the coadds). I would also propagate to the final catalogue a flag to know if there was a neighbouring object or not so you could run test to see if it has an impact on systematics.

    I hope this is clear enough. Feel free to ask clarifying questions

  3. cailmdaley commented on Jul 15, 2026

    @cailmdaley
    ContributorAuthor

    thank you Axel for clarifying that distinction, i learned a lot reading this! i have tried to incorporate your feedback and suggestions in #770 as you've seen already

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions