UberSeg end to end on #933's tile detection (SEG_VIGNET from the tile SExtractor run) - #925
cailmdaley wants to merge 52 commits into
Conversation
…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>
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>
…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
A committed ini can then name a workflow knob with its default, and the workflow exports the variable only when the knob leaves its default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under SEG_VIGNET = True the converter adds one int32 stamp per object of the relabelled segmentation map, cut with the window and centres VIGNET is cut with, so the two register pixel for pixel. The stamp cutter moves to sextractor_script.cut_stamps for the SExtractor path to share. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under SEG_VIGNET = True, sextractor_runner cuts the SEGMENTATION check image on each object's VIGNET grid and adds it to the sexcat. VIGNET is centred on the pixel nearest SExtractor's double barycentre; the float32 X_IMAGE/Y_IMAGE round to it except on exact half pixels, which are resolved by matching VIGNET against image minus BACKGROUND. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tile_cat takes the stamps from the catalogue it already reads, and BLEND_HANDLING = uberseg fails before the object loop when the column is missing. SEG_VIGNET_PATH, its separate catalogue and its checks go. The stamp columns stay views into the loaded table, so it is held once, and BLEND_HANDLING is environment-expanded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
blend_handling: uberseg (config.yaml, default noisefill) exports
SP_SEG_VIGNET=True to tile_detect and SP_BLEND_HANDLING=uberseg to
tile_ngmix, and adds the SEG_VIGNET column's memory to each ngmix chunk.
The committed inis read them as ${SP_SEG_VIGNET:-False} and
${SP_BLEND_HANDLING:-noisefill}, so noisefill plans the campaign it
planned without the knob and the params pin does not move.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A noisefill campaign launched from a shell that exports SP_SEG_VIGNET or SP_BLEND_HANDLING (bare, or in the APPTAINERENV_/SINGULARITYENV_ forms that survive --cleanenv) would hand them to every job through the slurm executor's --export=ALL. The Snakefile now pops them at parse time, so only blend_env's exports under uberseg set them. Prologues and shells are unchanged; the DAG harness gains a launch_env to test it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under SEG_VIGNET, sextractor_runner runs SExtractor with a copy of the param file that adds X_IMAGE_DBL and Y_IMAGE_DBL; add_seg_vignet centres each stamp at rint(DBL - 1), as SExtractor centres VIGNET, and drops the two columns, so the catalogue is noisefill's plus SEG_VIGNET. The half-pixel tie matching against image minus background goes, and with it the BACKGROUND check image dependency. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
blend_handling defaults to uberseg in config.yaml and the Snakefile, so a campaign without the key writes SEG_VIGNET in both detection modes and runs ngmix with BLEND_HANDLING = uberseg. The committed inis keep the modules' own noise-fill defaults and the workflow exports the uberseg values, so blend_handling: noisefill plans exactly the previous noise-fill campaign: params_pin_noisefill.json is the previous pin, and a noise-fill run dir resumes with the key set explicitly. The default pin moves in tile_detect and tile_ngmix only. astra's committed blend_handling becomes uberseg, with noisefill the comparison arm. test_committed_blend_handling_fills_defects checks the committed blend_handling x defect_fill pair against prepare_ngmix_weights: the workflow default is the committed blend handling, and under it every defect is zero-weighted and filled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BbT81hykcvtuefzB3FhYvY
7af2d80 to
c704e63
Compare
|
Yikes, is there any way to use uberseg without actually doubling the disk and memory? |
A value with ${VAR:-default} expands to default when VAR is unset or
empty, as in the shell; $VAR and ${VAR} stay strict. The same change as
feat/uberseg-seg-vignet's, so the two merge cleanly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MATCH_CATALOGUE (an ASCII SExtractor catalogue of the same image) pairs each SExtractor row with its mutual nearest neighbour within MATCH_RADIUS pixels; paired rows take its NUMBER, unpaired rows leave the catalogue, and a SEG_VIGNET column, when present, is relabelled to the new numbering. Fewer than MATCH_MIN_FRACTION of the rows pairing stops the run: that is what a catalogue measured on other pixels (a DR5 image against DR6) looks like. The join runs before the post-processing keys epochs on NUMBER. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Data and image simulations both run tile_detect as SExtractor (config_tile_Sx.ini), so XWIN, FLAGS_WIN, the VIGNET neighbour markers and every SExtractor column are measured one way. Under tile_detection: unions_catalogue, tile_get_catalogue fetches only the UNIONS .cat and tile_detect joins its detections to it (SP_MATCH_CATALOGUE), keeping the shared NUMBER and TILE_UNIQUE_ID. On DR6 186.307 all 41,201 detections pair (median separation 4e-5 px); 45 catalogue objects, faint deblended children, have no detection. Gone: read_ext_sexcat and its tests, config_tile_Uc.ini, the runner link, the segmentation-map fetch, merge_final_cat's SEXTRACTOR_ONLY_COLUMNS and --tile-detection, and the detection.catalogue_neighbour_marking decision. completeness.py changes, so this lands at a campaign boundary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without DETECTION_IMAGE the runner called SExtractor in dual-image mode with the same image and weight twice, which changes the background, MAG_AUTO and FLUX_RADIUS of a fifth of a tile's objects against the single-image call and the UNIONS catalogue. Dual-image mode stays for a real detection image. Closes #936 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ation The run also stops when fewer than MATCH_MIN_FRACTION of the catalogue objects pair, which catches a detection configuration that finds fewer objects than the catalogue. Tests cover the radius bound and both sides of the guard. astra.yaml records the eight-tile numbers, single-image detection, and the MAG_AUTO differences beyond a pixel-stack overflow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
${VAR-x}, ${VAR:=x} and ${ VAR } passed through as literal paths.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Peak RSS 1.75 GiB and wall time 143 s at most; SExtractor is built without threads. One retry doubles memory and runtime. Image simulations export SP_MATCH_CATALOGUE empty. Stale comments on the catalogue fetch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
300000 overflowed on four of eight DR6 tiles (a cD galaxy, a bright star's halo, 13 times on Alioth's tile); 3000000 clears all eight. Peak RSS rises to 1.84 GiB and wall time to 160 s on the worst tile; tile_detect's 4000 MB stays at 2.1x. The exposure config never overflows on the 40 CCDs checked and is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The guard catches other pixels (DR5 pairs ~95%), not deblending near large objects (DR6 tiles pair at least 99.2%) or a few children on a near-empty edge tile. New key MATCH_TOLERATED_UNPAIRED = 20; MATCH_MIN_FRACTION = 0.98. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Any way to use UberSeg without doubling disk and memory? Yes. It doesn't double anything today, and two small changes bring its overhead close to zero. Measured on the smk-ab925 A/B (same 25 tiles, code 4570a907). What it costs now (measured)
Why memory grows 16×, not 1×
Fixes, cheapest first
Under #933On data, #925's SExtractor-mode path is the live one. Unverified: why the per-chunk delta (245 MiB) sits below the column size (323 MiB). The likely cause is the Snakemake benchmark's sampling. Claude Opus 5.5 on behalf of Cail, from the nibi Surveyor's measurements |
#933 runs ShapePipe's tile SExtractor on data too and joins DR6 by match_catalogue, deleting the catalogue converter. SEG_VIGNET now comes from SExtractor mode on data and sims alike. Resolutions: - read_ext_sexcat.py, read_ext_sexcat_runner.py, test_read_ext_sexcat.py, config_tile_Uc.ini: deleted, as in #933. - sextractor_runner.py: add_seg_vignet runs before match_catalogue, which relabels the stamps it finds. - pipeline/config.py: #933's stricter ${VAR:-default} expansion, plus #925's getexpandedboolean. - tile.smk: #933's single tile_detect; blend_env("tile_detect") joins SP_MATCH_CATALOGUE in detect_env. The rule docstring describes SEG_VIGNET on the SExtractor path only. - astra.yaml: catalogue_neighbour_marking stays deleted; the blend and defect texts drop the converter. - workflow/README.md: #933's tile_detection text plus #925's blend_handling. - params_pin.json: #933's side, regenerated in a following commit. - ngmix.py, test_ngmix_defect_fill.py: the masked-pixels tests built tile VIGNETs with the deleted converter; they now mark them as SExtractor does, with sextractor_script.cut_stamps, on the same DR6 patch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017JQtu8oaZQEAqzbxZbPpWs
match_catalogue relabels every SEG_VIGNET label through the whole SExtractor -> DR6 map, unpaired footprints to UNMATCHED_LABEL (-1). That separates each row's own footprint from every other one only if the catalogue's NUMBERs are unique and positive, so the join now refuses a catalogue where they are not. Tests, through sextractor_runner with SExtractor's outputs stubbed: on a crowd where each paired row's DR6 NUMBER is another object's SExtractor NUMBER (one of them the dropped detection's), each own footprint carries the final NUMBER and nothing else does, the dropped footprint is -1, and uberseg_weight and seg_has_neighbour see the stamps as they saw the SExtractor-labelled ones. Sims (no join) keep the check image's stamps. A negative control shows a partial relabel collides. make_cat's exclusion of SEG_VIGNET moves here from the deleted converter tests. Swapping the join before add_seg_vignet, relabelling only paired labels, or dropping the NUMBER guard each fails a test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017JQtu8oaZQEAqzbxZbPpWs
Measured on DR6 tile 202.301 with sextractor_runner as tile_detect runs it (post-processing off): peak RSS 1.53 GiB under noisefill, 2.93 GiB under uberseg. add_seg_vignet reads the 400 MB check image and adds an int32 column the size of VIGNET, which the join holds twice. Scaled to #933's 1.84 GiB worst tile, uberseg needs ~3.5 GiB, above the 4000 MB noisefill request; 7000 MB is 2x that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017JQtu8oaZQEAqzbxZbPpWs
Regenerated with --update-params-pin in shapepipe-develop-240b37e4.sif with host snakemake 9.23.1. The noisefill pin is #933's params_pin.json byte for byte, so blend_handling: noisefill plans exactly #933's chain; the uberseg default moves in tile_detect and tile_ngmix only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017JQtu8oaZQEAqzbxZbPpWs
…test reads it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017JQtu8oaZQEAqzbxZbPpWs
SExtractor 2.25.0 (src/analyse.c, ix=(int)(obj->mx+0.49999); the SIF's Debian 2.25.0+ds-3 does not patch it) truncates the 0-based barycentre plus 0.49999. np.rint differs at exact halves with an odd floor and for fractional parts in (0.5, 0.50001); vignet_centre now mirrors SExtractor. The tests' expected centres are written out by hand from that rule and their stamps cut by plain slicing, not by cut_stamps; they cover exact halves and fractions either side of 0.50001, and an object only the double positions resolve. Reverting to np.rint fails five of them. On DR6 tile 202.301 no object falls where the two rules disagree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017JQtu8oaZQEAqzbxZbPpWs
The guard took unique positive NUMBERs but not their type or range: a non-integer, or a value past 2**31 - 1, narrows on the way into the int32 NUMBER and SEG_VIGNET columns (4294967295 becomes -1, UNMATCHED_LABEL). The join now requires integers in [1, MAX_NUMBER]. Boundary tests cover repeats, 0, negatives, 5.5, 5.0, 2**31, 2**32 - 1 and accept 2**31 - 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017JQtu8oaZQEAqzbxZbPpWs
The sextractor, match_catalogue, tile.smk, workflow README and params pin conflicts are develop's squashed #933 against the same content here, so they keep this branch's side: add_seg_vignet before match_catalogue, the full-table relabel, the NUMBER guard and SExtractor's VIGNET centring. ngmix: #927 replaces the PIXEL_SCALE option with each object's own pixel scale from its epochs' Jacobians; develop's side is taken, and the masked-pixels tests stop passing a pixel scale to Ngmix. #927's centroid prior test sets the defect-fill attributes and epoch_cuts that process reads on this branch. The regenerated params pins are unchanged; the noisefill pin is develop's params_pin.json byte for byte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017JQtu8oaZQEAqzbxZbPpWs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UberSeg on #933's tile detection: on data as on sims, the SEG_VIGNET stamps come from the tile SExtractor run's own segmentation map. Contains #922 (masked pixels), which this builds on.
UberSeg on a blended galaxy (tile 202.301, NUMBER 15152, MAG_AUTO 22.7). Left: the tile VIGNET, with the brighter neighbour's footprint masked (orange). Middle: SEG_VIGNET, the SExtractor segmentation relabelled to DR6 NUMBERs. Right: the UberSeg weight. Every pixel nearer the neighbour's footprint than to the galaxy's own (outlined) gets zero weight, 33% of the stamp here, and the galaxy keeps full weight.
Why every label goes through one map. The join drops SExtractor detection 23409, and 23409 is also an unrelated DR6 NUMBER on the same tile; all 8 dropped labels on 202.301 collide like this. A partial relabel (middle) leaves those pixels reading as that other object. The full relabel (right) marks them −1, an anonymous neighbour.
Problem
UberSeg (
uberseg_weight,seg_has_neighbour,Ngmix._check_central_seg_label) needs a segmentation stamp per object, on VIGNET's grid. It compares the stamp's labels with the row'sNUMBER. #925 writes these stamps as the tile catalogue'sSEG_VIGNETcolumn in two ways: from the DR6 segmentation map in the catalogue converter, and from SExtractor's SEGMENTATION check image. #933 deletes the converter. Data now runs ShapePipe's own tile SExtractor, andmatch_cataloguejoins it to DR6, so each row takes DR6'sNUMBERand unpaired detections leave the catalogue. The SExtractor path now serves data and sims alike. On data, though, the check image is labelled with SExtractor's numbers, while the rows carry DR6's.The two numberings overlap almost completely. On tile 202.301, 35,971 of the 36,014 final
NUMBERs are also SExtractorNUMBERs on the tile, and all 8 dropped detections have SExtractor labels that are also finalNUMBERs of other rows. A stamp that keeps any old label can therefore show a neighbour carrying the object's own number. UberSeg would then give the neighbour's pixels to the object.Change
sextractor_runnercutsSEG_VIGNET(add_seg_vignet) right after SExtractor and beforematch_catalogue. The join relabels aSEG_VIGNETcolumn it finds.SEG_VIGNETis cut where SExtractor cuts VIGNET. SExtractor 2.25.0 (src/analyse.c,ix=(int)(obj->mx+0.49999)) truncates the 0-based barycentre plus 0.49999, and the SIF's Debian2.25.0+ds-3package doesn't patch that file.vignet_centremirrors the rule onX_IMAGE_DBL - 1andY_IMAGE_DBL - 1. It differs from round-to-nearest at exact halves with an odd floor and for fractional parts in (0.5, 0.50001). The float32X_IMAGEcan't resolve those cases, so SExtractor also writes the double positions, which are dropped once the stamps are cut.match_cataloguemaps every stamp label through a single lookup table. A paired row's SExtractorNUMBERbecomes its DR6NUMBER. A dropped row's label, and any label not in the catalogue, becomesUNMATCHED_LABEL= −1. Sky stays 0. The pairing is one-to-one, so this keeps the property UberSeg relies on: the row's own footprint is exactly the set of pixels labelled with itsNUMBER. This works because the consumers only ask own-versus-other. The dropped footprint stays a neighbour (its light is still in the image) under a label no row can have.This holds only if DR6
NUMBERs are unique integers that the int32NUMBERandSEG_VIGNETcolumns hold exactly. The join now refuses a catalogue with a repeated, non-positive, non-integer or above-2³¹−1NUMBER; for example, 4294967295 would narrow to −1, the dropped label. That is the only code change tomatch_catalogue. Detect tiles with SExtractor on data too; take NUMBER from the UNIONS catalogue #933 already had the relabel; this PR tests it against collisions and guards its premise.A partial relabel, which maps only the paired labels and leaves the rest, collides exactly as described above. The tests include it as a negative control.
blend_handling: uberseg,tile_detectasks for 3,000 MB more (DETECT_SEG_MEM_MB), i.e. 7,000 MB. On 202.301, peak RSS is 1.53 GiB under noisefill and 2.93 GiB under uberseg.add_seg_vignetreads the 400 MB check image and adds an int32 column the size of VIGNET, which the join holds twice. Scaled to Detect tiles with SExtractor on data too; take NUMBER from the UNIONS catalogue #933's worst tile (1.84 GiB), uberseg needs about 3.5 GiB. Runtime is 170 s against 80 s, well inside 20 min.config_tile_Uc.inistay deleted.pipeline/config.pytakes Detect tiles with SExtractor on data too; take NUMBER from the UNIONS catalogue #933's stricter${VAR:-default}expansion and keeps UberSeg end to end on #933's tile detection (SEG_VIGNET from the tile SExtractor run) #925'sgetexpandedboolean.tile.smkkeeps Detect tiles with SExtractor on data too; take NUMBER from the UNIONS catalogue #933's singletile_detect, anddetect_envaddsblend_env("tile_detect")toSP_MATCH_CATALOGUE.astra.yamlkeepscatalogue_neighbour_markingdeleted and drops the converter from the blend and defect texts. Some masked-pixels tests intest_ngmix_defect_fill.pybuilt tile VIGNETs with the converter. They now mark stamps as SExtractor does (cut_stamps) on the same DR6 patch. UberSeg end to end on #933's tile detection (SEG_VIGNET from the tile SExtractor run) #925's make_cat exclusion test moves to the SExtractor tests.--update-params-pin(SIF plus host snakemake 9.23.1).params_pin_noisefill.jsonis byte-identical to Detect tiles with SExtractor on data too; take NUMBER from the UNIONS catalogue #933'sparams_pin.json, soblend_handling: noisefillplans exactly Detect tiles with SExtractor on data too; take NUMBER from the UNIONS catalogue #933's chain. The uberseg default movestile_detectandtile_ngmixonly.Verification
tests/module/test_sextractor_seg_vignet.py) go throughsextractor_runnerwith SExtractor's outputs stubbed. They use a crowd in which each paired row's DR6NUMBERis another object's SExtractorNUMBER, one of them the dropped detection's. Each own footprint carries the finalNUMBERand nothing else does. The dropped footprint is −1.uberseg_weightandseg_has_neighbourgive the same answers as on the SExtractor-labelled stamps. Without a join (sims), the stamps are the check image's. Each of these injected bugs fails the tests: the join running beforeadd_seg_vignet, relabelling only paired labels, dropping theNUMBERguard or its type and range checks, and centring withnp.rint.vignet_centreagainst hand-written centres: exact halves (31.5 → 30, 32.5 → 31), fractions just below and above 0.50001, and a negative 0-based position. A scene test cuts its expected stamps by plain slicing, not bycut_stamps, at those hand-written centres. It includes objects wherenp.rintof the double positions is wrong, and one where SExtractor's rule on the float32 positions is wrong.NUMBERguard: repeats, 0, −3,5.5,5.0, 2³¹ and 2³²−1 are refused, and 2³¹−1 is accepted./project/def-mjhudson/unions-wl/into/scratch/cdaley/uberseg933/tile/. The realsextractor_runnerran with the committedconfig_tile_Sx.ini,SP_SEG_VIGNET=TrueandSP_MATCH_CATALOGUEset, and post-processing off (allocation 23087393). Results:np.rintdisagree, and none has an exact half (checked by a separate SExtractor run that writesX/Y_IMAGE_DBL, allocation 23094126). The numbers below were measured before the centring fix, so they hold unchanged.NUMBERin 36,013 of 36,014 stamps. The exception is a 24.2 mag object whose barycentre lies on sky (centre label 0); its own label is in the stamp.NUMBER.X/Y_IMAGEcentre (half-pixel ties). On that grid,SEG_VIGNETequals the relabelled check image for all 35,857. For the other 157 objects, VIGNET holds a constant value on some of the object's own pixels, so the grid can't be found from the pixels. For those,SEG_VIGNETequals the relabelled cut at the rounded centre.SEG_VIGNET's foreign labels to 99.2% both ways.tests/workflow: 22 passed.Merge notes
Retargeting UberSeg end to end on #933's tile detection (SEG_VIGNET from the tile SExtractor run) #925 onto Detect tiles with SExtractor on data too; take NUMBER from the UNIONS catalogue #933 brings
feat/masked-pixels's 18 commits into the diff, as they are already in UberSeg end to end on #933's tile detection (SEG_VIGNET from the tile SExtractor run) #925.git merge-treeagainst current heads.developis 615e73f (Compress the merged campaign HDF5 with lzf #937, Read the tile WCS header log lock-free in make_post_process #938 and make_cat: write the final catalogue once per save stage, node-local #939 merged).developfix/stable-fwhm-modefeat/wire-external-masksparams_pin.json,workflow/config.yamlfeat/instrument-defect-mapparams_pin.json,workflow/README.md,workflow/config.yamlfeat/retire-versioningdocs/source/pipeline_canfar.mdchore/drop-unused-weight-vignetstests/unit/test_config_parse.py,params_pin.jsonfix/pixel-scale-from-wcsngmix_package/__init__.py,ngmix.py,ngmix_runner.py,test_ngmix.py,test_ngmix_uberseg.pyThe port adds no new conflict.
Campaign boundary: the uberseg default changes
tile_detectandtile_ngmixprologues relative to Detect tiles with SExtractor on data too; take NUMBER from the UNIONS catalogue #933. A campaign that ran on Detect tiles with SExtractor on data too; take NUMBER from the UNIONS catalogue #933 resumes unchanged only withblend_handling: noisefill.Out of scope
match_cataloguestill holds the catalogue twice. Streaming the join would cuttile_detect's peak memory under both blend handlings.LDAC_OBJECTScolumn, so it doesn't measure the post-processing's memory or time with the larger catalogue.Claude Opus 5.5 on behalf of Cail
🤖 Generated with Claude Code