Processing and analysis code for remote-telescope imaging sessions

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.
This commit is contained in:
laurence 2026-07-21 15:29:49 +01:00
commit 5286a2e81b
53 changed files with 8820 additions and 0 deletions

91
session-scripts/rename.py Normal file
View file

@ -0,0 +1,91 @@
"""Prefix every image output with the object name, and fix every reference.
Renaming files is the easy half. The half that breaks a project silently is the
references: METHODS.md and the three analysis notes cite these filenames, and
the scripts that WRITE them would otherwise recreate the old names on the next
run. So this rewrites the markdown and the scripts in the same pass.
Dry run by default; pass --apply to actually move anything.
"""
import os
import re
import sys
import layout
ROOT = layout.SESSION
PREFIX = "NGC5128-"
IMAGE_EXT = (".png", ".jpg", ".jpeg", ".tif", ".tiff")
APPLY = "--apply" in sys.argv
def image_files():
"""Every image in the stacked tree that is not already prefixed."""
out = []
for folder in (ROOT, layout.path("original")):
if not os.path.isdir(folder):
continue
for name in sorted(os.listdir(folder)):
path = os.path.join(folder, name)
if not os.path.isfile(path):
continue
if not name.lower().endswith(IMAGE_EXT):
continue
if name.startswith(PREFIX):
continue
out.append((folder, name, PREFIX + name))
return out
renames = image_files()
print(f"{len(renames)} image files to rename\n")
for folder, old, new in renames:
where = os.path.relpath(folder, ROOT)
print(f" {where}/{old} -> {new}")
# Build the substitution map. Longest names first so that a short name which is
# a substring of a longer one cannot corrupt it.
mapping = {old: new for _, old, new in renames}
ordered = sorted(mapping, key=len, reverse=True)
targets = []
for folder, _, _ in [(ROOT, None, None)]:
for name in os.listdir(folder):
if name.lower().endswith(".md"):
targets.append(os.path.join(folder, name))
scripts = layout.path("scripts")
if os.path.isdir(scripts):
for name in os.listdir(scripts):
if name.lower().endswith(".py"):
targets.append(os.path.join(scripts, name))
print(f"\nchecking {len(targets)} markdown and script files for references")
edits = []
for path in targets:
try:
text = open(path, encoding="utf-8").read()
except UnicodeDecodeError:
text = open(path, encoding="latin-1").read()
updated = text
hits = 0
for old in ordered:
# A reference is the bare filename; guard the left edge so an already
# prefixed occurrence is not double-prefixed.
pattern = re.compile(r"(?<![\w./-])" + re.escape(old))
updated, n = pattern.subn(mapping[old], updated)
hits += n
if hits:
edits.append((path, hits, updated))
for path, hits, _ in edits:
print(f" {os.path.relpath(path, ROOT)}: {hits} references")
if not APPLY:
print("\nDRY RUN - nothing changed. Re-run with --apply")
sys.exit(0)
for folder, old, new in renames:
os.replace(os.path.join(folder, old), os.path.join(folder, new))
for path, _, updated in edits:
open(path, "w", encoding="utf-8").write(updated)
print(f"\nrenamed {len(renames)} files, updated {len(edits)} referencing files")