Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
9fcf3f1
feat(cfis): wire the UNIONS external masks into the workflow
cailmdaley Sep 9, 2026
f5c76a4
feat(workflow): emit the instrument flags as a healsparse defect map …
cailmdaley Sep 10, 2026
6783f4d
docs(workflow): the defect map in the README, and how to join it to t…
cailmdaley Sep 10, 2026
958b562
fix(defect-map): a short split is an error, a bad chip is not an OOM
cailmdaley Sep 10, 2026
a9daa4f
fix(defect-map): a reclaimed exposure is a leaf, and the merge asks f…
cailmdaley Sep 10, 2026
1f6e832
docs(defect-map): the measurements, and three per exposure rather tha…
cailmdaley Sep 10, 2026
db4f426
fix(defect-map): reclamation never waits on a rasterization it cannot…
cailmdaley Sep 10, 2026
f760687
perf(defect-map): the merge allocates its accumulator once
cailmdaley Sep 10, 2026
45a082f
feat(cfis): wire the UNIONS external masks into the workflow
cailmdaley Sep 9, 2026
4454d07
fix(file_io): recognize numpy bool arrays when picking the FITS colum…
cailmdaley Sep 10, 2026
f77959d
merge: feat/wire-external-masks-v2 (#886 on #879/#894) into feat/inst…
cailmdaley Sep 26, 2026
8d38292
workflow: no defect map for image simulations (MAPS_DEFECTS)
cailmdaley Sep 26, 2026
168d7fc
feat(cfis): wire the UNIONS external masks into the workflow
cailmdaley Sep 9, 2026
aee2ee9
fix(file_io): recognize numpy bool arrays when picking the FITS colum…
cailmdaley Sep 10, 2026
3bf2e12
Merge branch 'feat/wire-external-masks-v2' into feat/instrument-defec…
cailmdaley Sep 26, 2026
41be84a
docs(astra): the instrument flags become a conservative healsparse de…
cailmdaley Sep 26, 2026
bc09a5a
test(defect-map): the fragment contains every flagged pixel
cailmdaley Sep 26, 2026
07038fd
feat(cfis): wire the UNIONS external masks into the workflow
cailmdaley Sep 9, 2026
c1fbe7c
fix(file_io): recognize numpy bool arrays when picking the FITS colum…
cailmdaley Sep 10, 2026
f84065d
docs(astra): the shear catalogue's default MASK_n* cut is the six r-m…
cailmdaley Sep 26, 2026
03fd684
test(masks): final_cat.param's MASK_n* names match MASK_EXT_PATHS; th…
cailmdaley Sep 26, 2026
d2b4de8
Merge feat/wire-external-masks-v2 (final) into feat/instrument-defect…
cailmdaley Sep 26, 2026
e3d7d4f
fix(defect-map): a changed fragment changes its manifest and the merg…
cailmdaley Sep 26, 2026
b9882a2
fix(defect-map): a resolution change rebuilds instead of passing as a…
cailmdaley Sep 26, 2026
f524957
fix(defect-map): bind map and sidecar with a generation id
cailmdaley Sep 26, 2026
53d4b09
test(defect-map): drive the reconcile tests through main()
cailmdaley Sep 26, 2026
0e4b4f5
Merge feat/wire-external-masks-v2 (on the reviewed #879) into feat/in…
cailmdaley Sep 26, 2026
eec9b03
test(workflow): the DAG harness knows the defect rules; the reconcile…
cailmdaley Sep 26, 2026
5f7332d
Merge branch 'feat/wire-external-masks-v2' into feat/instrument-defec…
cailmdaley Sep 26, 2026
2d8f925
Merge feat/wire-external-masks (develop merged in) into feat/instrume…
cailmdaley Sep 28, 2026
be528c8
Write the defect map under <products_dir>/defect_map/
cailmdaley Sep 28, 2026
2b59d35
Coverage geometry becomes a library; retire the v1.x header-scrape chain
cailmdaley Sep 28, 2026
47b7e45
feat(workflow): the exposure-count map, beside the defect map
cailmdaley Sep 28, 2026
c29fd12
feat(workflow): the exposure-count map is on for every fitted-PSF run
cailmdaley Sep 29, 2026
e8ce5fe
fix(workflow): no exposure-count map job without a footprint record
cailmdaley Sep 29, 2026
60d9d67
workflow: refuse retired coverage:/defect_map: config keys at parse time
cailmdaley Sep 29, 2026
2e12ccd
tests: key the retired-key ordering check on either defaults call
cailmdaley Sep 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions astra.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,9 @@ analyses:
- id: sky_masks
type: data
source: UNIONS healsparse mask maps
- id: exposure_psf_ccds
type: data
source: per-exposure split WCS (headers-<exp>.npy) and the persisted validation_psf members (exp_persist)
outputs:
- id: masked_measurement_inputs
type: data
Expand All @@ -225,7 +228,97 @@ analyses:
catalogue's MASK_n<bit> columns.
inputs: [exposure_flags, sky_masks]
decisions: [pixel_mask_source, psf_star_mask_veto, sky_mask_application, mask_default_cut]
- id: defect_map
type: data
format: hsp
description: >-
The campaign's instrument-flag defect map: a boolean healsparse
map, True = masked, the union of one fragment per exposure. A
product; nothing in the workflow reads it. Data runs only.
inputs: [exposure_flags]
decisions: [defect_map_from_flags]
- id: nexp_map
type: data
format: hsp
description: >-
The campaign's exposure-count map: a uint16 healsparse map at the
defect map's resolution holding, per pixel, the number of exposures
with a valid PSF model covering it. The count sp_validation's
npoint cut reads. A product; nothing in the workflow reads it.
Built for every fitted-PSF campaign; image simulations on the true
PSF record no footprints and skip it.
inputs: [exposure_psf_ccds]
decisions: [nexp_map_valid_psf_ccds]
decisions:
nexp_map_valid_psf_ccds:
label: The exposure-count map stamps only CCDs with a valid PSF model
rationale: >-
exp_footprint records the four sky corners of each CCD whose
validation_psf catalogue exp_persist packed; psfex_interp writes that
catalogue on success and returns without it on NOT_ENOUGH_STARS,
BAD_CHI2 or FILE_NOT_FOUND, so the member list is the valid-PSF set.
Corners come from the split's WCS at pixel edges. nexp_map stamps
value 1 per CCD polygon into a uint16 map; the CCDs of one exposure
do not overlap, so a pixel's value is the number of exposures that
can contribute a PSF-corrected epoch there. It is built from every
record on the products root, reclaimed exposures included, and
written raw (no median smoothing). There is one count: of exposures,
not of pointings. The resolution is the mask ladder's, shared with
the defect map.
Values:
config.yaml#exposure_maps.nexp.enabled = true.
default: valid_psf_ccds
options:
valid_psf_ccds:
label: Count exposures whose CCD has a valid PSF model
all_ccds:
label: Count every exposure whose CCD covers the pixel
excluded: true
excluded_reason: >-
Counts epochs that cannot contribute a shape: a CCD without a
PSF model yields no PSF-corrected measurement, so the count
overstates depth exactly where the PSF fit failed.
defect_map_from_flags:
label: Instrument flags rasterized conservatively into a healsparse defect map
rationale: >-
The per-CCD flag images (bad columns, saturated
pixels, bleed trails; the exposure_flags input) otherwise never leave the
pixel domain, and the footprint built from CCD-corner WCS cannot
subtract them. exp_defect_map samples every nonzero-flag CCD pixel
on an oversample x oversample grid spanning its full extent,
corners included (exposure_maps.defect.oversample 3 in workflow/config.yaml),
maps the samples through the CCD's image-split WCS, and masks every
healpix pixel they touch, at the mask ladder's convention (nside
131072 over coverage 128, True = masked). Any flag bit counts.
defect_map_merge ORs the campaign's fragments into
defect_map/defect_map_<campaign>.hsp, with a sidecar recording which exposures
are in; a removed or changed fragment forces a rebuild, since a
union cannot be un-OR-ed. On 2079612p CCD 0, oversample 2, 3 and 5
miss 169, 54 and 5 of 14229 reference healpix pixels. The map is a
product no workflow stage consumes (config_tile_Mc.ini's
MASK_EXT_PATHS does not name it); image simulations get none, their
flag images being all zero.
Values:
config.yaml#exposure_maps.nside = 131072;
config.yaml#exposure_maps.nside_coverage = 128;
config.yaml#exposure_maps.defect.oversample = 3.
default: rasterize_conservative_os3
options:
rasterize_conservative_os3:
label: "Any-touch rasterization, 3x3 samples per CCD pixel"
centre_sampling:
label: Mask a healpix pixel only where its centre is flagged
excluded: true
excluded_reason: >-
Erases one-pixel bad columns, the thin geometry #878 exists to
keep.
none_footprint_from_wcs:
label: No defect map; footprint from CCD-corner WCS alone
excluded: true
excluded_reason: >-
The footprint silently includes defective pixels: percent-level
area, in exactly the thin small-scale geometry a window function
needs.
pixel_mask_source:
label: Only the instrument flag image reaches pixels
rationale: >-
Expand Down
33 changes: 33 additions & 0 deletions docs/source/exposure_maps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Exposure-level maps

A workflow campaign builds two HealSparse maps from its exposures, beside the
merged catalogues on the products root. Both are per-exposure records combined
by one campaign job, at the mask ladder's resolution (`nside` 131072 over
`nside_coverage` 128, set once under `exposure_maps:` in
`workflow/config.yaml`), and both are produced automatically whenever the input
can support them — no configuration is needed.

