astrophotography/pipeline
laurence fbd1eb8ec5 Add the plate solve stage; it works on two sessions of four
astrometry.py solves the deepest master against Gaia and copies the WCS
into every master. Asterism matching is invariant to rotation and scale,
so camera angle never has to be guessed, but not to a mirror flip, so
both parities are tried.

Three defects were found and fixed by running it, and each is worth
recording because none would have been found by reading the code.

Gaia's launch_job_async submits to a job QUEUE and polls. On a query of
600 rows it hung for ten minutes while curl showed both the Gaia and
VizieR endpoints answering in under a second. VizieR is now queried
first - it serves the identical DR3 catalogue over a plain HTTP request -
with Gaia's SYNCHRONOUS endpoint as the fallback.

VizieR's row_limit truncates before sorting, so asking for the 600
brightest stars returned 600 arbitrary ones. Asterism matching only works
when both lists hold the same bright stars, so this silently produced no
match at all. It now fetches generously and picks the brightest locally.

The detector deliberately rejects saturated cores, which means the
brightest DETECTIONS are not the brightest STARS, while the catalogue's
are - so the two top-N lists can barely overlap. A sliding window down
the catalogue's magnitude ranking fixed NGC 2030, which matched at
catalogue[15:135], skipping the 15 brightest. That is the saturation
hypothesis confirmed rather than assumed.

NGC 2030 now solves on 243 stars at 0.31 arcsec residual. NGC 2070 and
NGC 6744 still do not, and TODO.md records what has already been ruled
out - scale, the fetch, truncation - so the next session starts from the
remaining candidates rather than repeating the elimination.
2026-07-21 21:27:33 +01:00
..
analyse.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
annotate.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
astrometry.py Add the plate solve stage; it works on two sessions of four 2026-07-21 21:27:33 +01:00
closeup.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
colour.py Add the colour stage, palette chosen from the filters present 2026-07-21 19:44:02 +01:00
compose.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
depth.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
enhance.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
final.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
gaia_colours.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
gc-bwtrial.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
gc-classify.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
gc-complete-inner.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
gc-complete.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
gc-detect.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
gc-fetch-vizier.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
gc-gaia-astrom2.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
gc-plots.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
gc-validate.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
hdr.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
layout.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
measure.py Add the measure and register stages, generic across sessions 2026-07-21 19:39:30 +01:00
mo_cavs.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
mo_check.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
mo_common.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
mo_detect.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
mo_fig_moving.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
mo_fig_transient.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
mo_finalise.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
mo_gccheck.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
mo_link.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
mo_mpc.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
mo_sensitivity.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
mo_shiftstack.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
mo_transient.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
mo_vet.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
original.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
README.md Make the merged repository coherent: README, state, and internal links 2026-07-21 17:16:20 +01:00
register.py Add the measure and register stages, generic across sessions 2026-07-21 19:39:30 +01:00
rename.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
restructure.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
sb_common.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
sb_dust.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
sb_iso.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
sb_limits.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
sb_model.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
sb_prep.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
sb_profile.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
sb_render_tail.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
sb_residual.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
session.py Add session discovery and ingest, driven by headers not filenames 2026-07-21 17:28:26 +01:00
solve.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
stack.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
starless.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
subdir_readmes.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
triptych.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
unzip.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00
verify_core.py Move the processing code under pipeline/ 2026-07-21 17:13:54 +01:00

pipeline

Processing and analysis code for remote-telescope imaging sessions, starting with iTelescope data from the itelescope drain campaign.

The code lives here. The data does not - image sessions stay on disk (or wherever they are archived) and are addressed by an environment variable, so a session directory contains only pixels, results and a description of what was done to them.

What is here now

pipeline/ - the 50 scripts that processed the NGC 5128 session of 2026-07-21, exactly as they were run, plus the shared layout.py that tells them where files live. This is a working record rather than a finished product: the scripts were written in sequence as the work went along, several of them by parallel agents, and they show it. They are kept because they are the honest provenance of a set of published results, and because the productionised pipeline should be able to reproduce those results exactly.

pipeline/restructure.py - reorganises a flat session directory into the named layout below. Idempotent, dry run by default.

Pointing the scripts at a session

set ASTRO_SESSION=D:\astro\NGC5128\20260721      # Windows
export ASTRO_SESSION=/data/astro/NGC5128/20260721 # POSIX
python pipeline/layout.py                  # prints the resolved layout

layout.py maps a filename to its subdirectory, so a script asks for master-Red.fit or _stars.npz and gets the right path without knowing the directory structure:

Directory Holds
raw/ exactly what the telescope delivered: archives and their preview jpegs
calibrated/ uncompressed calibrated subs
stacks/masters/ per-filter registered, plate-solved masters
stacks/original/ alignment-only baseline stacks, no other processing
final/ the deliverable renderings
renderings/ other finished images
science/figures/ analysis plots
science/catalogues/ measured tables (CSV)
science/data/ models, masks, derived quantities
science/notes/ analysis write-ups
intermediates/ caches a re-run can regenerate

Every session directory also carries its own METHODS.md describing what was done to that data and what was found - written for a reader who was not there.

Running order

The scripts are named for their stage and run in this order:

unzip.py -> analyse.py -> stack.py -> solve.py -> depth.py
         -> compose.py -> hdr.py / enhance.py / starless.py / annotate.py
         -> final.py -> closeup.py -> triptych.py

The analysis families are independent of each other and of the renderings: gc-* (globular clusters), sb_* (surface photometry), mo_* (moving objects and transients).

Requirements

Python 3.12 with numpy, scipy, astropy, scikit-image, sep, astroalign, photutils, astroquery, matplotlib, tifffile, Pillow.

Where this is going

The next piece of work is a scheduler-driven pipeline: a staged CLI (ingest -> calibrate -> measure -> register -> stack -> solve -> compose -> analyse) with each stage resumable, packaged as an Apptainer image and driven by Slurm array jobs. Targets beyond mono LRGB: narrowband palettes, one-shot colour with debayering, other observatories' header conventions, and full calibration from bias/dark/flat for sources that do not pre-calibrate.

Three findings from the first session are requirements for that build, not optional extras:

  1. Vet moving-object candidates in detector coordinates. Registration holds the sky still, so it drags detector-fixed defects across the frame on perfectly straight, constant-rate tracks. Hot pixels are better-behaved asteroids than real asteroids. This one cut took 141 confident spurious detections to zero.
  2. Carry r50/psf through to any catalogue cross-match. Comparing an aperture magnitude of a resolved source against a point-source catalogue like Gaia is meaningless, and looks exactly like a 2.8 magnitude outburst.
  3. Never fit a sky background to a field the target fills. A plane fitted around a large galaxy absorbs its halo - measured at -17.9 ADU/px here. Fit the background and a source model together.