Move the processing code under pipeline/

Preparing to merge this repository into a combined astrophotography
repo. session-scripts/ becomes pipeline/ because the scripts import
layout.py from their own directory and must stay together, and because
'pipeline' says what it is rather than how it came about. observing/
stays at the top level: observing plans are not processing code.
This commit is contained in:
laurence 2026-07-21 17:13:54 +01:00
parent 653ca103cd
commit c6299f41ab
53 changed files with 0 additions and 0 deletions

191
pipeline/mo_mpc.py Normal file
View file

@ -0,0 +1,191 @@
#!/usr/bin/env python3
"""
mo_mpc.py -- "was there a known minor planet in the frame?"
Purpose
-------
The NGC 5128 (Centaurus A) LRGB session of 2026-07-21 was searched for moving
objects (see _mo_dets.npz / _mo_tracklets.npy). Before any detection can be
called a *new* object, we must rule out that it is simply a catalogued minor
planet that happened to drift through the field. This script asks the
authoritative services what known small bodies were inside the field of view at
the epoch of the exposures.
Field / epoch
-------------
Field centre : RA 13 25 27.37, Dec -43 01 10.9 (J2000)
= 201.36404 deg, -43.01969 deg
Field size : 42.9' x 28.6' (half-diagonal ~25.8')
Search radius: 30 arcmin (0.5 deg) -- comfortably encloses the whole frame
Epoch : 2026-07-21 09:22 UTC (= 2026 07 21.39), mid-luminance-sequence
(luminance ran 08:56-10:00 UTC, session ended ~11:16 UTC)
Observatory : Q62, iTelescope / Siding Spring Observatory
Services queried
----------------
1. JPL Small-Body Identification API
https://ssd-api.jpl.nasa.gov/sb_ident.api
Primary source. Well documented, accepts arbitrary (including future) epochs
because it propagates orbits rather than serving a precomputed ephemeris.
IMPORTANT UNITS GOTCHA: despite some documentation wording, fov-ra-hwidth and
fov-dec-hwidth are interpreted in DEGREES, not arcminutes. Passing "30"
yields a 30-degree half-width box (the API echoes the box it used in
["observer"]["fov_offset"], which is worth checking every run).
Queried twice: sb-kind=a (asteroids) and sb-kind=c (comets).
2. MPC "MPChecker" / minor planet checker
https://minorplanetcenter.net/cgi-bin/mpcheck.cgi (classic HTTP POST form)
https://www.minorplanetcenter.net/cgi-bin/checkmp.cgi
Cross-check. Kept in the script even if it fails, so the log records whether
the MPC endpoint was reachable and what it said.
Output
------
Raw responses (JSON + HTML) and a human-readable summary are appended to
intermediates/_mpc_query.txt
Nothing is fabricated: if a service errors or refuses the epoch, the error text
itself is written to the log.
"""
import json
import math
import sys
import datetime
import requests
import layout
# ---------------------------------------------------------------- field/epoch
RA_SEX = "13-25-27.37"
DEC_SEX = "M43-01-10.9" # JPL uses a leading "M" for a negative Dec
RA_DEG = 201.36404
DEC_DEG = -43.01969
OBS_TIME = "2026-07-21_09:22:00"
MPC_CODE = "Q62" # iTelescope, Siding Spring
RADIUS_ARCMIN = 30.0
HWIDTH_DEG = RADIUS_ARCMIN / 60.0 # = 0.5 deg; API wants DEGREES here
VMAG_LIM = 22.0
OUT = layout.path("_mpc_query.txt")
log_lines = []
def log(s=""):
print(s)
log_lines.append(str(s))
# ------------------------------------------------------------------ JPL query
def jpl(sb_kind):
"""Query ssd-api.jpl.nasa.gov/sb_ident.api for one small-body class."""
params = {
"sb-kind": sb_kind, # 'a' = asteroid, 'c' = comet
"mpc-code": MPC_CODE,
"obs-time": OBS_TIME,
"fov-ra-center": RA_SEX,
"fov-dec-center": DEC_SEX,
"fov-ra-hwidth": f"{HWIDTH_DEG}",
"fov-dec-hwidth": f"{HWIDTH_DEG}",
"mag-required": "true",
"two-pass": "true",
"vmag-lim": f"{VMAG_LIM}",
}
r = requests.get("https://ssd-api.jpl.nasa.gov/sb_ident.api",
params=params, timeout=180)
log(f"--- JPL sb_ident sb-kind={sb_kind} -> HTTP {r.status_code}")
log(f" URL: {r.url}")
if r.status_code != 200:
log(" RAW: " + r.text[:2000])
return None
d = r.json()
log(" observer : " + json.dumps(d.get("observer", {})))
log(" n_first_pass : " + str(d.get("n_first_pass")))
log(" n_second_pass: " + str(d.get("n_second_pass")))
return d
def show(d, label):
"""Print the refined (second-pass) hits, falling back to first pass."""
if d is None:
return
rows = d.get("data_second_pass") or []
fields = d.get("fields_second") or []
if not rows:
rows = d.get("data_first_pass") or []
fields = d.get("fields_first") or []
log(f" [{label}] no second-pass data; showing first pass")
log(f" fields: {fields}")
if not rows:
log(f" [{label}] NO OBJECTS in field.")
return
for row in rows:
log(" " + " | ".join(str(x) for x in row))
# ------------------------------------------------------------------ MPC query
def mpchecker():
"""Classic MPChecker POST form. Recorded even if it fails."""
data = {
"year": "2026", "month": "07", "day": "21.39",
"which": "pos",
"ra": "13 25 27.37", "decl": "-43 01 10.9",
"TextArea": "",
"radius": str(int(RADIUS_ARCMIN)),
"limit": f"{VMAG_LIM}",
"oc": MPC_CODE,
"sort": "d", "mot": "h", "tmot": "s",
"pdes": "u", "needed": "f", "ps": "n", "type": "p",
}
for url in ("https://minorplanetcenter.net/cgi-bin/mpcheck.cgi",
"https://www.minorplanetcenter.net/cgi-bin/checkmp.cgi"):
try:
r = requests.post(url, data=data, timeout=120,
headers={"User-Agent": "Mozilla/5.0 (mo_mpc.py)"})
log(f"--- MPC POST {url} -> HTTP {r.status_code}, {len(r.text)} bytes")
log(r.text[:8000])
except Exception as e: # noqa: BLE001
log(f"--- MPC POST {url} -> EXCEPTION {type(e).__name__}: {e}")
# ----------------------------------------------------------------------- main
if __name__ == "__main__":
log("MPC / minor-planet field check")
log("run at " + datetime.datetime.utcnow().isoformat() + "Z")
log(f"field centre {RA_SEX} {DEC_SEX} (J2000) = {RA_DEG}, {DEC_DEG}")
log(f"epoch {OBS_TIME} UTC, obs code {MPC_CODE}, "
f"radius {RADIUS_ARCMIN}' , V<{VMAG_LIM}")
log()
for kind, label in (("a", "asteroids"), ("c", "comets")):
try:
show(jpl(kind), label)
except Exception as e: # noqa: BLE001
log(f" JPL {label} EXCEPTION {type(e).__name__}: {e}")
log()
mpchecker()
# --- derived summary: total sky motion and position angle for JPL hits ---
log()
log("=== derived summary (from JPL second pass, sb-kind=a) ===")
try:
d = jpl("a")
for row in (d.get("data_second_pass") or []):
name, ra, dec = row[0], row[1], row[2]
norm = float(row[5])
vmag = row[6]
dra, ddec = float(row[7]), float(row[8]) # "/h, RA rate has cos(dec)
tot = math.hypot(dra, ddec)
pa = math.degrees(math.atan2(dra, ddec)) % 360.0 # N through E
log(f"{name}: RA {ra} Dec {dec} V={vmag} "
f"{norm:.0f}\" ({norm/60:.1f}') from centre "
f"motion {tot:.1f}\"/hr at PA {pa:.1f} deg "
f"(dRA*cos(dec)={dra:.1f}, dDec={ddec:.1f} \"/hr)")
except Exception as e: # noqa: BLE001
log(f"summary EXCEPTION {type(e).__name__}: {e}")
with open(OUT, "w", encoding="utf-8") as fh:
fh.write("\n".join(log_lines) + "\n")
print("\nwrote " + OUT)