| map | chain | product | built for |
| --- | --- | --- | --- |
| defect | `exp_split` → `exp_defect_map` → `defect_map_merge` | `defect_map/defect_map_<run>.hsp` | data runs |
| exposure count | `exp_persist` → `exp_footprint` → `nexp_map` | `nexp_map/nexp_map_<run>.hsp` | fitted-PSF runs |

**The defect map** is boolean, `True` = masked: every healpix pixel touched by
a flagged CCD pixel (bad columns, saturated pixels, bleed trails) of any
exposure. It carries the instrument flags, which otherwise never leave the pixel
domain, into the same form as the sky masks. Image simulations' flag images are
blank, so sims build none.

**The exposure-count map** counts, per pixel, the exposures with a valid PSF
model covering it — the count behind sp_validation's `npoint >= 3` cut.
`exp_footprint` records the sky corners of each CCD whose PSF fit succeeded;
`nexp_map` stamps every record on the products root into the map, so it grows as
tiles are appended. `psf_model: fake` fits no PSF, so sims build none.
`exposure_maps.nexp.enabled: false` opts a campaign out; the map is rebuilt
whole, so a campaign appended in many small batches may prefer to build it once
at the end.

Nothing in the workflow reads either map back. Plot the count map by hand with
`plot_coverage_map -i <products_dir>/nexp_map/nexp_map_<run>.hsp ...`, using the
sky windows under `exposure_maps.nexp.plot`. The design and the measurements
are in `workflow/README.md`.
1 change: 1 addition & 0 deletions docs/source/toc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
configuration
testing
workflow
exposure_maps
pipeline_tutorial

