Skip to content

Write the per-tile final_cat as HDF5, one dataset per column - #942

Merged
cailmdaley merged 10 commits into
developfrom
feat/final-cat-hdf5
Oct 10, 2026
Merged

cailmdaley merged 10 commits into
developfrom
feat/final-cat-hdf5

Conversation

@cailmdaley

@cailmdaley cailmdaley commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Closes #941. Builds on #939 and #937 (both merged).

Change

  • make_cat writes the per-tile catalogue as HDF5, in one pass. The runner collects every stage's columns (SExtractor, TILE_ID/TILE_UNIQUE_ID, ngmix, per-epoch PSF slots, MASK_*) in one ordered dict and writes final_cat<num>.hdf5. Each column is one lzf dataset; vector columns are 2-D. Columns keep stage order and dtype (byte order normalised to native). The file has no attributes, because the FITS file carried nothing beyond its column cards and EXTNAME.
  • WORK_DIR is removed (runner and config_tile_Mc.ini). With a single write there is nothing to stage: a real 9,226 × 174 tile writes in 0.17 s to NFS on candide.
  • Readers. create_final_cat.read_data reads per-column HDF5 into the structured array the merge already writes, and still reads FITS. find_final_cat prefers .hdf5 over .fits in both layouts. FITS support stays for runs already on disk and for sp_validation's image-sims im_merge rule. merge_final_cat reads final_cat-<ID>.hdf5 and drops its unused --hdu.
  • Workflow. final_cat(), tile_make_cat and astra.yaml declare final_cat-<tile>.hdf5.

The merged final_cat_<run>.hdf5 layout does not change (one compound dataset per tile).

Campaign boundary

The declared per-tile output and the tile_make_cat params pin both change. A resumed campaign finds no final_cat-<tile>.hdf5 and re-declares every tile, so start a fresh root.

Schema note

The FITS writer promoted every float to float64. HDF5 keeps SExtractor's float32, so in SExtractor-mode (image-sims) campaigns 12 merged columns become float32 with identical values (MAG_*, FLUX_*, FLUXERR_*, MAGERR_*, FLUX_RADIUS, SNR_WIN, FWHM_*). A merged row shrinks from 530 to 482 bytes. Data campaigns keep their dtypes: DR6 detection columns are already f8/i4/i8 (checked on a DR6 sexcat).

Verification

  • Real tile (image-sims 1z2z_grid_3, 273.282): make_cat as FITS (make_cat: write the final catalogue once per save stage, node-local #939) and as HDF5 (this branch) gives the same 174 columns in the same order with equal values (NaN-aware); only the float32 dtypes differ. Merging each through read_data + copy_data gives identical rows.
  • Tests (dev SIF shapepipe_develop-dev-20260926, candide): pytest tests -m "not slow" gives 861 passed, 1 skipped, including new tests for the per-column lzf writer, merge invariants on make_cat's writer, and read_data HDF5/FITS parity.
  • uvx astra-tools@0.2.17 validate passes.

Not changed

The Gen-2 helpers scripts/python/merge_final_cat.py, get_number_objects.py, combine_runs.bash, post_proc_sp.bash and example/unions_800/config_tile_match_ext_r.ini still expect FITS. sp_validation reads only the merged file and needs no change.

Claude Opus 5.5 on behalf of Cail

🤖 Generated with Claude Code

https://claude.ai/code/session_01JkM3cmXPuuSrNAf7vJde9e

cailmdaley and others added 3 commits October 2, 2026 12:25
make_cat collects every stage's columns (SExtractor, TILE_ID and
TILE_UNIQUE_ID, ngmix, per-epoch PSF slots, MASK_*) in one ordered dict
and writes final_cat<num>.hdf5 at the end: one lzf-compressed dataset per
column, vector columns as 2-D datasets, native dtypes and byte order.

