Consistent use of the external healsparse masks - #847
Conversation
…images New mask_ext module (PRD in #847): reads the image WCS, evaluates the pixel grid in memory-bounded row chunks, queries the healsparse map, applies a config-driven bit->flag mapping, optionally sums the external instrument flag (matching Mask._build_final_mask semantics), and writes the standard int16 <PREFIX>_flag<num>.fits. Same module serves tiles and exposure CCDs; mask files and bit meanings live only in config. Example tile/exposure configs; 7 unit tests on synthetic maps (bit mapping, chunk-seam independence, RA wrap, off-map, ext-flag sum, WCS round-trip). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CfRuCQa2UHo44yp2MZDsJX
|
Policy update from the 2026-07-21 mask-force telecon (details on #845): the rasterizer design here is unchanged, but the — Claude (fable) on behalf of Cail |
martinkilbinger
left a comment
There was a problem hiding this comment.
Looks all pretty good.
One question: Why is only the footprint mask ("BIT_FLAG_MAP = 64:1") in the config files? Should these not also include the star, maximask, and manual masks?
…images New mask_ext module (PRD in #847): reads the image WCS, evaluates the pixel grid in memory-bounded row chunks, queries the healsparse map, applies a config-driven bit->flag mapping, optionally sums the external instrument flag (matching Mask._build_final_mask semantics), and writes the standard int16 <PREFIX>_flag<num>.fits. Same module serves tiles and exposure CCDs; mask files and bit meanings live only in config. Example tile/exposure configs; 7 unit tests on synthetic maps (bit mapping, chunk-seam independence, RA wrap, off-map, ext-flag sum, WCS round-trip). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CfRuCQa2UHo44yp2MZDsJX
c25cfdf to
f6b9461
Compare
First step of the external-healsparse-mask path (#846): the reader rasterizes maskforce healsparse products onto image pixel grids in place of internal mask generation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CfRuCQa2UHo44yp2MZDsJX
…images New mask_ext module (PRD in #847): reads the image WCS, evaluates the pixel grid in memory-bounded row chunks, queries the healsparse map, applies a config-driven bit->flag mapping, optionally sums the external instrument flag (matching Mask._build_final_mask semantics), and writes the standard int16 <PREFIX>_flag<num>.fits. Same module serves tiles and exposure CCDs; mask files and bit meanings live only in config. Example tile/exposure configs; 7 unit tests on synthetic maps (bit mapping, chunk-seam independence, RA wrap, off-map, ext-flag sum, WCS round-trip). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CfRuCQa2UHo44yp2MZDsJX
… maps Optional MASK_EXT_PATHS (band:path pairs, env-expanded) in the make_cat section: each band's healsparse map is queried at object world positions (XWIN_WORLD/YWIN_WORLD) and added as a MASK_<BAND> column — the ShapePipe end of UNIONS-WL/spherex#38. Strict no-op when unset; off-map objects carry the map sentinel (-1 for integer maps). 3 unit tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CfRuCQa2UHo44yp2MZDsJX
The real 2025 r-band UNIONS mask (mask_r_nside131072.hsp) is a boolean healsparse map (True = masked), not an integer bit-flag map. With a boolean map, any BIT_FLAG_MAP bit other than 1 silently selects nothing (True & 64 == 0), producing an all-clean flag image. Raise instead, and document the two mask flavours in the example configs. Found by the real-data smoke: rasterizing the candide mask copy onto the CFIS.233.293 tile grid. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013btLTHCgmiiggZmxJ4hM3n
The catalogue-level mask query path (parse_mask_ext_paths, save_mask_ext_data) added in this PR had no shipped example. Document MASK_EXT_PATHS in config_make_cat_psfex.ini, the tile-level MAKE_CAT_RUNNER config, matching the design language used for the rasterizer's BIT_FLAG_MAP in config_tile_MaExt.ini. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KQnUyBXB85PdFF4xAKC6WC
f6b9461 to
f554e2b
Compare
ShapePipe stops making masks (#846, part of #845). Gone in one piece: the `mask` module (GSC 2.3 Vizier queries, Messier/NGC region files, the WeightWatcher .ww default and the halo/star .reg templates), the `mask_ext` rasterizer that briefly stood in for it, and every config that ran either — the *.mask / *.mask_simu mask configs, config_{exp,tile}_Ma_onthefly.ini, config_{exp,tile}_MaExt.ini, the defunct MaMa and Sx_exp_* variants, and workflow/config/cfis/config_exp_Ma.ini with its three dangling symlinks. The star-catalogue staging the mask module needed goes with it: the `star_catalogue` / `exp_star_cat` Snakemake halves (workflow/scripts/star_cats.py), scripts/python/create_star_cat.py and collate_star_cat.py, and shapepipe.utilities.vizier. Two utilities become dead in the same stroke and are deleted rather than left orphaned: `focal_plane` (only create_star_cat and star_cats.py called focal_plane_disc) and `utilities.file_io` (write_atomic had exactly those two callers; it is prose-cited from tile.smk, fixed there). Nothing replaces them with code. The sky-fixed masks are healsparse maps and are queried per object — MASK_<band> columns in make_cat, FLAG_EXT in the new mask_query module — so what used to be a pipeline stage is now a config entry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cem4A9vjxA7nkPnyBKrc5W
…e masks One healsparse lookup, two consumers. shapepipe.utilities.mask_query holds the primitive: `query_map` reads a map and returns its value at each (RA, Dec), and `flag_positions` ORs several maps into one integer per object. `make_cat` now calls query_map for its per-band MASK_<band> columns instead of opening healsparse itself, and the new `mask_query` module runs between sextractor and setools on exposures, writing an integer FLAG_EXT (0 = clean) into a NEW sexcat_ext<num>.fits beside the input — the spread_model `add` pattern, so the LDAC_IMHEAD HDU survives and nothing mutates in place. star_selection.setools gains `FLAG_EXT == 0` beside every `IMAFLAGS_ISO == 0`: setools expressions have no bitwise operators, so the bit selection happens in the module (MASK_PATHS, optional MASK_BITS) and the config only tests for zero. The one interpretive choice is off-coverage. get_values_pos returns a map's sentinel outside its coverage — False for boolean maps, -1 for integer ones. make_cat passes that through verbatim (its documented off-map flag), but flag_positions treats it as NOT flagged for both kinds: OR-ing -1 in would flag every object a map does not reach, i.e. a map whose footprint stops short of an exposure would silently reject all of its stars. Off-coverage counts are logged. Tests build tiny nside_sparse=4096 maps and a three-HDU LDAC fixture and lock in the boolean/integer/MASK_BITS/OR/off-coverage cases, that the LDAC structure and the input file survive, and that make_cat's verbatim behaviour is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cem4A9vjxA7nkPnyBKrc5W
Exposures. SExtractor now reads the instrument flag straight from split_exp (`FILE_PATTERN = image, weight, flag` — split_exp already writes an unprefixed flag<num>.fits per CCD), with the run_sp_exp_Ma INPUT_DIR entry gone; mask_query_runner sits between it and setools in both the example and the workflow config, and star_selection.setools cuts on FLAG_EXT == 0 beside every IMAFLAGS_ISO == 0. Same rewiring in config_exp_mccd.ini, which shares that setools file. sextractor_runner's declared input_module follows the real parent. Tiles. config_tile_Sx_nomask.ini becomes THE tile config: tiles have no instrument flag image and no mask module now, so FLAG_IMAGE = False with default_noimaflags.param is the only variant, in example/cfis and in the sims dir. random_cat still needs a pixel mask image, but nothing in the pipeline produces one — its second input is now declared external (the healsparse-native replacement is #797), which is a real capability gap and is flagged as one in the config, the runner and docs/source/random_cat.md rather than papered over. Snakemake. `exp_psf` now depends on `exp_split` directly; `star_catalogue`, `exp_star_cat`, `exp_mask`, `star_cat_cmd`, `in_container` and the STAR_CAT_* helpers are gone, along with `config["star_cats"]` and the two mid-chain localrules. The `exp_short` group goes too, and the docstring says why: it existed to fuse exp_split with exp_mask, and a group of one rule submits exactly the job the ungrouped rule submits. completeness.py trades its exp_mask stage (mask_runner 40/1) for a mask_query_runner row inside exp_psf, floor 2 rather than 40 because setools tolerates the same sparse-CCD attrition either side of it; run_report drops the two stages; clean_exposure stops reclaiming link farms that no longer exist. Verified in the container: all 30 module runners import, every example and workflow config's MODULE list resolves through get_module_runners, and the workflow parses and builds its DAG (`snakemake --lint`, `-n`). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cem4A9vjxA7nkPnyBKrc5W
pipeline_tutorial.md's "Mask images" section becomes "Masks" and explains the
design instead of the old two-run mask procedure: healsparse maps queried per
object into FLAG_EXT and MASK_<band>, the instrument flag as the one mask that
reaches pixels, no internet access and no star-catalogue download anywhere.
pipeline_canfar.md loses its mask-tiles and mask-exposures steps and the
combine_runs flag_* staging; random_cat.md gains a warning that its input mask
images now come from outside ShapePipe; workflow/README.md and the sims README
describe the chains as they now are.
Dependencies. weightwatcher leaves the Dockerfile and the three docs pages that
listed it — the deleted mask module was its only caller (`ww` appears nowhere
else). astroquery and hpgeom leave pyproject.toml: astroquery had exactly two
importers, utilities/vizier.py and star_cats.py, and hpgeom was the mask_ext
rasterizer's. `uv lock` regenerated the manifest (astroquery, html5lib, pyvo
removed; hpgeom stays, healsparse pulls it in). canfar_avail_results loses its
-m / pipeline_flag check mode, and scripts/README.rst its create_star_cat entry.
Left deliberately: shapepipe.utilities.summary{,_params_pre_v2}'s mask_runner
entries. Those describe the pre-v2 CANFAR job map and parse the logs of runs
that already exist on disk — removing them would break summary_run against
historical trees without making anything current cleaner.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cem4A9vjxA7nkPnyBKrc5W
The diet is settled: instrument flags (IMAFLAGS_ISO, already read by SExtractor) plus the UNIONS star-body map (bit 2, mask_ugriz_nside131072_n4.hsp), and nothing else. The committed [MASK_QUERY_RUNNER] examples in config_exp_psfex.ini (both copies) and config_exp_mccd.ini now name exactly that one map instead of the two-map placeholder. Halo bits 0 and 1 stay out on purpose — halos flag objects for the final catalogue, they do not reject PSF stars (mask-force telecon, 2026-07-21) — and MaxiMask is out too. The module docstring says so under its own heading, so that a reader who finds one path in the config knows it is a decision rather than an unfinished list, and knows widening it is a config edit and no code. That asymmetry also explains the contract: MASK_PATHS is a path list rather than a bit mask because the UNIONS products are one boolean map per bit, so choosing bits is choosing files. MASK_BITS survives for integer maps that pack several bits into one file, and its commented example drops from 1028 to 4 to match the diet. The setools header and the tutorial's Masks section carry the same note. Configs re-validated in the container (0 bad, all MODULE lists resolve) and the 10 mask tests still pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cem4A9vjxA7nkPnyBKrc5W
… docs Three follow-ups from review. CI. deploy-image.yml's runtime binary smoke still ran `weightwatcher --version` after the Dockerfile dropped the package, so the very next image build would have failed on a tool nothing calls. Step line and the comment's mention both go. SEGMENTATION, and this one was a real regression I introduced. Folding config_tile_Sx_nomask.ini into config_tile_Sx.ini carried the nomask variant's `CHECKIMAGE = BACKGROUND` over the masked config's `BACKGROUND, SEGMENTATION`. The segmentation check image is not masking furniture: vignetmaker cuts per-object segmentation stamps from it (config_tile_PiViVi_canfar_sx.ini FILE_PATTERN = sexcat, segmentation), and ngmix's uberseg blend handling raises at construction without them (ngmix.py ~496, shapepipe#776). Restored in both copies. The sims config already had it, which is why nothing else caught this. Docs. pipeline_canfar.md's "Collate star catalogues" section drove collate_star_cat.py, deleted with the star-catalogue tooling. The recipe is replaced with a warning that names what is missing rather than a silent cut: the merge step below it consumes validation_psf_conv files that now have no producer in this repo, and the star_cat/ + `combine_runs.bash -c psf_conv` staging around it does not apply either. The PSF measurement itself is unchanged — shapes are measured in sky coordinates during interpolation — so the collation is a pass-through that is straightforward to rebuild. No other docs page referenced a deleted script. Re-verified in the container: 30/30 module runners import, 65 configs' MODULE lists resolve (0 bad), the 10 mask tests pass, the workflow still parses and builds its DAG, and deploy-image.yml is valid YAML. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cem4A9vjxA7nkPnyBKrc5W
Five review items, all in the query path. PARTIAL READS. query_map did HealSparseMap.read(path) on a 583 MB map, per CCD — ~22 GB of I/O per 40-CCD exposure to answer questions about 0.06 deg². It now reads HealSparseCoverage first, computes the coverage pixels the positions touch with hpgeom.angle_to_pixel at the map's nside_coverage, and loads only those. make_cat gets this for free, sharing the primitive. Measured on the DR6 star map for 2000 positions in one CCD-sized box, one process each: partial 0.102 s / 157 MiB peak RSS, full 12.8 s / 3364 MiB, IDENTICAL values. 125x faster, 21x less memory. The full-read footprint is 3.3 GiB — under the 4 GB mark — and the partial read adds ~0.15 GB to a rule already asking for 16 GB, so exp_psf's mem_mb is left alone. Per exposure this is ~4 s of map reading instead of ~8.5 min. test_partial_read_matches_full pins partial == full for both map dtypes. Two things the reviewer's sketch did not anticipate, both found by probing the real map rather than reasoning. (a) healsparse RAISES when no requested pixel is in the coverage map, so the all-off-coverage case is answered without asking it: one small probe read for dtype and sentinel, then the sentinel everywhere. (b) The neighbours padding is insurance, not necessity — every position falls in exactly one coverage pixel and all are requested — but at nside_coverage=128 it costs under a megabyte, so it stays. BOOLEAN OFF-COVERAGE. n_off was hardcoded to 0 for boolean maps, so a map that misses an exposure logged "0 flagged, 0 outside coverage" — indistinguishable from clean. valid_mask=True cannot fix this: the UNIONS products are BOOLEAN, and healsparse stores only the True pixels, so valid_mask returns the value itself (verified against mask_r_nside131072_n4.hsp). Coverage now comes from the coverage mask, which works for both dtypes, and all-off-coverage logs a warning saying the zero column means "the map does not reach here", not "clean". Also: FLAG_EXT's docstring no longer claims the value says which bits fired — true only for integer maps, since boolean maps can only contribute 1. config_Rc.ini's mask INPUT_DIR placeholder becomes <path/to/tile_masks> rather than a $SP_CONFIG path that does not exist. A zero-detection CCD no longer raises on data["XWIN_WORLD"] — it writes an empty FLAG_EXT column, matching the sparse-CCD tolerance setools and the completeness floor already carry. hpgeom returns to pyproject.toml, now imported directly rather than via healsparse. pipeline_canfar.md's HSM sky-coordinates paragraph is lifted out of the collate_star_cat deletion warning into standing prose, pointing at the test that pins the convention. Verified: 15/15 mask tests pass, 30/30 runners import, 65 configs resolve, the workflow still builds its DAG, uv.lock regenerated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cem4A9vjxA7nkPnyBKrc5W
… masking Scope error in eb93838, mine to own: collate_star_cat.py was swept up with the GSC masking star catalogues on a name match. It is the PSF VALIDATION collation — per-exposure validation_psf files gathered into the validation_psf_conv catalogues config_Ms_psfex_conv.ini merges — and has nothing to do with mask generation. Restored byte-identical from origin/feat/snakemake-orchestration, with tests/module/test_collate_star_cat.py, and pipeline_canfar.md's "Collate star catalogues" section put back verbatim in place of the deletion warning ba2271a wrote (the HSM sky-coordinates paragraph returns with it, as the section's own prose, which is where it started). Two things asked for in the revert turn out not to be needed, checked rather than assumed: * focal_plane.py and utilities/file_io.py stay deleted. collate_star_cat.py imports nothing from shapepipe at all — only stdlib plus tqdm, joblib, numpy, astropy, galsim and cs_util. focal_plane_disc and write_atomic had exactly two callers between them, create_star_cat.py and star_cats.py, and both are masking and both stay deleted. * There is no scripts/README.rst entry to restore. That file listed create_star_cat (the masking one) and never listed collate_star_cat, so the edit in 1bceb2d was already correct. create_star_cat.py, star_cats.py and utilities/vizier.py remain deleted, as intended. Verified in the container: the 9 restored tests pass alongside the 15 mask ones (24 total), 30/30 runners import, 65 configs resolve, the workflow builds its DAG, and both restored files diff clean against origin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cem4A9vjxA7nkPnyBKrc5W
…name
Three from final review.
`mask_map.sentinel` replaces `mask_map._sentinel` in the probe path — same
value (verified for both int and bool maps on healsparse 1.12.2), public API.
The probe also indexed `np.flatnonzero(coverage.coverage_mask)[0]` unguarded,
so a map with no coverage at all died with a bare IndexError from inside the
utility. It now raises `ValueError("healsparse map <path> has empty coverage")`,
naming the file, with a test.
The shipped MASK_PATHS placeholder becomes the real DR6 product name,
`mask_r_nside131072_n4.hsp`, in all three configs. The products are staged at
/project/6001537/cdaley/masks/dr6/ (same tree as
/project/def-mjhudson/cdaley/masks/dr6/), which is where the partial-read
measurement in f7d1fd6 was taken — that docstring already named the file
correctly, so nothing else mentioned the old placeholder.
25 tests pass in the container (mask_query, make_cat_mask_ext,
collate_star_cat); 65 configs resolve.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cem4A9vjxA7nkPnyBKrc5W
…ra downstream Cail's call. random_cat computed a tile's effective unmasked area and drew randoms inside it by counting zero pixels in a tile mask IMAGE. That input was the deleted mask module's output, and with the query design there is no such image and no plausible producer for one: the survey window is map algebra on the healsparse coverage map (#797), done downstream, not a per-tile pipeline step that rasterizes to pixels first. Rather than keep a module wired to a placeholder path nobody can fill — which is what f7d1fd6 left, an honest but unpaid IOU — the module goes: the package, the runner, config_Rc.ini, docs/source/random_cat.md and its toc.rst entry. There were no tests to remove. The module was already unsound independently of masking, which is part of why keeping it had no value: `save_as_healpix` is called as `_save_as_healpix`, and `process` references `file_name` and `output_dir` which are never bound. Any run past the first few lines would have raised. `reproject` leaves pyproject.toml with it — random_cat.py held its only import (the two ngmix hits for "reprojection" are prose in comments). uv.lock regenerated: reproject, plus pims, pyavm, slicerator, toolz and zarr that came in behind it. Verified in the container: 29 runners import (was 30), 64 configs resolve (was 65, 0 bad), the 25 mask/collate tests pass, the workflow still builds its DAG, and random_cat / N_RANDOM / RandomCat / config_Rc / run_sp_Rc appear nowhere outside scripts/sh/, which stays untouched as agreed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cem4A9vjxA7nkPnyBKrc5W
… placeholder at the real ladder The Aug-2026 post-GSC2 band-combined healsparse products (arc:home/mhudson/masks/) use the mask_ugriz_nside131072_n<bit>.hsp naming; the mask_r_* files on canfar's ShapePipe/mask are the 2025 vintage. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cem4A9vjxA7nkPnyBKrc5W
…hing by default The PSF star selection no longer cuts on the external masks. mask_query stays in the chain and keeps writing FLAG_EXT from the shipped MASK_PATHS (the star-body map, bit 2); star_selection.setools drops `FLAG_EXT == 0` from all four mask blocks and rejects on the instrument flags alone, leaning on outlier rejection for the rest. This makes the exposure side agree with the tile side, which was already permissive: make_cat writes MASK_<band> unfiltered. Flag transparently, cut downstream — so the effect of a mask on the star sample can be MEASURED before it is imposed, rather than baked in on the way past. The escape hatch is documented where someone would look for it, because a permissive default is only safe if reversing it is obvious. star_selection.setools' header now spells out the change (add `FLAG_EXT == 0` beside each `IMAFLAGS_ISO == 0`, one line per block) and says why it exists: if outlier rejection turns out not to be robust enough. The module docstring gains a "Nothing cuts on it by default" section, and the config comments beside MASK_PATHS say NOTHING CUTS ON IT rather than naming a cut that is no longer there. The tutorial's Masks section, pipeline_canfar.md and workflow/README.md lose the same stale claim; the shared utility now says setools *could* cut on the column, not that it does. The diet section is retitled: MASK_PATHS is a list of maps to RECORD against each candidate, not to reject on. Halos stay out for the same reason as before — they say nothing about whether a star is a good PSF sample. Verified: 25 tests pass, 29 runners import, 64 configs resolve, ruff clean. Only the four cut lines changed in the setools body — the mask blocks and their IMAFLAGS_ISO tests are otherwise untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cem4A9vjxA7nkPnyBKrc5W
…ASK_EXT Two changes, both about making the naming and the default say what the design means. MASK_EXT, not FLAG_EXT. The column is one family with make_cat's MASK_<band>: an external mask, queried and stored per object. "FLAG" belonged to the other kind of mask entirely, which is the distinction the docstrings now lead with — instrument flags mark a CORRUPTED MEASUREMENT (bad columns, saturation) and are the only masks that reject anything in-pipeline, via IMAFLAGS_ISO in setools and zero-weighted pixels and dropped epochs in ngmix; healsparse masks are sky-fixed LOCATION flags that say where an object sits, not that its pixels are broken, so they are queried to columns and every rejection is downstream. Renamed in code, tests, docstrings, the setools header, the tutorial and both READMEs. MASK_PATHS ships commented out, and absence is now a strict no-op rather than a ValueError: the runner treats a missing key the way make_cat treats a missing MASK_EXT_PATHS, and MaskQuery with no maps copies the catalogue through byte-for-byte with NO MASK_EXT column. The copy is the load-bearing part — setools reads this module's output, so a no-op that wrote no file would break the chain instead of disabling the query. mask_query stays in the MODULE chain either way, so turning the query on is uncommenting one line and never editing the chain. Two tests cover it, one asserting the output is byte-identical to the input. The framing that ties both: the PSF star selection deliberately starts from outlier rejection alone, and MASK_EXT is the configurable pickup if that proves insufficient — write the column, measure the effect, impose it only if the measurement says to. Also corrected in passing: edec546 renamed the measured file in the partial-read note along with the config placeholders, which credited the measurement to a file I did not run against. The note now says what was actually measured — the staged single-band copy mask_r_nside131072_n4.hsp — and names the mask_ugriz ladder it belongs to. Verified: 27 tests pass, 29 runners import, 64 configs resolve (MASK_PATHS absent, section and chain entry present in all three), ruff clean on the touched files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cem4A9vjxA7nkPnyBKrc5W
Question moot now, |
Closes #846. Closes #837. Part of the #845 epic; builds on #852 and merges after it.
Until now, ShapePipe made its own masks: the
maskmodule rasterized GSC star positions, Messier/NGC regions, and the instrument flags into a per-image flag file, and the star selection cut on that image. The mask force's external masks never entered the pipeline.This PR removes all mask generation from ShapePipe. Two kinds of mask input remain, ingested differently.
IMAFLAGS_ISO; ngmix sees them in its stamp weights.make_catwrites every bit, per band, intoMASK_<band>, unfiltered. All selection on these columns happens downstream.PSF star selection — the one decision that cannot be redone later — cuts only on the instrument flags: they mark corrupted measurements, stars whose own photometry and shape are unreliable. For everything the sky masks describe, the pipeline deliberately starts by relying on PSFEx's outlier rejection alone, and the first runs measure how well that holds. If it proves insufficient, there is machinery to pass any of the external masks — for example the star mask — to the PSF star selection, switched on in config.
Shape measurement uses no external mask: ngmix zero-weights instrument-flagged pixels and drops heavily flagged epochs, exactly as before. Whether an object in a halo, a trail, or a masked star enters the science sample is decided downstream from the
MASK_<band>columns.The survey footprint is a separate, positive coverage map — the exposure map of CCDs with a PSF model (#797), or the pointing-coverage map — and the mask bits subtract from it to give the final footprint.
The mask products
Sky-fixed boolean healsparse maps, one per bit, nside 131072 ≈ 1.6″; current products
mask_ugriz_nside131072_n<bit>.hsp(2026-08-05, post-GSC2-fix; locations on the wiki):Paths and bit choices live only in config, so regenerated products cost no code.
What changed in the code
The
maskmodule is deleted, together with everything that existed only to feed it: the GSC star-catalogue machinery, the WeightWatcher configs, and the tile-level masking variants.random_catis deleted too — randoms and unmasked area are map algebra on the coverage map downstream, which is what closes #837. Exposure SExtractor now reads the instrument flag file straight fromsplit_exp, and tiles are detected without any flag image. In the Snakemake workflow the exposure chain shrinks to get-images → split → psf: the star-catalogue staging, its config keys, and the mask rules are removed. The old CANFAR bash drivers still name the deleted configs; they are superseded by the workflow and left for a separate cleanup.The first run of this branch refreshes the #850 old-vs-new comparison against the 2026 products.
— Claude on behalf of Cail