.. toctree::
Expand Down
3 changes: 0 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,6 @@ canfar_submit_job = "shapepipe.canfar_run:run_job"
canfar_monitor = "shapepipe.canfar_run:run_log"
canfar_monitor_log = "shapepipe.canfar_run:run_monitor_log"
get_ccds_with_psf = "shapepipe.get_ccds_run:run_ccd_psf_handler"
download_headers = "shapepipe.coverage_run:run_download_headers"
extract_field_corners = "shapepipe.coverage_run:run_extract_corners"
build_coverage_map = "shapepipe.coverage_run:run_build_coverage"
plot_coverage_map = "shapepipe.coverage_run:run_plot_coverage"

[tool.uv]
Expand Down
95 changes: 0 additions & 95 deletions scripts/sh/build_and_plot_coverage_maps.sh

This file was deleted.

83 changes: 7 additions & 76 deletions src/shapepipe/coverage_run.py
Original file line number Diff line number Diff line change
@@ -1,79 +1,20 @@
"""COVERAGE_RUN

Call coverage processing classes.
Console entry point for plotting a coverage map.

Building the exposure-count map is the Snakemake ``nexp_map`` rule's job;
plotting a finished ``.hsp`` is a human act on a durable product, so it stays a
hand-run command, on the same argument that keeps ``run_report.py`` out of the
DAG. The plot windows for the UNIONS SGC and NGC fields are in
``workflow/config.yaml``'s ``exposure_maps.nexp.plot`` block.

Author: Martin Kilbinger <martin.kilbinger@cea.fr>

"""

import sys

from shapepipe.utilities.header_downloader import HeaderDownloader
from shapepipe.utilities.field_corners_extractor import FieldCornersExtractor
from shapepipe.utilities.coverage_map_builder import CoverageMapBuilder
from shapepipe.utilities.coverage_plotter import CoveragePlotter


def run_download_headers(args=None):
"""Run Download Headers.

Download FITS headers from VOSpace for exposures in a CCD list.

Parameters
----------
args : list, optional
command line arguments

Returns
-------
int
exit code

"""
obj = HeaderDownloader()
return obj.run(args=args)


def run_extract_corners(args=None):
"""Run Extract Corners.

Extract per-CCD sky-footprint corner coordinates from FITS headers.

Parameters
----------
args : list, optional
command line arguments

Returns
-------
int
exit code

"""
obj = FieldCornersExtractor()
return obj.run(args=args)


def run_build_coverage(args=None):
"""Run Build Coverage.

Build HealSparse coverage maps from per-CCD corner coordinates.

Parameters
----------
args : list, optional
command line arguments

Returns
-------
int
exit code

"""
obj = CoverageMapBuilder()
return obj.run(args=args)


def run_plot_coverage(args=None):
"""Run Plot Coverage.

Expand All @@ -92,13 +33,3 @@ def run_plot_coverage(args=None):
"""
obj = CoveragePlotter()
return obj.run(args=args)


def main(argv=None):
"""Main.

Main program.

"""
# Scripts to call coverage classes are created by pyproject.toml
return 0
Loading
Loading