With a single write, building on node-local disk has nothing left to
buy (the write of a 9,226 x 174 tile takes 0.17 s on candide's NFS), so
WORK_DIR and its stale-file handling go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JkM3cmXPuuSrNAf7vJde9e
create_final_cat.read_data reads a per-tile HDF5 catalogue (one dataset
per column) into the same structured array the merge already writes,
and still reads FITS catalogues made before the format change, so hand
merges over existing runs keep working. find_final_cat looks for .hdf5
before .fits in both layouts. merge_final_cat reads
tiles/<prefix>/<ID>/final_cat-<ID>.hdf5 and drops the unused --hdu.

The merge invariants now write their tile catalogues with make_cat's own
writer, cover a vector column, and check that read_data gives one result
from the HDF5 and FITS forms of a catalogue.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JkM3cmXPuuSrNAf7vJde9e
tile_make_cat publishes final_cat-<tile>.hdf5 and final_cat_merge reads
it. The rendered tile_make_cat shell changes, so the params pin moves:
this is a campaign boundary. astra.yaml records the format.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JkM3cmXPuuSrNAf7vJde9e
@cailmdaley

Copy link
Copy Markdown
Contributor Author

shouldn't we also update these helper scripts to accept other fits and hdf5?

cailmdaley and others added 4 commits October 5, 2026 14:32
merge_final_cat.py: drop both --tile-detection (removed on develop, #933)
and --hdu (removed here). test_read_ext_sexcat.py: deleted, as on develop
(the converter it tested is gone). params_pin.json regenerated; versus
develop only tile_make_cat changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012yfkBvkzPv33iJxXZ6jAxd
shapepipe.utilities.final_cat.read_final_cat reads a per-tile final
catalogue into a native-byte-order structured array, detecting the format
per file from the HDF5 signature, and raises KeyError naming every
missing requested column. Unit-tested on one catalogue written both ways.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfF1SjWsjHQzZYjL8bbnLo
read_data delegates format detection and the missing-column raise to the
shared reader instead of branching on the .fits suffix; the workflow's
merge_final_cat.py inherits it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfF1SjWsjHQzZYjL8bbnLo
Collect final_cat files ending in .hdf5 or .fits and read each through
read_final_cat; --hdu_num applies to FITS only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfF1SjWsjHQzZYjL8bbnLo
@cailmdaley

Copy link
Copy Markdown
Contributor Author

Yes. This is done in this PR: 1139435, 249b3c2 and 8dc0a76.

One reader for both formats. The new module is shapepipe.utilities.final_cat.read_final_cat.

  • It decides HDF5 or FITS per file from the content (the HDF5 signature), not from the filename.
  • Either format comes back as the same structured array in native byte order, with vector columns as sub-array fields.
  • A missing requested column raises a KeyError that names the file and the column.

Helpers that now read through it.

  • scripts/python/create_final_cat.py, and the workflow's merge_final_cat.py through it.
  • The legacy scripts/python/merge_final_cat.py (used by post_proc_sp.bash). Before this change it only collected *fits, so on a directory of HDF5 tiles it found zero files and then failed with an IndexError. A directory that mixes FITS and HDF5 tiles now merges in one pass.

Helpers that needed no change.

  • These never open a final catalogue: combine_runs.bash (it symlinks final_cat-*, so either extension works), clean_tile.py, run_report.py and hdf5_reconcile.py.
  • These read sexcats rather than final catalogues: get_number_objects.py and ngmix_range.py.
  • The workflow's merge still finds tiles only through final_cat-<tile>.hdf5, on purpose: that path is the Snakefile's tile-done marker.
  • Out of scope: example/unions_800/config_tile_match_ext_r.ini still points match_external_runner at FITS final catalogues.

Tests. I ran these in the develop SIF on nibi.

  • tests/unit/test_final_cat_reader.py is new. It writes one catalogue in both formats, with scalar, vector, int64 and bool columns, and checks four things: every column reads identically, a column subset comes back in the requested order, a file whose extension names the wrong format still reads, and a missing column raises for each format.
  • That file plus test_final_cat_merge_invariants.py and test_decisions.py: 70 passed.
  • The import tests: 113 passed.
  • A hand run of the legacy merge on one HDF5 tile and one FITS tile gave 10 rows.

— Claude (Opus, nibi chair) on behalf of Cail

# Conflicts:
#	tests/module/test_make_cat_mask_ext.py
#	tests/workflow/params_pin.json
cailmdaley and others added 2 commits October 10, 2026 10:23
Regenerate the workflow parameter pin in the develop SIF with host Snakemake 9.27.0.

Co-Authored-By: GPT-6.1 Sol via Claude Opus 5.5 <noreply@anthropic.com>
@cailmdaley
cailmdaley merged commit 1ebef34 into develop Oct 10, 2026
3 checks passed
cailmdaley added a commit that referenced this pull request Oct 12, 2026
… pin

# Conflicts:
#	tests/workflow/params_pin.json
cailmdaley added a commit that referenced this pull request Oct 12, 2026
…seg-vignet; regenerate params pins

# Conflicts:
#	src/shapepipe/modules/make_cat_package/make_cat.py
#	tests/workflow/params_pin.json
cailmdaley added a commit that referenced this pull request Oct 12, 2026
…d_sextractor_data

(cherry picked from commit 5b0b357)
(cherry picked from commit de2b00f)
cailmdaley added a commit that referenced this pull request Oct 12, 2026
…ts-persist; final_cat as HDF5

# Conflicts:
#	workflow/rules/tile.smk
cailmdaley added a commit that referenced this pull request Oct 12, 2026
…rams pin

# Conflicts:
#	docs/source/pipeline_canfar.md
#	scripts/python/merge_final_cat.py
#	workflow/README.md
cailmdaley added a commit that referenced this pull request Oct 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Write the per-tile final_cat as HDF5, matching the campaign merge

1 participant