Changelog¶
All notable changes to torchfits are documented here.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased¶
1.0.0rc4 — 2026-07-20¶
Fourth release candidate for collaborator soak after prep / deep-review cleanup.
Removed¶
- Root aliases
read_table,stream_table,read_table_rows,get_header,get_batch_info— usetable.read_torch/table.scan_torch/read_header/read_batch_info. - Spectral and continuum transforms (
spectral.py,continuum.py) — torchfits keeps FITS I/O–adjacent viz/ML preprocess only; spectroscopy analysis moves out of this package. - Dead
core.py/ChecksumVerifier— checksums go through_Cviachecksum_api. - CLI deprecated aliases
--fitsort,--bytes,--preview— use--keyword-table,--header-bytes,-n/--rows. - Deprecated
table_module=dual-path on cache invalidate/clear. table.to_polars_lazy— usescan_polarsorto_polars(...).lazy().torchfits.cli.rgbshim — importlupton_rgb/write_rgb_imagefromtorchfits.transforms.- No-op
handle_cache=onread_tensorandhandle_cache_capacity=onread_subset(persistent reuse stays onopen_subset_reader).
Added¶
- Skinny metadata:
read_nrows,read_keys,read_shape,read_hdu_type,read_num_hdus,read_colnames,read_extname,read_table_info— CFITSIO structural/key queries without a full header dump. open_table_reader(path, hdu=1)— reusable table handle (mirror ofopen_subset_reader).table.read_torch(..., where=)— fused C++ project+predicate path.FITSHeaderScale.from_path/FITSHeaderNormalize.from_pathvia skinny keys.transforms.as_module/AsModule— thinnn.Moduleadapter fornn.Sequential.- CLI
transform --name Class:key=val,...constructor kwargs. - HTTP Range cutouts + vos/vault remote fetch (prior unreleased work).
- Public
TensorHDU.shape_str/dtype_str; optionalFitsTableDataset(labels=).
Changed¶
read()rejects unknown kwargs withTypeError(no silent swallow of leftovers likepolicy=).- Table
read_torchuses a thin C++ path (skipsread_unifiedimage probes). - Datasets
label_key,get_image_meta, benches/examples lean on skinny meta. - Lazy root
__getattr__uses a lock-backed attribute cache (noglobals()mutation). - Library logger uses
NullHandler(no import-time StreamHandler). - Bench deficit CSV always lists raw lags;
significanceisnoiseorsignificant(floors label only). - Removed disconnected
benchmarks/bench_fast.pyand pixi aliasesbench-fast/bench-fast-stable/bench-core(usebench-fits). - Dead private
read_large_tableleftover and unused_unsigned.py(unsigned paths live in_read_pipeline/write_api/fits_schema). - Table mutation: single
_mutation_cache_barrierpre/post; dtype maps via_ensure_dtype_maps(). FitsTableDataset.__getitem__returns(row_dict, label)formake_loader/fits_collate_fnparity with image datasets.- Lupton examples/docs use
lupton_rgb(r=..., g=..., b=...)(reddest → R). - Example smoke runner auto-discovers
examples/*.py(skips_*.pyhelpers). - DataView BITPIX 64 →
torch.int64; C++write_imagesupports int8/int64.
Fixed¶
- HTTP Range cutouts: NumPy view
byteswap(True)on frombuffer tensor (this torch build has noTensor.byteswap); drop redundant cutout.clone(). - SigmaClip: 0-d
new_zeros(())fill fortorch.where(..., out=)(nozeros_likebuffer). - Filtered table zero-match
where=: keyed empty tensors (not{}). lupton_rgb: Astropy-parity Lupton asinh mapping (per-pixel peak clip). Gallery SDSS / MegaPipe figures regenerated with readable stretch.SubsetReader: uncompressed 2D images mmap the data segment once and slice+bswap into torch (MegaPipe-class mosaics); CFITSIOfits_read_subsetremains the fallback for compressed / scaled / non-2D.- Clearer
TypeErrorwhen inferring FITS TFORM from uint16/uint32/uint64. LogStretch.inverseclamps exponents to avoid float overflow.- Remote prefetch bookkeeping cleaned after completed downloads (locks retained).
- WHERE
BETWEENboundaries no longer use bare\S+(operators/parens). fast_parse_header_cards/Headerkeep empty comments as""(not"None").- Wheel builds pin torch 2.10 ABI (
--no-build-isolation+ before-build install) so release smoke no longer fails against a 2.13-built extension. - Deep-review P0–P4 harden (WHERE OOM gate, batch exception narrowing, HDU close race, prefetch errors, mutation cache barrier, NAXIS overflow guard).
Docs¶
- Removed-names table lists root
read_table/stream_table/read_table_rows/get_header/get_batch_info. - Core I/O cache section documents root vs
torchfits.cachelayers. - Examples:
open_table_reader+ EXTNAMEtable.read_torch. - Round-3 scorecard (post thin-I/O): MPS
exhaustive_mps_20260719_143706, CANFAR CPUexhaustive_cpu_20260719_144337, CUDAexhaustive_cuda_20260719_144457; MegaCam20260719_075555; MLml_20260719_145743. - Slim transform gallery; real Lupton RGB figure; removed spectral/continuum docs.
- Core I/O docs point at
table.read_torch/table.scan_torch(root aliases gone). - User Guide ML with FITS: Galaxy Zoo 1 + Legacy Survey one-epoch CNN train; MegaPipe mosaic collage + cutout timing.
- Canonical
TORCHFITS_*env tables in architecture; slimmed duplicates elsewhere. release-gaterunsdocs-contract(example sync + zensical build) anddocs-links(internal hyperlink crawl ofsite/).
Examples¶
example_ml_galaxyzoo_legacy.py,example_megapipe_cutout_collage.py,scripts/fetch_cfht_megapipe_sample.sh.
1.0.0rc3 — 2026-07-18¶
Third release candidate for collaborator soak.
Docs¶
- Persona pass on user-facing pages: rc honesty, corrected migration threading
(private CFITSIO handles since rc2), API notes for EXTNAME / 3D
read_subset, cache vs disk-cache /make_loaderlayering. - Examples gallery: MaNGA LOGCUBE (
example_manga_logcube.py), Lupton RGB from real SDSS g/r/i (example_lupton_rgb_sdss.py, stdlibbz2inflate before read), CFHT MegaCam MEF cutouts (example_megacam_mef_cutouts.py). - Fill API / cache / loader doc gaps:
TORCHFITS_CACHE_DIRvs in-process handle cache, whenoptimize_cacheno-ops on table datasets,make_loadervs plainDataLoader.
Agent / Jules¶
AGENTS.md+JULES.md: weekly Jules retarget to bug/perf-only deep passes; ledger in.cursor/jules-ledger.md; out-of-scope cosmetic PRs.
Fixed¶
- String HDU / EXTNAME:
read_tensorandread_subsetaccepthdu="EXTNAME"(e.g.hdu="MYDATA").hdu="auto"still raises a clearValueError. - 3D subset:
read_subset/open_subset_readerpreserve the leading cube axis; window applies to trailing(y, x)only. - Zero-size cutout box: a degenerate width or height keeps the other axis length (no longer collapses both dims to 0).
- Table
where=+ TNULL: filtered reads honorapply_fits_nulls=Trueso sentinel nulls do not leak as real values. - Remote prefetch race:
resolve_local_pathwaits on an in-flight prefetch for the same URL instead of racing a second download onto the same.partial. - Lupton RGB: zero-size bands raise a clear error instead of a cryptic
RuntimeError. .fits.bz2: clearValueErrorwhen CFITSIO cannot read bzip2-compressed paths (decompress first — see Lupton example).
Docs site¶
- GitHub Pages deploys stable (
/, latestv*tag) and edge (/edge/, tip ofmain) fromdocs.yml— use edge to debug docs without a SemVer release. Docs “stable” may be an rc tag; PyPI non-prerelease can lag.
1.0.0rc2 — 2026-07-18¶
Second release candidate on the 1.0 line. CFITSIO concurrent-read correctness,
leftover API/docs/CLI, and audit cleanup. SemVer 1.0.0 still waits for soak.
Install / compatibility¶
- Runtime / build metadata:
torch>=2.10(wheels and pixi stay on the 2.10 ABI lane). Source builds embed the detected torch major.minor as the ABI tag. - Docs: wheels vs source, unified GPU/accelerator install,
configure_for_environmentcalled once at import. Droppedipykernelfrom[dev]. - Disk cache root:
TORCHFITS_CACHE_DIR(default XDG /~/.cache/torchfits); remotes and samples as subdirs; Dataset /make_loaderhonorcache_dir=.
CLI¶
- Short options:
-e/--hdu,-f/--format,-o/--out,-w/--where,-c/--columns,-n/--rows,-k/--keyword(and setkey-k/--key). header --keyword-table(deprecated alias--fitsort).convert --where/--columnsfilter+export; optional FITS table out.probe --header-bytes(alias--bytes) /--timeout.probeSSRF guard: blocks private/loopback/link-local/reserved addresses viagetaddrinfo(all records) and re-validates every HTTP redirect hop.
API / ML¶
read/read_headerdefaulthdu=0(hdu=Nonestill autodetection).- Dataset peers:
FitsTensorDataset(general N-D),FitsImageDataset,FitsCubeDataset,FitsSpectrumDataset(multi-armlayout=, IVAR companions). - HTTP(S) remote prefetch under the configurable cache root.
- mmap guidance for DataLoader / network FS.
- Lift
_HDUInfo/_TableWriteProxyto module scope with__slots__. - HDU
_repr_html_usesscope=col/scope=rowfor accessibility. - Individual HDU HTML reprs: keyboard-focusable container + theme-aware borders (aligned with HDUList/Header).
Correctness / bindings¶
- Concurrent reads open a private
fitsfile*per call (CFITSIO R2); no shared-handle LRU across threads. SharedReadMeta + shared rawfdremain. - Python table/subset paths no longer share one cpp handle across threads.
- Table writes ensure C-contiguous buffers (signed-stride safe) before
fits_write_col; image writes force contiguous host tensors beforefits_write_img. write_table_hduuses RAIIvector<string>forfits_create_tblname/ttype pointers (no mid-throwchar**leak).evaluate_whererejects== NULL/!= NULLon numeric arrays; preferisnull/notnullortable.read(..., where=).- Remove duplicate
num_rowsbinding; clean deadanalyze_tablecomments. append_rows/insert_rowsbest-effort rollback viafits_delete_rowsafter a failed post-insert write.- FITS header integer keys use
PyLong_Check+ overflow-checkedTLONGLONG. - Empty-primary MEF compressed write HDU indexing fixed.
- Jules integrations: probe SSRF (#216), hoist inner classes (#214), HDU HTML a11y (#213/#219).
Benchmarks / tests¶
- ML loader: on-disk
size_mbfor compressed cases; pin compressed torchfits tohdu=1(matches fitsioext=1); missing numeric CSV fields useNone. - GPU transports: pass
quick=into table_build_cases. - Table filter tests assert exact fixture row counts.
- Concurrent same-file image/table read smoke tests.
- Scorecard: new local MPS run
exhaustive_mps_20260718_180230; Linux CPU/CUDA hosts remain the rc1 soak runs until CANFAR is re-driven against this tag.
Docs / transforms¶
- Mermaid diagrams in architecture (zensical superfences).
- Architecture: per-read handles, deliberate skip of CFITSIO iterator/
where. - Roadmap: CFITSIO 1.1 leftovers + permanent design choices from the audit.
- Advanced transforms frozen for 1.0; Lupton RGB wrapper in
transforms.lupton_rgb; richer multi-band RGB deferred to 1.1. - MegaCam cutout bench: ZNAXIS-aware HDU discovery; peer fitsio ranking; materialize-once baseline; payload-based throughput (not whole-file MB/s).
- Docs landing: lean browse grid; nav uses mark-only logo (
torchfits-logo-mark.png). - Vendored CFITSIO docs pin 4.6.4; note
fits_iterate_dataintentionally unused. - cfitsio-direct: Rice + optional MegaCam
cutout_repjobs.
1.0.0rc1 — 2026-07-17¶
Release candidate for the 1.0 API. SemVer 1.0.0 waits for post-rc soak; do
not treat this tag as the final 1.0.0 freeze.
Changed¶
-
verify/verify_checksums: missing checksum keywords are success. Files withoutDATASUM/CHECKSUMnow returnok=True,status="no_checksums", CLI textOK (no checksum keywords), exit 0. Previously CLI exited 4 (FAIL). Aligns withfitsverify(missing keywords are not corruption). Scripts that treated any nonzero verify exit as “bad file” must key offstatus == "fail"/ exit 4 instead. -
Root table helpers deprecated.
read_table,stream_table, andread_table_rowsemitDeprecationWarning. Prefertorchfits.table.read/read_torch/scan_torch.
Fixed¶
-
ArcsinhStretch/LogStretch: validatea > 0in__init__. Previouslya=0silently producedNaN(div-by-zero ininverse/forward). Now raisesValueErrorwith a clear message. (transforms/stretch.py) -
_normalize_row_slice: reject negativestopwithValueError. Previouslyslice(0, -1)silently returned 0 rows — the function cannot resolve negative indices without knowing the total row count. Now raisesValueErrorwith an actionable message. (_table/utils.py) -
Empty
WHERE/row_slice/rowsresults: preserve column schema. Previously all empty-result paths returnedpa.table({}), losing all column names/types and causingKeyErroron valid queries (e.g.where="ID > 9999"on a table with no matches). New_empty_table_with_schema()helper builds typed empty tables from FITS header cards, preserving requested column ordering. When header schema is unavailable but columns were requested, returns null-typed empty columns instead of{}. (_table/read.py) -
io.write()header type: widen toHeader | dict[str, Any] | None. RemovedTODO(1.0)andtype:ignore[arg-type]in_table/write.py. The runtime already accepted dicts; only the type annotation was narrow. (io.py,_io_engine/write_api.py,_table/write.py) -
Example runner:
REQUIREDexamples can no longer silently skip. OnlyOPTIONALexamples (e.g.example_polars.py) may skip on missing deps.REQUIREDexamples always surface failures. (examples/test_examples.py)
Added¶
docs/cli.md:### verifysection (three labels, exit codes, fitsverify note); CLI cold-start / process-tax note.docs/compatibility.md: Python / PyTorch / Arrow / platform matrix.scripts/clean_install_smoke.sh: local wheel → fresh venv install smoke.tests/test_http_probe_fixture.py: Range HTTP replay forprobe.- HTTP probe JSON records include
"source": "http"(matchesvosprobe). - Tests: stretch
a<=0, empty schema preservation, verify messaging contract, deprecation warnings, HTTP probe fixture.
Benchmark evidence¶
- Multi-host scorecard (from b1 same-day refresh, still current for rc1):
exhaustive_mps_20260717_040150,exhaustive_cpu_20260717_040146,exhaustive_cuda_20260717_042840. - Local release-suite (
20260717_212321, Mac MPS, mmap matrix,--no-gpu): 2,825 rows, 3 deficit rows, exit 0. No domain failures.
Validation¶
848+ tests; mypy / ruff clean; docs integrity; examples runner REQUIRED green;
bash scripts/clean_install_smoke.sh; HTTP probe fixture.
1.0b1 — 2026-07-17¶
Beta freeze of the public FITS → tensor / dataframe story. Not a SemVer 1.0.0 API freeze (rc line followed for soak + blockers).
Added¶
torchfits.table.read_torch(tensor-column dataframe path) andtable.read_arrow(synonym oftable.read).- Docs gallery: KaTeX math, transform before/after figures, CLI recipes, real-sample cache helpers (merged via docs gallery work).
- Release reviews: rendered docs, API adoption, deep code, real-data CLI vs astropy/fitsio/gnuastro/CFITSIO (FITSH skipped).
Changed¶
- Docs teach FITS tables as dataframes while keeping the
torchfits.tablenamespace; which-reader box demotes compatibility aliases. - Landing / site_description: tensors and dataframes (columnar catalogs).
Fixed¶
torchfits transformon integer HDUs: promote to float before transform and write float outputs without reusing integer BITPIX headers.
[0.9.3] — 2026-07-17¶
Added¶
torchfits header --fitsort --keyword …multi-file keyword table (same idea as qfitsdfits | fitsort).- Optional
vos:/vos://probe when thevospackage is installed. - Invalid
--hduvalues exit with usage code 2 instead of a traceback. - Lean
_repr_html_onTensorHDU,TableHDU, andTableHDUReffor notebooks. torchfits convert --to pngLupton RGB preview via stdlib PNG (no Pillow / NumPy). PPM removed.- Table convert formats: parquet, csv, tsv, and arrow (Arrow IPC / Feather V2). Streaming writers for large catalogs (CSV/TSV: flat columns only).
Changed¶
torchfits.transformsis a package split by domain (stretch,normalize,fits_meta,spectral,continuum,clip) with the same public__all__.- Transforms docs: not
nn.Module; instance-local inverse state; Advanced notes forBandMath,PhaseFold,AsymmetricLeastSquares,AlphaShapeContinuum; invertibility + helpers tables. - Parquet convert uses streaming
write_parquet(..., stream=True)(out-of-core). - Multi-host scorecard refresh (
exhaustive_mps_20260717_040150,exhaustive_cpu_20260717_040146,exhaustive_cuda_20260717_042840): CUDA 0 deficits, CPU 1, MPS 16. scripts/gpu-bootstrap.shpinstorch>=2.10,<2.11so CANFAR cu128 installs do not pull PyTorch 2.11 and fail the ABI gate.
Fixed¶
- Block CFITSIO
sh://filenames (command injection via/bin/sh), extending the existing|checks.
[0.9.2] — 2026-07-16¶
Added¶
torchfitsCLI — MEF-aware shell tools:info,header,verify,diff,stats,table,convert,copy,arith,cutout,compress,decompress,transform,probe,setkey. JSON/JSONL output and stable exit codes. Guide:docs/cli.md.
Changed¶
- Public imports — root is I/O + HDU only. Import transforms from
torchfits.transforms.torchfits.hduis a documented namespace. - Removed
read_fastandread_image(useread/read_tensor). Deleted the unused_fastiomodule. - Table policy helpers (
can_use_*, …) are no longer listed intable.__all__.
Fixed¶
- Signed-byte (
BZERO=-128) and unsigned smart device reads convert on the host then copy once to CUDA/MPS. read_subset/SubsetReaderkeep signed-byte and unsigned integer conventions as narrow dtypes (int8/uint16/uint32) instead of float-promoting every cutout.- Automatic table
where=withmmap=Trueuses native mmap-scan pushdown when safe;mmap=Falsereads then filters in Arrow/tensor space. - CFITSIO
MINDIRECTreset to 8640 so ~13 KB HCOMPRESS tiles use direct tile I/O. - Multi-byte mmap image reads use NEON/SSSE3 endian convert for all sizes.
- Uncompressed BYTE_IMG reads use direct
preadfor mmap on and off. - One-shot image reads use thin
cpp.read_fullinstead of handle-cache scaffolding on the cold path. - Repeated cutout benches use the persistent subset reader (open once).
- Deficit scorecard: images any lag above ε; Arrow tables allow ≤1.05×; fitsio
excluded from mmap-on peers. Linux CPU/CUDA strict-gate 0 deficits; Mac
MPS 4 on
exhaustive_mps_20260717_000853.
Docs¶
- Site logo/favicon:
torchfits-logo.png. - README / benchmark run IDs aligned with
docs/benchmarks.md.
[0.9.1] - 2026-07-15¶
Fixed¶
- Native wheel metadata now constrains PyTorch to the 2.10 ABI used to build the extension. Torchfits 0.9.0 incorrectly allowed newer incompatible libtorch releases, which could segfault during image or table conversion.
- Native builds and imports now reject mismatched PyTorch ABIs, and every CI build path installs the same PyTorch minor used by the release wheels.
0.9.0 - 2026-07-14¶
Fixed¶
- Writing one FITS file no longer invalidates borrowed native handles for unrelated files. Native cache clearing now defers closing in-use handles, preventing a subsequent read from dereferencing a closed CFITSIO handle.
- Atomic table-column rewrites now close every managed
HDUListborrower for the target path, so nested open contexts cannot retain an old inode and erase an earlier mutation. write()normalizesos.PathLiketargets before native cache invalidation.- Wheels no longer include the C++ build-source directory.
- Numeric tensor-to-Arrow conversion now shares the tensor's NumPy buffer instead of iterating through PyTorch storage one byte at a time.
- Automatic table predicates use the fast native full-read path followed by
Arrow filtering; native row-wise pushdown remains available through the
explicit
backend="cpp"policy.
Added¶
read_polars()— one-call FITS-to-Polars convenience function. Callsread()withinclude_fits_metadata=True, converts viapl.from_arrow(rechunk=False), and returns aFITSPolarsFramewrapper that preserves FITS column metadata (TFORM, TUNIT, TDIM, TNULL, TSCAL, TZERO) alongside thepl.DataFrame. Delegates__getattr__,__getitem__,__len__to the wrapped DataFrame.scan_polars()— genuine streaming Polars path. Yieldspl.DataFramebatches viapl.from_arrow(batch, rechunk=False)overscan(), without materializing the entire Arrow table. Unliketo_polars_lazy(), no full table is built.FITSPolarsFrame— lightweight dataclass wrapper aroundpl.DataFramewithfield_metaandtable_metadicts for FITS metadata preservation.- Transform masks now thread through FITS-aware normalization and clipping; spectral resampling uses torch-native interpolation with parity references for vectorized continuum, phase-folding, wavelet, and sigma-clipping paths.
- CANFAR CUDA exhaustive (
exhaustive_cuda_0.9.0_20260714_065950) — 3,648 normalized rows across the mmap on/off and CUDA matrix; 7 deficits, all at or below 1.439×, with no large-N deficit.
Changed¶
- Removed the never-implemented
TensorHDU.stats()and its empty native result from the supportedtorchfits.cppinventory instead of inventing statistics semantics during the 0.8 API freeze. ci-localnow runs its pre-build package-isolation checks againstsrc/, so a clean Linux clone no longer depends on a pre-existing editable install.- Native cache environment limits are validated before loading Torch or the extension module, preserving useful configuration errors in clean installs.
- Scoped extension-only visibility and semantic-interposition optimizations to
_C; applying them directory-wide also changed vendored CFITSIO's C ABI and aborted Linux ASCII-table writes. - Raw, unmapped image reads now support FITS
BITPIX=64images astorch.int64, matching the mapped and scaled readers. - GitHub workflows use the Node 24-based
actions/checkout@v5andactions/setup-python@v6, and pin Apple Silicon testing tomacos-15instead of following the rollingmacos-latestmigration. -
Removed the environment-dependent optional
torch_frameinheritance fromTableHDUand thetorchfits.hdu.TensorFramealias. FITS table columns stay as tensor/list mappings, Arrow is the interchange boundary, and Polars is the dataframe surface. Any legacy dataframe bridge remains outside torchfits. -
rechunk=Falsedefault onto_polars(),to_polars_lazy(),scan_polars(),read_polars(), and top-levelto_polars(). Avoids Polars' unnecessary chunk concatenation when Arrow data is already single-chunk (the common case fromread()). Passrechunk=Trueexplicitly to restore the old behavior. to_polars_lazy()docstring — clarified that it materializes the entire Arrow table eagerly before wrapping asLazyFrame. Users seeking true streaming should usescan_polars()instead.
Removed¶
"cpp_numpy"table backend alias — the deprecation alias introduced in 0.7.0 is removed. Passbackend="cpp"instead of"cpp_numpy". TheDeprecationWarningis now a hardValueError.should_skip_cpp_numpy_for_where— internal alias removed fromtorchfits._table_engine. Useshould_skip_cpp_for_where.
0.7.0 - 2026-07-10¶
Added¶
FitsTableIterableDataset— constant-memory table streaming viatable.scanwith worker sharding by scan batch index.FitsCutoutDataset— map-style patch training from(path, hdu, x, y, …)cutout specs.- Zensical documentation site —
zensical.toml,docs/index.md, GitHub Pages workflow, andpixi run docs-build/docs-serve. migration_datasets.md— breaking-change guide for removed legacy datasets.transforms.__all__— explicit public transform catalog.- CI
release-gatejob — upstream parity, docs contract, data, transforms, and security smokes on Python 3.13. - Lab benchmark refresh (
exhaustive_0.7.0_20260711_022156) — full exhaustive lab run (3516 rows, mmap matrix + MPS); CPU performance floor unchanged (core deficits ≤1.33×). - CANFAR CUDA exhaustive (
exhaustive_cuda_0.7.0_20260711_055635) — 3626 rows, 11 deficits on staging GPU; artifacts archived tovos:sfabbro/torchfits-gpu-bench/. - CANFAR bench launcher — headless GPU sessions on staging with VOS persistence
via
vcp(scripts/launch_canfar_gpu_bench.sh,scripts/fetch_canfar_bench_vos.sh).
Changed¶
- Torch-first
table.readC++ path —backend="cpp"reads viaread_fits_table_rows/TableReader.read_rows(torch tensors) instead of the numpy hop; Arrow conversion stays at the PyArrow boundary only. - Table backend rename — public backend
"cpp_numpy"renamed to"cpp"; the old name still accepted withDeprecationWarning. - Legacy datasets removed —
torchfits.FITSDatasetandtorchfits.IterableFITSDatasetdeleted; usetorchfits.datatyped datasets. table.pytrim — re-exports public API only (private_helpers no longer re-exported fromtorchfits.table).- Package description — PyPI/README positioning for ML datasets + transforms.
Removed¶
src/torchfits/datasets.py— superseded bytorchfits.data.
0.6.0 - 2026-07-09¶
Changed¶
- Unified C++ table chunk reads: Refactored
_read_cpp_numpy_tableto clean up the 7-deep C++ dispatch fallback chain andhasattrchecks, delegating directly to the modern C++TableReaderandread_fits_table_rows_numpyAPIs. This successfully resolves Roadmap Track B1. - Version synchronization: Unified package version triplet to
0.6.0acrosspyproject.toml,pixi.toml, and package source. - Blocking mypy in CI — the
mypy src/step in GitHub Actions is now a hard gate (previously non-blocking via|| echo). All 103 type errors have been resolved across 18+ source files. Added[[tool.mypy.overrides]]inpyproject.tomlforpyarrow.compute(attr-defined) andpyarrow.*(ignore_missing_imports).
[0.6.0b2] - 2026-07-09¶
Added¶
- Predicate filter improvements (all sizes now use C++ pushdown): The
predicate_filterpath delegates to C++ for all table sizes, eliminating the Python fallback for narrow tables. Theread_policy.pysize threshold is removed — safe non-VLA tables always use C++ pushdown. Narrow-table predicate_filter lag vs fitsio reduced from ~2.86× (smallest) to ≤1.07×. - Lightweight is_compressed check: Compressed images use a fast O(1) header probe instead of opening and parsing the full HDU, reducing overhead on batched compressed-image reads.
- Thread-safe caches: CacheManager internal data structures use
std::shared_mutexfor concurrent reader access, safe under multi-worker DataLoader patterns without global GIL serialisation. - Parallel scan with sequential fallback: The C++ mmap pushdown scan is now
parallelised via
at::parallel_forwhentorch::get_num_threads() > 1, with a zero-overhead sequential path when single-threaded. Addedposix_madvise(POSIX_MADV_SEQUENTIAL)to the filtered scan path for kernel prefetch hints. - Lab benchmark refresh (mmap-on+off, 0.6.0b2): 2754 rows, 3 deficits
in
20260709_163739— down from 0.6.0b1's 14 deficits and 0.5.0b4's 22 deficits. Remaining 3-deficit breakdown: - 3 fitstable (narrow):
predicate_filteronnarrow_{10000,100000,1000000}(1.07–1.25× behind fitsio;narrow_1000dropped below the deficit threshold). The gap is now dominated by Python dispatch + Arrow conversion overhead, not the C++ scan itself (which reaches near-parity with fitsio at ~11.4 ms vs ~11.0 ms for 1 M rows). All compressed-image deficits eliminated. The uint16/uint32 mmap-on regression that motivated 0.5.0b4's bswap+BZERO merge is no longer in the deficit table. - Multi-worker DataLoader coverage:
tests/test_data.pynow exercisesmake_loader(..., num_workers=2)for bothFitsImageDatasetandFitsImageIterableDataset. Tests fork a subprocess to keep CFITSIO's threadpool away from pytest's own threadpool, and verify that every file is seen exactly once regardless ofnum_workersand shuffle seed. - End-to-end FITS round-trip coverage in
tests/test_transforms_e2e.py: TestEndToEndImageRoundTrip— write / read / scale / inverse for INT16 with custom BSCALE/BZERO, BZERO=32768 unsigned convention, and INT32 with rescaling.TestEndToEndTableRoundTrip— FITS binary tables with TSCAL/TZERO use real on-disk encoding (via astropy), thenFITSScaleColumns.from_header+TNullToNan.from_headerround-trip is verified to within the storage precision.TestEndToEndFITSHeaderNormalize— full int16 BZERO=32768 round-trip through the header-driven normaliser.- Release-gate now includes
tests/test_data.py,tests/test_transforms.py, andtests/test_transforms_e2e.py. This closes the torchfits.data documented with multi-worker test coverage and torchfits.transforms round-trip tests for scaled images and tables gate items. AsymmetricLeastSquares(lam, p, max_iter, dim)— Eilers 2003 penalised baseline correction with asymmetric weights. Iteratively solves the Whittaker smoother(W + λD^T D)z = Wywith differential weighting (p above baseline, 1-p below). Standard in Raman/NIR spectroscopy. D^T D penalty matrix built in float64 for numerical stability at large λ. Additive decomposition (invertible).AlphaShapeContinuum(half_window, iterations, dim)— Morphological closing (dilation→erosion) viaunfold+ max/min. Produces a guaranteed upper envelope (always ≥ signal). Practical approximation to the full alpha-shape algorithm (RASSINE). Additive decomposition (invertible).AsymmetricSigmaClip(n_low, n_high, dim)— Simple one-pass asymmetric sigma-clipping outlier rejection usingestimate_background(median + MAD). Supports different lower/upper sigma thresholds; replaces outliers with per-group median. Lossy (no inverse)._build_d2_matrixinternal helper for the n×n pentadiagonal second-difference penalty matrix D^T D used by the Whittaker smoother / AsLS.- 27 new tests for the three transforms (201 transforms tests total, all passing).
- Example coverage for
AsymmetricLeastSquares,AlphaShapeContinuum, andAsymmetricSigmaClip(later removed with the spectral/continuum hard-cut). - All three transforms exported to the root package for direct
from torchfits import AsymmetricLeastSquaresaccess. - Documentation for all three transforms in
docs/api.mdandREADME.mdtransform tables.
0.6.0b1 - 2026-07-08¶
Removed¶
- Removed deprecated
read_large_tablefunction (usestream_tableorread_tableinstead). - Custom WHERE AST evaluator runtime (~120 lines) from
_where.py:_evaluate_cmp,_evaluate_in,_evaluate_between,_evaluate_isnull,_evaluate_where, and theevaluate_wherepublic alias. Replaced withpyarrow.computenative predicates via_where_mask_for_table. The parser, tokenizer, normalizers, andwhere_columns_from_aststay for C++ pushdown path compatibility. - Compressed parallel decompression path (~350 lines):
try_read_compressed_rows_parallel,compressed_parallel_enabled/min_pixels/min_rows_per_thread/max_threads/hcompress_enabledhelpers,load_bswaptemplates,FitsHandleGuardlocal class,is_parallel_compressed_codec_cached,compressed_parallel_cache, andhardware_concurrencydependency. CFITSIO's built-in decompression already covers this serially — the 2-thread cap meant the heuristic rarely activated. - Unused
read_rice_parallel(~320 lines) fromcompression.cpp— vendored Rice decompression, nanobind binding, and the entirecompression.cpp/compression.hfiles. Dead after compressed parallel path removal. bind_compressionfrombindings.cpp— only boundread_rice_parallel.
Changed¶
- 3→1 C++ read path merge: Extracted a single
read_tensor_canonical()infits_detail.hand converted three read paths (read_full_cached,read_full_nocache,FITSFile::read_tensor) into thin wrappers, eliminating ~455 lines of duplication. - API naming consistency: Renamed
read_image_canonical→read_tensor_canonicalandFITSFile::read_image→FITSFile::read_tensor, aligning C++ with the Pythonread_tensor/write_tensorAPI. - bswap+BZERO merge: Merged the two-pass byte-swap and BZERO offset into a single
parallel_forin the multi-byte mmap fast path. For unsigned images (uint16 with BZERO=32768, uint32 with BZERO=2147483648),bswap + addexecutes in one traversal instead of two. - Unsigned mmap fast path unlocked:
_read_unsigned_image_if_needednow defers to the C++ path whenmmap=True, lettingread_tensor_canonicalhandle unsigned conventions natively (single-pass bswap+BZERO returning uint16/uint32 directly). Previously Python preempted C++ by callingread_full_rawand doing a second offset pass — making the bswap+BZERO merge dead code. uint32_2d: 8.3× faster (now beats fitsio); uint16_2d: 3.5× faster; 5 deficits eliminated. - Vectorized string decode: Replaced per-row Python
forloops ininterop.py(to_pandas,to_arrow),table_hdu.py(get_string_column,to_fits), andtable_hdu_ref.py(get_string_column) withnp.char.decode()+np.char.rstrip()for significant speedup on large string columns. - Deduplicated
fits_schema.py:column_tnull_map()delegates to_iter_tfields_indexed()instead of reimplementing the TTYPE/TNULL iteration loop. - Deduplicated unsigned dtype and TFORM parsing:
_table/read.pynow delegates tofits_schema.unsigned_column_dtypes_from_header()andfits_schema.iter_table_columns()instead of reimplementing TZERO/unsigned detection and TTYPE/TFORM header walks. - Table schema fast path:
table.schema()skips data reads whenwhere=None, inferring the Arrow schema directly from FITS TFORM header cards (≤1 header pass). - C++ source extraction: Split
fits.cpp(4552 lines) intofits_detail.h,fits_file.h/.cpp,fits_rw.h; splittable.cpp(3432 lines) intotable_types.h,table_reader.h(header-only),table_mutation.h/.cpp. Removedextern "C"linkage from table mutation functions to fix UB from C++ exceptions crossing C ABI boundaries. Removed dead declarations, unused types, stale comments, and double includes. - Merged cache stats:
CacheManager.get_stats()now pulls I/O engine metrics (io_hits,io_misses,io_total_requests) from the cache subsystem. - WHERE evaluator → Arrow compute:
TableHDU.filter()now builds a minimal Arrow table and delegates to_where_mask_for_table(pyarrow.compute native predicates) instead of running the old NumPy-based custom evaluator. The parser stays for C++ pushdown path compatibility. - Table read unification: Extracted
_read_ranges_as_chunkfrom_read_cpp_numpy_tableinto shared_table/engine.py, removing ~50 lines of duplicated code. - CI: Added non-blocking
mypy src/step to the GitHub Actions lint job. - Benchmark fairness fix: fitsio is no longer unconditionally skipped — runs when
mmap=offfor fair buffered-read comparisons (449 fitsio OK rows in fits domain, 180 in fitstable). examples/example_image_dataset.py:optimize_for_dataset+ correctpin_memorywhen reading directly to CUDA.scripts/run_exhaustive_bench_and_patch_docs.shskips rebuild when extension imports.
Fixed¶
- Root I/O attributes now resolve to the actual public functions, preserving
inspectable signatures, tracebacks, and identity while keeping bare
import torchfitsfree of PyTorch, NumPy, Arrow, and the native extension. torchfits.cppnow has an explicit FITS-native__all__; future compiled symbols no longer become public accidentally. Direct attribute delegation is retained for pre-1.0 compatibility.- Every lazy root export now has a matching
TYPE_CHECKINGdeclaration, so the shippedpy.typedmarker covers the complete documented root API. - Removed the empty, misleading
cacheextra: adaptive cache sizing uses the standard library. Documentation now states that PyArrow is the core table runtime while Pandas, Polars, and DuckDB are optional. - Runtime initialization no longer swallows native-load or invalid cache configuration errors and then marks the failed initialization as complete.
- Header-card write failures and HDU header-preservation failures are no longer silently ignored; callers now receive the native error instead of a successful return with lost metadata. A dead duplicate header helper was removed.
- Overwriting an existing FITS file is now transactional: the complete replacement is written beside the target and atomically installed only after success. Validation or native-write failures preserve the original bytes and file mode instead of deleting the user's file.
- HDU insert, replace, and delete operations use the same transactional rewrite rule, so a partial multi-HDU rewrite cannot replace the original file.
- Iterable HDU writes reject empty sequences, unsupported objects, header-only dictionaries, and non-tensor image payloads instead of silently emitting empty HDUs.
- Image datasets now route through the unified image reader, so their documented
mmap="auto"policy works instead of reaching the bool-onlyread_tensorboundary. Remaining immutable column tuples are normalized at public list boundaries. TableHDUvalidates its trust boundary: non-mapping inputs and columns with inconsistent row counts fail immediately instead of creating an internally inconsistent table.TableHDU.from_fits()now uses the publicread_table()pipeline instead of opening a separate native table/header path, keeping cache, validation, and runtime initialization behavior consistent with the rest of the package.- Removed the duplicate
whereentry from the package root__all__contract. - Scoped mypy's missing-import exceptions to optional dataframe integrations
and the compiled extension, allowing real Python type errors to
surface. Mypy now checks untyped function bodies and is a blocking local
preflight and CI check; the resulting
TableHDURefcolumn-sequence mismatch was fixed at the Arrow boundary. - Release wheels now run image and table round-trip tests against the installed artifact, and macOS arm64 wheels use the platform's real minimum deployment target (11.0). Platform documentation now matches the wheel matrix. Vendored CFITSIO remains statically linked but its development headers, archive, and CMake/pkg-config metadata are no longer copied into wheels.
- Multi-worker DataLoader tests now use a real
__main__guard, matching the macOSspawncontract instead of recursively creating workers frompython -c; timeout failures preserve worker stderr for diagnosis. - Vectorized NULL evaluation:
_where.pyreplaced Python-loopnp.array([v is None for v in val])with vectorized(val == None)for element-wise null checks. - Fixed unused imports in
tests/test_cache_config.py. - Security: Block CFITSIO pipe injection bypass via leading
!prefix (!|command) incheck_fits_filename_security; also enforced on unified cache open path. - GPU
scale_on_devicepreserves narrow integer H2D for FITS signed-byte (int8) and unsigned uint16/uint32 conventions instead of promoting through float32 or int64 on CPU.
Added¶
- Header: O(N) construction for large dict inputs via keyed fast-path in
_set_card(2000 keys ~0.002s locally vs ~2.5s pre-fix). - Jupyter: Scrollable, sticky-header HTML repr for
HeaderandHDUList. tests/test_scale_on_device.py— signed-byte, unsigned, and fitsio parity checks.- Release gate includes
test_scale_on_device.py. .cursor/skills/release-api-freeze-review/— pre-tag API/feature freeze audit workflow._table/engine.py— shared C++ table read dispatch module with extracted_read_ranges_as_chunkhelper (de-duplicated from_read_cpp_numpy_table).
Performance notes¶
- Local
bench_ml_loader.pydiagnostic (30×512² float32, CPU, 2 epochs): Rice-compressed 1.12× vs fitsio; uncompressed within ~4% (tune handle cache for your file count). - Lab exhaustive refresh (
exhaustive_mmap_0.5.0b4_20260630_162835, H100 MIG): 3626 rows, 13 deficits (down from 22). Integer CUDA gaps closed; remaining are marginal int8 (≤1.2×) and coldlarge_uint32_2dCPU vs astropy (~1.5×). - User-profile refresh (
unsigned_mmap_fix_20260708, CPU, mmap=on): 1,377 rows, 25 deficits.torchfits_specializeduint32_2d now beats fitsio (was 5–10× behind); uint16_2d at ~1.6× vs fitsio (was ~5×). Remaining deficits dominated by medium-size unsigned reads and compressed HCOMPRESS. Torchfits dominates table I/O (886×–2,318× vs astropy), image reads (7.92× vs astropy, 1.76× vs fitsio on large float32), and repeated cutouts (17× vs astropy, 1.09× vs fitsio).
0.5.0b4 - 2026-06-30¶
Changed¶
- Centralized FITS binary-table header parsing in
fits_schema(TFORM/VLA/string/bit/unsigned). table.readno longer recurses forwhere=; strategy lives in_table_engine.read_policy.- Table C++ handle caches moved to
_table.cache; I/O cache invalidation no longer depends on importingtorchfits.table. - README highlights 0.5.0 features and published benchmark speedups; API docs document table
backends and
where=tuning environment variables.
Added¶
- Unit tests for
fits_schema, table where-read policy, and runnable example scripts. - Public
torchfits.table.TABLE_BACKENDSconstant. pixi run release-gatetask matching the release checklist parity/docs/examples gates.
0.5.0b3 - 2026-06-30¶
Changed¶
- Refocused torchfits as a FITS I/O package: images, HDUs, headers, checksums, compression, FITS tables, caching, and table interop.
- Removed stale public claims that torchfits owns WCS, sphere geometry, HEALPix, sky-domain simulation, or training pipelines. Those domains belong outside torchfits.
- Added a roadmap and compatibility matrix that distinguish supported, partial, unsupported, and out-of-scope behavior.
- Replaced broad parity claims with test-backed parity tiers for common fitsio, Astropy, and selected CFITSIO-backed workflows.
Added¶
- Extended benchmark matrix: native uint16/uint32 2D image fixtures, typed binary tables (BIT/complex/string columns), and ASCII table fixtures.
bench_all.py --mmap-matrixruns mmap-on and mmap-off passes in one CSV so the I/O transport table can populate bothdisk→CPUanddisk→RAM→CPU(plus GPUdisk→CPU→GPU/disk→RAM→GPUwhen CUDA/MPS is available).scripts/run_exhaustive_bench_and_patch_docs.shfor lab-profilebench-allon CUDA/MPS hardware with automaticdocs/benchmarks.mdrefresh.- Lab CUDA benchmark snapshot
exhaustive_mmap_0.5.0b3_20260630_063118(3474 rows, mmap on+off matrix, 720 GPU transport rows on H100). docs/parity.mdfor the public compatibility matrix.- Astropy upstream smoke coverage for common image, HDU, compressed-image, table, ASCII table, VLA, complex column, and scaled-image workflows.
- Documentation integrity checks for stale WCS/sphere/HEALPix ownership claims.
- Supported-status promotion for in-place mmap table updates on COMPLEX
(
1C/1M), BIT (8X), and fixed-width STRING (12A-style) columns.torchfits.table.update_rows(..., mmap=True)now writes these column types correctly on disk. Verified via raw byte inspection and an astropy upstream-reader roundtrip. VLA columns remain explicitly unsupported in the mmap fast path by design. - Astropy and fitsio upstream smoke coverage that exercises the
COMPLEX / BIT / fixed-width STRING mmap-update parity shift,
including right-padding to the declared column width and verification
vs the upstream readers. The 8A-string assertion falls back to
astropy because the local fitsio upstream misdecodes updated
8Arows (the on-disk bytes are bit-exact to the expected layout; this is an upstream-reader limitation, not a torchfits writer bug). tests/test_astropy_upstream_smoke.py::test_astropy_compimage_compression_variants_match_torchfitsexercising additionalastropy.io.fits.CompImageHDUcompression variants (RICE / HCOMPRESS / PLIO) round-tripped against torchfits.
Fixed¶
- API docs and install guide now reference
torchfits.cachefor cache tuning (configure_for_environment,get_cache_stats,clear_cache) and the root I/O helpersget_cache_performance/clear_file_cachewhere appropriate. - Roadmap mmap limitations updated to match the parity matrix (BIT and fixed-width STRING mmap updates are supported; VLA and scaled columns remain partial).
Removed¶
- Dataset/training helper namespace from the torchfits package contract.
0.5.0b2 - 2026-06-30¶
Fixed¶
- Patched
fitstablespecialised column projection and row slicing benchmark errors due to invalidpolicyargument. - Cleaned up C++ build flags in
bench-gputo remove strict CUDA and Torch pins. - Audited C++ codebase for potential memory leaks, redundant hardware heuristics, and API bounds.
Added¶
- Restored core FITS benchmarks from v0.3.2: ML DataLoader performance (
bench_ml_loader.py) and GPU Memory usage/leak validator (bench_gpu_memory.py). - Added exhaustive progress print logging during benchmark execution.
- Added persistent cutout / multi-cutout repeated read benchmarks (
SubsetReader/open_subset_reader) for both CPU and GPU. - Added
read_tensorfor reading N-dimensional arrays (1D spectra, 2D images, 3D cubes, xD arrays) directly to a single PyTorchTensor. - Added
write_tensoras the specialized PyTorch-native writer for writing single PyTorchTensors directly to FITS files.
Deprecated¶
- Deprecated
read_imagein favor of the more general and PyTorch-nativeread_tensor.
0.5.0b1 - 2026-06-29¶
Changed¶
- Repository home:
github.com/astroai/torchfits. - Default development Python is 3.13 (pixi); supported install range remains 3.10+.
- Development Status classifier promoted to Beta.
- Removed obsolete diagnostic benchmarks, scratch scripts, and legacy HEALPix/WCS artifacts.
- CI rewritten: ruff-only lint, multi-OS/Python test matrix, CFITSIO vendoring via
extern/VERSIONS.txt. - Wheel builds: portable flags (no
-march=native),cp310–cp313on macOS and Linux.
Added¶
- GPU I/O transport benchmark rows (
bench_gpu_transports.py) with MPS on Apple Silicon and CUDA on Linux. pixi run bench-mpsfor Apple Silicon accelerator benchmarks.- Automated benchmark report workflow (
.github/workflows/bench-report.yml). scripts/render_bench_deficits.pyfor documenting performance deficits without fixing them.
Fixed¶
- Table mutations now invalidate FITS path caches via internal
iohelper (fixestorchfits._invalidate_path_cachesAttributeError).
Earlier releases¶
Earlier 0.1.x through 0.3.x releases included broader experimental astronomy domains. The current package contract is FITS I/O only; consult the current README, API reference, roadmap, and parity matrix for supported behavior.