The scripts that processed the NGC 5128 session of 2026-07-21 previously lived inside the data directory and addressed it with absolute paths. Code and data are now separated: the code lives here, and a session is located at runtime through the ASTRO_SESSION environment variable. layout.py is what makes that work. It maps a FILENAME to the subdirectory that file belongs in, using the same rules the session directories are organised with, so a script can go on asking for 'master-Red.fit' or '_stars.npz' without any call site knowing the directory structure. Anything unrecognised resolves to the session root, which is visible and correctable rather than silently wrong. restructure.py reorganises a flat session directory into that layout. It is idempotent and dry-run by default. The 50 session scripts are kept as they were run rather than tidied into a library. They were written in sequence as the work went along, several of them by parallel agents, and they show it - but they are the honest provenance of a published set of results, and the productionised pipeline should be able to reproduce those results exactly. Verified before committing: all 51 files compile without warnings, and verify_core.py, closeup.py and triptych.py were run end to end against the reorganised session, correctly finding inputs across calibrated/, stacks/masters/ and final/ and writing outputs back to the right places.
70 lines
2.5 KiB
Python
70 lines
2.5 KiB
Python
"""Pass 1: measure every calibrated frame and cache its star list.
|
|
|
|
Frames are 4788x3194 float32 (61 MB each) and the machine has little free RAM,
|
|
so each frame is opened, measured and released one at a time. The star lists are
|
|
cached to an npz because both the registration pass and the plate solve need
|
|
them, and re-detecting costs more than re-reading a small array.
|
|
|
|
Recorded per frame: sky background and its rms, the number of detections, and a
|
|
median FWHM derived from sep's half-flux radius. The FWHM is the seeing metric
|
|
used later to pick the registration reference and to weight the stack.
|
|
"""
|
|
import glob
|
|
import os
|
|
import re
|
|
|
|
import numpy as np
|
|
import sep
|
|
from astropy.io import fits
|
|
|
|
import layout
|
|
|
|
SRC = layout.SESSION
|
|
CACHE = layout.path("_stars.npz")
|
|
os.makedirs(os.path.dirname(CACHE), exist_ok=True)
|
|
|
|
NAME_RE = re.compile(r"-(Luminance|Red|Green|Blue)-BIN2-W-300-(\d+)\.fit$")
|
|
|
|
|
|
def frame_list():
|
|
out = []
|
|
for path in sorted(glob.glob(layout.path("calibrated-*.fit"))):
|
|
m = NAME_RE.search(path)
|
|
if m:
|
|
out.append((path, m.group(1), int(m.group(2))))
|
|
return out
|
|
|
|
|
|
if __name__ == "__main__":
|
|
frames = frame_list()
|
|
print(f"{len(frames)} calibrated frames")
|
|
store, meta = {}, []
|
|
for path, filt, idx in frames:
|
|
with fits.open(path, memmap=False) as hd:
|
|
data = hd[0].data.astype(np.float32)
|
|
bkg = sep.Background(data, bw=64, bh=64, fw=3, fh=3)
|
|
back_med = float(np.median(bkg.back()))
|
|
rms = float(bkg.globalrms)
|
|
sub = data - bkg.back()
|
|
objs = sep.extract(sub, 5.0, err=rms, minarea=9, deblend_cont=0.005)
|
|
objs = objs[(objs["flag"] == 0) & (objs["npix"] > 12) &
|
|
(objs["npix"] < 2000)]
|
|
objs = objs[np.argsort(objs["flux"])[::-1][:400]]
|
|
rad, _ = sep.flux_radius(sub, objs["x"], objs["y"], 6.0 * objs["a"],
|
|
0.5, normflux=objs["flux"])
|
|
fwhm = float(np.median(rad) * 2.0)
|
|
key = f"{filt}_{idx:03d}"
|
|
store[key + "_xy"] = np.column_stack([objs["x"], objs["y"]])
|
|
store[key + "_flux"] = objs["flux"]
|
|
meta.append((key, os.path.basename(path), filt, idx, len(objs), fwhm,
|
|
back_med, rms))
|
|
print(f"{key:16s} stars={len(objs):4d} fwhm={fwhm:5.2f}px "
|
|
f"bg={back_med:8.1f} rms={rms:6.1f}")
|
|
del data, sub, bkg, objs
|
|
|
|
np.savez_compressed(
|
|
CACHE,
|
|
meta=np.array(meta, dtype=object),
|
|
**store,
|
|
)
|
|
print("cached ->", CACHE)
|