astrophotography/session-scripts/triptych.py
laurence 5286a2e81b 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.
2026-07-21 15:29:49 +01:00

62 lines
2.4 KiB
Python

"""Side-by-side of the three renderings, full field and a matched core crop.
Same pixels in all three panels, so any difference is processing and nothing
else.
"""
import os
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
import layout
OUT = layout.SESSION
# The finished renderings live in their own directory: they are the
# deliverables, and keeping them apart from the masters, the
# intermediates and the analysis figures makes it obvious which
# files are meant to be looked at.
FINAL = layout.path("final")
PANELS = [
("NGC5128-final-1-stacked.png", "1. Stacked + standard stretch",
"align, average, stretch - nothing else"),
("NGC5128-final-2-processed.png", "2. Conventionally processed",
"gradient removal, colour balance, denoise"),
("NGC5128-final-3-best.png", "3. Science-informed",
"halo-preserving background, solar-analogue colour,\n"
"core recovered, star-protected deconvolution"),
]
CROP = (1950, 1200, 2850, 1875) # the dust lane, at full resolution
fig, axes = plt.subplots(2, 3, figsize=(21, 11.5),
gridspec_kw=dict(height_ratios=[1.35, 1]))
fig.patch.set_facecolor("#111111")
for col, (fname, title, sub) in enumerate(PANELS):
im = Image.open(layout.path(fname))
wide = im.copy()
wide.thumbnail((1500, 1500), Image.LANCZOS)
axes[0, col].imshow(np.asarray(wide))
axes[0, col].set_title(title, color="white", fontsize=15, pad=10)
axes[0, col].text(0.5, -0.055, sub, color="#9fb8d0", fontsize=10,
ha="center", va="top", linespacing=1.4, wrap=True,
transform=axes[0, col].transAxes)
axes[1, col].imshow(np.asarray(im.crop(CROP)))
axes[1, col].set_title("core, 1:1", color="#9fb8d0", fontsize=11, pad=6)
for row in (0, 1):
axes[row, col].set_xticks([])
axes[row, col].set_yticks([])
for s in axes[row, col].spines.values():
s.set_color("#333333")
fig.suptitle("NGC 5128 (Centaurus A) - iTelescope T32, 2026-07-21 - "
"L 12x300s, RGB 4x300s each - the same data, three treatments",
color="white", fontsize=17, y=0.975)
fig.tight_layout(rect=[0, 0.01, 1, 0.955])
fig.subplots_adjust(hspace=0.16)
path = layout.path("NGC5128-final-comparison.jpg")
fig.savefig(path, dpi=100, facecolor=fig.get_facecolor(),
pil_kwargs={"quality": 92})
print("wrote", path)