Repository navigation
UberSeg neighbour masking (Erin Sheldon) — config-selectable, validated against noise-fill #776
Description
Activity
@aguinot @martinkilbinger @fabianhervaspeters — the one open decision here is the segmentation-map source (see Open decision above): extend
vignetmakerto cut + reproject a coaddSEGMENTATIONvignet per epoch (Sheldon-faithful, à la MEDSinterpolate_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- linked a pull request that will close this issuengmix: config-selectable UberSeg neighbour masking (BLEND_HANDLING) #770
on Jun 30, 2026 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
- added a commit that references this issue
on Jul 12, 2026 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
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}, defaultnoisefill, read likeCENTROID_SOURCE. With the default the behaviour is byte-for-byte identical to today (asserted in tests).uberseg_weight(weight, seg, object_number)reimplements Sheldon'smeds._uberseg.uberseg_treenearest-segment Voronoi partition with a scipycKDTree. We reimplement rather than depend onmeds: the astronomy MEDS isesheldon/meds(not the PyPImeds, which is an unrelated package), andimport medsdrags the fullfitsio/esutilstack for a single C tree. The surviving central core is a single connected, roughly circular region — emergent geometry of the partition, faithful to MEDSget_uberseg(no separate circular aperture).Ngmix→do_ngmix_metacal→make_ngmix_observation→prepare_ngmix_weights;Postage_stampgains a per-epochsegslist.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:vignetmakerto cut a coaddSEGMENTATIONvignet (the tile SExtractor already has theCHECKIMAGEmachinery; it currently emits onlyBACKGROUND) and reproject it into each epoch frame via the per-epoch WCS — Sheldon-faithful, but needs nearest-neighbour resampling of an integer seg map.PR #770 implements everything not gated by this (the algorithm, the
BLEND_HANDLINGplumbing, the consumption seam, the unit tests); selectingubersegwithout 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
ubersegpath 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 aFLAGS=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_madnoise-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