Commit graph

57 commits

Author SHA1 Message Date
88aaac854c Merge pull request 'solve: prefer the estate's own Astrometry.net over nova for blind solves' (#2) from astrometry-net-fallback into main 2026-07-21 23:00:48 +01:00
118dc36e92 solve: prefer the estate's own Astrometry.net over nova for blind solves
The estate now runs Astrometry.net itself (ankh-morpork-infra astrometry/):
solve-field against ~5 GB of local index files, sized from this repo's
TELESCOPES.md so the whole iTelescope fleet's fields of view are covered.

blind.py now tries it before nova.astrometry.net. Speed is the least of the
reasons - a blind solve of a real DSS field returns in 0.7s where nova queues
for minutes. The reasons that matter are that nova requires UPLOADING the
master to a third party and holding an API key, and neither is necessary any
more for the ordinary case. nova remains the fallback, so an estate outage
costs speed and privacy rather than the ability to solve.

pipeline/astrometry_net.py is the client and nothing more: stdlib-only POST,
parses the returned .wcs so the full TAN solution is used rather than a
re-derivation from the summary numbers. It deliberately does NOT decide whether
to trust a solution - blind.py already verifies every blind solve against Gaia
and applies a star-count and residual gate, and two gates that can disagree is
worse than one that is trusted.

The source ('estate' or 'nova') is threaded through the log lines, the returned
method, and the ASTRSOLV card, because a year from now that card is the only
way to tell whether a frame was solved in-house or uploaded.

Also corrected astrometry.py's 'cannot solve without a blind solver' message,
which has been untrue since run.py started falling through to blind.py.

Tested against the live service: blind solve of a DSS2 field with a known
centre returned within ~4 arcsec in 0.7s; the WCS round-trips through astropy;
and an unreachable service falls through to nova instead of raising.
2026-07-21 23:00:23 +01:00
2f268c7ca9 Add blind plate solving as the fallback when the seeded solver cannot match
The seeded solver starts from the header's pointing, scale and roll
angle and matches against Gaia. That works whenever the image and the
catalogue hold recognisably the same stars, and fails when they do not: a
single 60 second narrowband frame over a four degree field records a
sparse, shallow population that overlaps poorly with any magnitude slice
of Gaia, and no amount of window sliding fixes a population mismatch.

A blind solver does not care about any of that. It builds geometric
hashes from the image's own stars and looks them up in pre-built indexes,
so it needs no pointing, no scale and no orientation - only pixels.
blind.py drives nova.astrometry.net through astroquery, passing the plate
scale as a hint where one is known, which turns a search over every
possible scale into a search over one.

Two properties worth stating plainly. It needs a free API key, and
without one it says so clearly and the pipeline continues unsolved rather
than failing. And it uploads the image to a third-party service, which is
fine for these targets but is a fact worth knowing before pointing it at
something unpublished.

The result is verified rather than trusted. Whatever the service returns
is matched back against Gaia locally and put through the same gate as the
seeded solver - 40 stars and 1.5 px - so a solution has to be good, not
merely returned. A wrong WCS remains worse than no WCS whoever produced
it.
2026-07-21 22:47:29 +01:00
3c4ae5c77e Solve by pointing where the header allows it, and refuse doubtful solutions
Three fixes and one refusal, all found by working on the wide-field
narrowband session that would not solve.

The detection floor was a fixed 12 pixels, which quietly assumed a well
sampled star. At 0.53 arcsec/px with 5.9 px seeing that is right; at 3.5
arcsec/px the stars are undersampled at 1.75 px FWHM and cover a handful
of pixels each, so the floor discarded nearly every real star and kept
blends and galaxies instead. It now scales with the measured seeing, and
that session went from 305 usable detections to 600.

Matching now happens over the frame's inscribed circle rather than a cone
reaching its corners. On a 4 degree field the old cone covered 33 square
degrees of sky against 16 of image, so half the catalogue was not in the
picture at all.

Where the header records a roll angle - and iTelescope's does - the
orientation no longer has to be recovered from scratch. Rotation, scale
and parity are applied directly and only the residual pointing error is
searched, by histogramming every detection-to-catalogue offset and taking
the peak. Asterism matching remains as the fallback for headers that say
nothing about orientation.

The refusal matters most. With catalogue depth slices added, the wide
field produced a "solution" of 25 stars at 2.50 px residual, claiming a
centre half a degree from the pointing. A genuine solve on that field
matches hundreds of stars and refines to well under a pixel. Accepting it
would have silently corrupted every position in the science catalogue, so
a solution must now reach 40 stars AND 1.5 px or it is discarded and the
session is reported as unsolved. A wrong WCS is worse than no WCS.

NGC 2030 improves from 0.31 to 0.12 arcsec on 123 stars under the
pointing-based match. NGC 2070 is honestly unsolved.
2026-07-21 22:41:36 +01:00
3eb5ab6a03 Produce the four deliverable images and the science outputs for every session
The four-image set worked out on Centaurus A is now generated for any
target, because its value is the comparison: the same data at four levels
of treatment, so a viewer can see what processing did and did not add.
colour.py's assembly is driven by flags - gradient, neutralise, denoise,
saturation, hdr, protect-compact - so the baseline and the fully
corrected version come from ONE code path and the only differences
between them are the ones named.

Image 3 applies what the measurements justify rather than a house style.
Each item is there because measuring the first session caught the
conventional version getting something wrong: a plane fit that had
absorbed 17.9 ADU/px of galaxy halo, a core flattened by a white point
set by field stars, deconvolution ringing around every bright star, and
denoising erasing faint compact sources that turned out to be globular
clusters. Compact sources are now explicitly protected from smoothing -
2374 of them on NGC 2030.

The close-up revealed a real design error, caught by its own assertion.
Forcing a square crop cannot contain a target wider than the frame is
tall, which is the normal case for a nebula in a wide field, and the
assertion fired rather than silently cutting the subject in half. Crops
are no longer square, and when a target genuinely fills the field the
close-up is skipped with that said plainly - re-saving image 3 under a
name claiming to be a close-up would be worse than producing nothing.

science.py adds the measurements that generalise to any target:
photometric calibration from the field's own Gaia stars, the limiting
magnitude actually reached, a source catalogue with calibrated
magnitudes, an annotated field placed by the plate solution, and a radial
surface-brightness profile. Object-specific analyses stay hand-driven,
because a cluster survey suits a galaxy and is meaningless for a nebula.

All of it depends on astrometry, so an unsolved session gets no science
and says so instead of quietly producing less. NGC 2030 calibrates to a
zero point of 24.794 with 0.202 mag scatter on 917 stars, 3470 sources,
limiting G of 18.2.
2026-07-21 22:18:35 +01:00
38f7a81395 Add the report generator and the push-button entry point
run.py is the one command the whole exercise was aimed at:

    python run.py --all PATH

It finds every session beneath a directory and takes each through
ingest, measure, register, solve, colour and report. Stages are skipped
when their output already exists, so an interrupted run continues rather
than restarting, and --force overrides that.

A failure in one stage of one session does not stop the others. In a
batch of twenty the useful outcome is nineteen results and one clear
error, not nothing - so failures are caught, recorded and summarised at
the end, and the plate solve in particular is allowed to fail without
taking the images down with it. An unsolved session still produces a
perfectly good picture; it just cannot produce positions.

report.py writes each session its own METHODS.md, generated from what
the stages actually returned rather than from what they were supposed to
return. That matters for the honest parts: a session whose plate solve
failed says so in its own documentation, and the limitations section is
derived from the data - too few frames for outlier rejection, an
integration too short to recover, a missing luminance channel, seeing
that caps the achievable detail. A hand-written note is written once and
then rots; this is rebuilt on every run.
2026-07-21 21:40:50 +01:00
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
4ed23cef96 Add the colour stage, palette chosen from the filters present
colour.py assembles a viewable image from whatever a session actually
has: LRGB, RGB with a synthetic luminance, SHO in the Hubble palette,
HOO, or a single filter as greyscale. Nothing is assumed about which
filters exist, which is the whole point - three of the four archived
sessions have no luminance channel.

The background fit takes the lesson from Centaurus A, where a plane
fitted around a large galaxy absorbed 17.9 ADU/px of its halo. The
excluded region is now sized from the data: the ellipse grows until it
contains most of the flux above sky, so it suits a galaxy filling the
frame and a small nebula equally, and only a plane is ever fitted, never
a flexible surface. One mask is derived from the deepest channel and
applied to all of them, so the fit cannot shift the colour balance.

Broadband and narrowband need opposite stretches, which cost a round to
discover. Broadband channels share the luminance's midtone, preserving
the real brightness ratios that keep star colours honest. Narrowband
cannot: the lines differ enormously in strength, so a shared stretch
renders the entire nebula in whichever colour Ha was mapped to. Each
narrowband channel is now stretched to its own sky target instead.

Green suppression is applied to broadband only. On a narrowband palette
it would fight the palette, green being a deliberate channel assignment
rather than an artefact.

Measured rather than eyeballed, because the SHO render looked wrong by
eye and was not: both palettes put the sky at 0.098 against a 0.10
target and neutral to within 0.004. What differs is the data. NGC 2030's
brightest pixels reach 0.80 to 0.87, the Tarantula's only 0.23 to 0.33,
because that session is a single 60 second frame per filter - three
minutes in total. A correct pipeline renders nearly empty data as a
nearly empty frame.
2026-07-21 19:44:02 +01:00
0aa37cc503 Add the measure and register stages, generic across sessions
measure.py produces the numbers used to choose a registration reference
and to weight the stack - sky, noise, seeing - and caches each frame's
star list, because registration and the plate solve both need it and
detection costs far more than reading a small array back. Frames are
opened one at a time; a 4096x4096 float32 frame is 67 MB and a session
holds ninety of them.

register.py aligns everything onto a single reference and combines per
filter. One reference for ALL filters, not one per filter, which is what
makes the masters pixel-aligned so the colour composite needs no further
registration. The reference is the sharpest frame of the filter with the
most signal, because the reference sets the output grid and a poor choice
costs resolution everywhere, permanently.

The case worth the care is NGC 6744: luminance at bin1 4096x4096 and
colour at bin2 2048x2048, so frames share neither shape nor pixel scale.
Asterism matching already returns a similarity transform including scale,
so the maths was never the problem; the trap is assuming the warp output
is the same shape as its input, which would write a bin2 frame onto a
bin2 grid that silently fails to line up with a bin1 reference. Every
warp is now given the reference shape explicitly.

Verified rather than assumed: after stacking, the upsampled bin2 colour
masters align to the bin1 luminance master to within 0.04 pixels, across
about 245 matched stars per channel.

Sigma clipping is skipped below three frames, where there is nothing to
reject against and clipping would only discard signal - which matters
because one session has a single frame per filter.

Tested on three sessions covering the awkward shapes: RGB without
luminance, SHO with one frame per filter, and the mixed bin1/bin2 LRGB.
2026-07-21 19:39:30 +01:00
f32411970f Merge pull request 'Generic pipeline stage 1, and state files a cold session can resume from' (#1) from generic-pipeline into main 2026-07-21 17:32:27 +01:00
3f785136ed Bring the state files up to date so a cold session can resume
Rule 2 of the workflow is that state lives in the repo rather than the
chat, and a long session had put most of the current position only in
the chat. This writes it down.

TODO.md gains a RESUME HERE block at the top, because the useful
question for the next session is not what has been done but what to do
next and what is blocking it. It names the three live strands, the three
decisions waiting on the user, and the one thing that will break the
batch run if it is forgotten: NGC 6744 mixes bin1 luminance at 4096x4096
with bin2 colour at 2048x2048, so registration has to resample onto a
common grid. Nothing else in the archive does that, which is exactly why
it will be forgotten. It also drops three duplicate "Done (recent)"
headings that had accumulated, and tabulates what the five archived
sessions actually contain.

NOTES.md gains the gotchas, all of which produced plausible wrong
answers rather than errors: every frame is delivered twice as calibrated
and raw, so extracting both silently doubles the stack; an interrupted
segmented download leaves DIRECTORIES named like files, which defeats
anything that trusts an extension; a re-mirror duplicates rather than
replaces; two T20 archives are genuinely corrupt; extraction triples the
disk footprint and filled the drive. Alongside them the analysis lessons
from NGC 5128, kept for the same reason - each is a way to be confidently
wrong.

DECISIONS.md records why the pipeline reads headers rather than
filenames, and why imaging is being made push-button while the science
stays hand-driven.

ARCHITECTURE.md described a documentation-only repository with no code,
which stopped being true when the pipeline merged in. It now covers all
three parts, the code-and-data separation the pipeline is built around,
and the on-disk session layout.

Both of this session's own errors are recorded in TODO.md as things not
to repeat: measuring a foreground star instead of the galaxy nucleus and
booking telescope time to fix it, and telling the user NGC 6744 had not
been processed when it had.
2026-07-21 17:32:00 +01:00
015bff47fc Add session discovery and ingest, driven by headers not filenames
First stage of the generic pipeline. The existing scripts parse
filenames, expecting calibrated-T32-...-Luminance-BIN2-W-300-001.fit,
which works for exactly one telescope, one binning and one exposure
length. Three of the four archived sessions have no luminance channel at
all, so that approach cannot process them.

session.py opens each frame and reads FILTER, EXPTIME, XBINNING, OBJECT
and DATE-OBS instead. It groups frames by filter, works out which colour
scheme the session supports (LRGB, RGB, SHO, HOO or mono), and writes a
manifest. Filter aliases are mapped, so a header saying H-alpha, Halpha
or HA all resolve to Ha.

Three real defects were found by running it against the archive rather
than by reasoning about it:

Frame counts came out doubled. iTelescope ships every frame twice, as
'calibrated-*' with bias, dark and flat applied and 'raw-*' without, and
extracting both put two copies of each frame into the stack. Only the
calibrated archives are extracted now, with CALSTAT used as a header
level safety net for sessions that were already extracted the old way.
NGC 5128 now scans as 12/4/4/4, exactly matching the session processed
by hand, which makes it a usable regression test.

A re-mirror over the organised tree produced duplicates and mirror
damage. An interrupted segmented download leaves DIRECTORIES named after
the file being fetched - a directory called something.fit - plus
.cyberducksegment parts. Anything that trusts a file extension will try
to open a directory as a frame. Those are now skipped explicitly at
discovery, ingest and scan, and byte-identical copies that a re-mirror
puts back are removed as they are refiled.

Two T20 archives are corrupt and cannot be extracted. That is real data
loss, not a bug: the session has 14 of its 16 frames and the warning is
printed rather than swallowed.

Not yet done: NGC 6744 mixes bin1 luminance at 4096x4096 with bin2
colour at 2048x2048, so registration will need to resample onto a common
grid rather than assuming every frame shares a shape.
2026-07-21 17:28:26 +01:00
59d6e30712 Make the merged repository coherent: README, state, and internal links
The mechanical merge preserved both histories but left two repositories
sitting side by side rather than one repository. This is the part that
makes it whole.

A top-level README explains the three strands - the telescope network
and campaign, the processing code, and in-person observing plans - and
says plainly that image data does not live here, because that is the
first question anyone will have when they find a pipeline with no
pixels.

state/PROJECT.md was still describing a markdown-only reference project
whose scope explicitly EXCLUDED automating bookings and image
processing. Both of those are now most of what the project does, so the
objective, scope and key facts are rewritten to match reality.

state/DECISIONS.md records why the merge happened - the two repos were
split by chronology rather than design, and the seam was already
leaking, with the campaign's TODO citing results held in the other repo
and the pipeline's README explaining a campaign it did not contain. It
also records that the image data deliberately stays out of the
repository.

state/TODO.md gains the pipeline and observing work that previously had
nowhere to live, including the measurement that changes the pipeline
plan: per-frame processing is 2.9 seconds and the whole 24-frame session
is 1.1 minutes single-threaded, so a scheduler earns nothing on stacking
and should be pointed at the Monte Carlo stages instead.

Internal links fixed for the new paths: pipeline/README.md referred to
session-scripts/ throughout, and itelescope/README.md pointed at a
state/ directory that is now one level up.

The .gitignore conflict between the two repositories is resolved by
combining them, with image formats ignored globally except under docs/,
where documentation figures are committed deliberately.
2026-07-21 17:16:20 +01:00
87387064c5 Merge the astro-pipeline repository, history intact
# Conflicts:
#	.gitignore
2026-07-21 17:14:28 +01:00
bd8842b3c0 Merge the itelescope repository, history intact 2026-07-21 17:14:06 +01:00
b47f7a1e75 Initial commit for the combined astrophotography repository
Empty root commit so both source repositories can be merged in as
unrelated histories without either becoming the arbitrary 'first' one.
2026-07-21 17:14:06 +01:00
c6299f41ab 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.
2026-07-21 17:13:54 +01:00
a0e458b429 Move the iTelescope network reference and campaign under itelescope/
Preparing to merge this repository into a combined astrophotography
repo. The network review, the southern-target guide, the drain campaign
and the observing plans all belong together under one directory; the
Default Workflow files (CLAUDE.md, docs/, state/) stay at the top level
because they will govern the combined repository rather than just this
part of it.
2026-07-21 17:13:47 +01:00
653ca103cd Add the field plan for the 12 August 2026 total eclipse from Menorca
Menorca sits inside the path of totality, but the geometry is extreme
and it drives every decision in the plan: the Sun is 2 degrees above
the horizon at maximum, on azimuth 288, and totality lasts 1 minute
12 seconds at Ciutadella before the Sun sets at 20:42.

Two consequences follow from that 2 degrees. The light travels through
roughly twenty times more atmosphere than at the zenith, so everything
is dimmer, softer and redder than any published exposure table assumes
and the histogram has to be the authority rather than a chart. And a low
hill, a building or a haze bank on the sea horizon hides the entire
event, so an unobstructed west-north-west view matters more than
anything else about the site.

That produces a genuine trade-off worth recording. Totality runs 1m38s
at Mahon against 1m12s at Ciutadella, because the centre line favours
the south-east - but Mahon is on the east coast, so from there the view
towards 288 degrees crosses the island with Monte Toro in roughly that
direction. Horizon beats duration: 26 extra seconds is a bonus, a
blocked horizon is total loss. The plan therefore recommends a west or
south-west coastal site.

The plan also corrects an idea that was on the table: a photographic ND
filter is not a solar filter. Standard NDs cut visible light while
passing infrared largely unattenuated, which is where the heat is, and
that will damage a sensor. The requirement is a certified ISO 12312-2
filter around OD 5 on the front of the lens, with the specific warning
that Baader's AstroSolar Photo Film ND 3.8 is camera-only and unsafe to
look through, while the ND 5.0 safety film does both jobs.

The Star Adventurer GTi is explicitly excluded from eclipse day.
Seventy-two seconds needs no tracking, and polar aligning in bright
twilight towards a 2 degree Sun is impractical. It earns its place on
the night skies of the same trip, not on the 12th.

Kept in this repo rather than in a notes app because it is worked out
from real numbers, it will be revised as the date approaches, and the
reasoning behind each decision is the part worth keeping.
2026-07-21 17:10:01 +01:00
f2d173f047 Fix directory resolution in layout, and generate the session READMEs
layout.path() treated every argument as a filename, so asking it for a
directory - layout.path('final'), which the rendering scripts do - built
a path to a file called 'final' INSIDE final/ and created an empty
final/final along the way. Directory names now resolve to the directory
itself.

Three scripts also held path literals split across two lines with
implicit string concatenation, where the rewrite had replaced only the
first fragment and left a dangling continuation. verify_core.py,
mo_gccheck.py and mo_mpc.py are corrected; all 52 files now compile
without warnings.

subdir_readmes.py writes a short README into each session subdirectory
saying what it holds, where it came from, and whether it is safe to
delete. Those three facts are what someone opening a folder cold needs,
and they belong with the data rather than in this repo.

Re-verified after the fix: closeup.py and triptych.py run end to end
against the reorganised session and leave no stray directories behind.
2026-07-21 15:33:52 +01:00
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
de37a19ee6 Merge pull request 'Correct the saturated-nucleus claim and cancel the booking it justified' (#17) from correct-core-saturation into main 2026-07-21 14:51:06 +01:00
7dc6119359 Correct the saturated-nucleus claim and cancel the booking it justified
The previous commit asserted that the 21 Jul Centaurus A data came back
with a saturated nucleus, and booked a short-exposure core set on T32 to
fix it. The assertion was wrong.

It came from taking the maximum pixel inside a 300 x 300 px box centred
on the frame. That measures the brightest thing near the middle of the
image, which is not the same as measuring the galaxy. The 65313 ADU
belonged to a foreground star 128 px (69 arcsec) from the nucleus, well
inside the box. Median filtering the inner 800 x 800 px to remove stars
leaves the galaxy peaking at 1944 ADU, roughly a thirtieth of the clip
level, with not one star-free pixel above 30000 ADU anywhere in that box.
The brightest pixel in the whole frame is at the right-hand edge, 2370 px
from centre.

The core looked blown in the processed image because of the stretch, not
the sensor: the white point sat at the 99.995th percentile, a level set
by field stars, so the galaxy's entire range from a 7 ADU sky to a 1944
ADU peak was compressed into the top few percent of the tone curve.
Re-rendering the same frames with a second curve scaled to the galaxy
recovered the core completely, with no new data.

The T32 reservation has been cancelled without running, so it cost
nothing. The other three reservations were verified intact afterwards
and quota is back to 6:05 of 8:00. plans/hdrcore-t32.txt stays in the
repo and on the scope: 47 Tuc and the SMC may genuinely saturate, a
dense cluster core being the fastest thing there is to clip, and the
ladder is ready if they do.

The corrected habit is recorded in CAMPAIGN.md: measure whether a core is
saturated before buying time to fix it, filtering stars out of the
measurement first. The check takes a minute and would have saved this
whole detour.

TODO.md also records a second result that affects future planning: the
faint outer halo of that stack is limited by the sky-plane subtraction,
which absorbed about 17.9 ADU/px of real halo light, rather than by
exposure time. More integration alone would not have gone deeper.
2026-07-21 14:50:38 +01:00
57e999a669 Merge pull request 'Book a short-exposure core set for Cen A, and make it a campaign habit' (#16) from hdr-core-booking into main 2026-07-21 13:42:18 +01:00
7bf834a184 Book a short-exposure core set for Cen A, and make it a campaign habit
Processing the 21 Jul Centaurus A data showed the nucleus is saturated: a
single 300 s luminance sub reads 65313 ADU in the core, so the deep stack
is clipped there and the core cannot be recovered by processing. The only
fix is more data at shorter exposures.

plans/hdrcore-t32.txt is that data: L 8x60s, 8x15s and 8x5s plus RGB
5x30s, all BIN2 so the frames register against the deep stack without
rescaling. The ladder spans a factor of 60 below the 300 s subs, which
covers the unknown true peak of the nucleus, and the RGB gives the core
its colour. 18.2 minutes of imaging, about 25 points.

Uploaded to T32 and verified byte-identical, then booked for the evening
of 22 Jul, 18:50-19:50 SSO local, which is the same window in which the
deep run actually succeeded (T32 has a 35 degree elevation floor and Cen A
is an evening target). Verified in MySummary; quota now 7:05 of 8:00.

The wider point is recorded in CAMPAIGN.md: bright targets need a short
set budgeted alongside the deep run. NGC 6744 has already been shot
without one, and 47 Tuc and the SMC in queue #5 will certainly need one,
a globular cluster core being the fastest thing there is to saturate.
2026-07-21 13:41:23 +01:00
6ba6024192 Merge pull request 'Verify Cen A, book CrA on T8 for the 22 Jul evening, rebuild the rate model' (#15) from campaign-ledger-2026-07-21 into main 2026-07-21 12:44:39 +01:00
cd3de71361 Verify Cen A, book CrA on T8 for 22 Jul evening, rebuild the rate model
Cen A (reservation 749302, T32) completed at 11:16 UTC, 34 minutes early
because the plan finished, not because of a fault: NGC5128 images are on
t32.itelescope.net:8032 and transaction 851344 billed 160 points. Balance
is now 2,097 of the original 2,638.

That second bill settles the billing question the 20 Jul note left open.
T32 billed 160 for a 120 min plan (2.00 h x 80) and T59 billed 381 for a
180 min plan (3.00 h x 127); both land exactly on the plan's exposure
total and neither lands on wall-clock time. So billing is per imaging
minute as originally documented, and the x1.6 wall-clock multiplier is
withdrawn from CAMPAIGN.md, TODO.md and NOTES.md. What is actually wrong
is the rate card's per-scope numbers, so each scope's rate now has to be
derived from its first real bill. The practical consequence is the
opposite of the old warning: the campaign is now heading to UNDER-spend
by roughly 800 points, so later planning sessions should add or lengthen
runs rather than trim them.

Queue #2 (Corona Australis on T8) is booked at last. The original
01:35-04:35 slot was confirmed dead by probing New.aspx, which answered
"Conflicting reservation. Start time must be later than 2:25 AM" (user
hajo's reservation 749207, exactly as the previous session predicted).
Rather than take the leftover 02:30-05:30, where the target sits 3.5 to
6.5 hours past the meridian, the run moved to the next night's evening,
2026-07-22 21:45 to 2026-07-23 00:45 SSO local, which brackets the ~22:30
culmination so all 115 minutes of imaging happen near the zenith. This is
the geometry of the previously rejected option (c) on the night of option
(b): (c) was only rejected because Cen A held the reservation quota, and
that expired when Cen A completed. Verified via Reservation/MySummary.aspx;
quota now 6:50 of 8:00.

The New.aspx probe is documented in plans/README.md as the way to test a
slot for conflicts, since the DayPilot grid renders client-side and shows
nothing to a plain HTTP fetch.
2026-07-21 12:43:11 +01:00
f94ecce678 Merge pull request 'Book free T33 47 Tuc retry (749314); defer cra-t8 on quota + hajo conflict' (#14) from book-t33-retry-20260721 into main 2026-07-21 09:52:49 +01:00
f49e045bb6 Book free T33 47 Tuc retry (749314); defer cra-t8 on quota + hajo conflict
- Session ran early (08:48 UTC, before Cen A 749302's 08:50 UTC start), so this
  round did only the time-sensitive, unblocked work.
- Free T33 47 Tuc retry booked and grid-verified as reservation 749314
  (night of 21 Jul SSO, 00:05-00:50, runs 14:05 UTC). Third attempt: the two
  prior free reservations expired unused, so if this one also fails to run the
  issue is free-scope auto-start, not the booking.
- Cen A (749302) verify deferred: had not run yet; runs 08:50-11:50 UTC.
- cra-t8 (queue #2) deferred, blocked twice: quota held by Cen A until ~11:50
  UTC, AND user hajo holds T8 00:25-02:25 SSO local that night (res 749207),
  overlapping the planned 01:35-04:35 slot. Documented resolution options;
  preferred is slipping cra-t8 one night to 2026-07-23T01:35 to dodge the
  conflict and keep Corona Australis near culmination.
- Scheduled job ae3af497 (~12:13 UTC) will verify Cen A and book cra-t8 against
  a live grid re-check.
2026-07-21 09:52:35 +01:00
0e246f9dda Merge pull request 'Rebook Cen A on T32: evening reservation 749302' (#13) from rebook-cena-t32 into main 2026-07-20 21:57:13 +01:00
bd31a8a6fb Rebook Cen A on T32 as evening reservation 749302; document quota-driven booking order
- Reservation 749302 created and grid-verified: T32, night of 21 Jul SSO,
  18:50-21:50 local (08:50-11:50 UTC 21 Jul), plan cena-t32.txt, est ~350 pts
  at the corrected x1.6 rate with 25% moon discount (46% moon ~30 deg away).
- The original queue slot (00:35-03:35) was an astronomy error: T32 has a
  35 degree elevation floor and Cen A culminates ~17:10 SSO local in late July,
  sinking to ~11 degrees by midnight. Evening is the only viable window; noted
  in the ledger so the mistake is not repeated.
- Reschedule flag confirmed to never stick (second booking posting
  RescheduleCheckbox=on that renders as No in the grid): every reservation is
  one-shot, verify the morning after.
- Quota math recorded: 5h20m of 8h held; queue #2 (cra-t8) plus the free T33
  47 Tuc retry must wait for the Cen A run to release its 3h (~11:50 UTC
  21 Jul). Full remaining queue at corrected rates ~2,530 vs 2,257 balance:
  Phase 3 needs a ~270-point trim once real bills confirm the rate model.
2026-07-20 21:56:56 +01:00
fb5f17a8e1 Merge pull request 'Session outcomes 19-20 Jul: T59 NGC 6744 success, rate correction, T33 misses' (#12) from session-outcome-20260720 into main 2026-07-20 21:30:39 +01:00
07713c2f13 Record 19-20 Jul session outcomes: T59 success (381 pts), T33 free runs missed
- T59 NGC 6744 (reservation 749227) ran 00:05-03:30 SSO local 20 Jul and billed
  381 points (txn 851148), not the ~240 estimated. Full LRGB dataset verified on
  the scope server (L 15x300s BIN1, R/G/B 7x300s each BIN2); preview JPEGs
  checked: round stars, clean galaxy, no weather damage, so no refund claim.
- Rate correction in state/NOTES.md: history 'Time Used' equals wall-clock
  session time (3h24m32s), and the effective rate was ~112 pts/hr wall /
  ~127 pts/hr exposure vs the 80/hr the UpdatePlan rate card predicted. All
  existing queue estimates flagged ~1.6x low until more bills confirm the model.
- Both free T33 reservations (749228 47 Tuc, 749229 NGC 6752) expired unused:
  no images, no logs; grid shows reschedule=No despite the flag being set at
  booking. First-light test is still outstanding; plans remain uploaded on T33.
- Ledger, booked-reservations table and queue updated: balance verified 2,257
  via GetAccountStatus; queue #1 (Cen A T32) missed its 21 Jul slot and needs a
  new night.
2026-07-20 21:30:11 +01:00
67f8455e41 Merge pull request 'Campaign plan files' (#11) from campaign-plans into main 2026-07-18 23:25:56 +01:00
512cdd3074 Campaign plan files for every queued session
The eight ACP plans backing the drain-campaign booking queue: cena-t32
(Centaurus A LRGB), cra-t8 (Corona Australis dust), catspaw-t8 (NGC 6334
Ha, booked as 749230), smc47tuc-t8 (the SMC + 47 Tuc signature frame),
tarantula-t32 (NGC 2070 LRGB), ngc6188-haoiii-t59 and ngc6188-sii-t59
(bright-moon narrowband pair), scorpius-t70 (super-wide, pending the T70
server returning). All except scorpius-t70 are uploaded to their scopes
and verified byte-identical; keeping them in the repo lets any session
re-upload after scope-side maintenance wipes or for T71 substitution
re-cuts. Binning conventions: Bin1 L / Bin2 RGB on the CCD scopes (T8),
Bin2 throughout on T32 (camera is Bin2-only), Bin1 on modern CMOS (T70),
Bin1 narrowband on T59 with 600 s subs.
2026-07-18 23:25:54 +01:00
6666de5e27 Merge pull request 'Booking queue and quota handling' (#10) from booking-queue into main 2026-07-18 23:25:23 +01:00
a091e38bc4 Booking queue: full campaign reservations attempted, quota documented
All seven remaining campaign sessions were attempted against
reservations.itelescope.net in one pass. The rolling reservation allowance
(8 hours outstanding on Plan-40) admitted only the Cat's Paw Ha session
(749230, T8, night of 26 Jul, verified via Edit.aspx?id= since the grid
cannot page that far forward); the other six returned 'quota.' and are
recorded as a ready-to-fire queue with exact New.aspx parameters. All plans
are uploaded and verified on T8/T32/T59; T70's plan could not be uploaded
(scope server unreachable, consistent with the Chile outage) and its entry
is blocked pending the server returning.

Substitution rules recorded: T71, if it returns, takes the widefield
sessions (better optics, lower rate); T70 books as-is if reachable; status
line checked before every booking round. Booking cadence: a daily job (or
manual 'process the booking queue') books queue items in order as completed
sessions release quota.
2026-07-18 23:25:22 +01:00
36cc26d7fa Merge pull request 'Reservations created and reservation system documented' (#9) from reservations into main 2026-07-18 23:04:27 +01:00
9003d672ff Reservations created for all three sessions; mechanics documented
The Reservations Pilot app at reservations.itelescope.net is now mapped and
driven end to end: three-step WebForms flow (form GET, Refresh Plans
postback with plan+times selected which enables Confirm, then Confirm),
observatory-local times, 4-hour maximum, plan attachment causing iTelescope
to auto-start the run at reservation start, and the reschedule flag that
rebooks weather losses on the next free night. Crucial gotcha recorded: the
confirm modal returns OK even when nothing was created (first attempt
silently failed on the 4-hour rule), so verification against the grid is
mandatory.

Live reservations: 749227 (T59, NGC 6744, 00:05-03:45 SSO local 20 Jul),
749228 (T33, 47 Tuc test, 00:05-00:50 same night), 749229 (T33, NGC 6752,
00:05-00:50 following night). Ledger corrected: T59 moon discount is 0%
until 21 Jul (the earlier 25% figure came from a Utah scope's grid), so the
NGC 6744 estimate returns to ~240 pts. Monitoring crons replace the old
starter crons since the scheduler now fires the plans itself.
2026-07-18 23:04:26 +01:00
1846125907 Merge pull request 'T59 window fix: reservation collision and moon discount' (#8) from t59-window-fix into main 2026-07-18 22:56:37 +01:00
c1d353ca10 T59 window fix: third-party reservation constrains the NGC 6744 run
The booking grid (Reservation/Overview.aspx embeds the DayPilot events)
shows astrosharp holding T59 from 03:50 to 04:40 SSO local on 20 Jul, i.e.
17:50-19:40 UTC on 19 Jul, which collided with the original 15:33 UTC
walk-in start. The run is rescheduled to start ~14:15 UTC (parallel with
the free T33 test, different scope) so it completes by 17:45 UTC, with a
trim-or-skip rule if the start slips. The grid also confirms a 25% moon
discount in effect, so the cost estimate drops from ~240 to ~185 points;
ledger updated. Times in the embedded grid are per-observatory local time
(verified against Utah sunset and the first-quarter moon note).
2026-07-18 22:56:36 +01:00
09e2d95358 Merge pull request 'Drain campaign: spend the balance by 10 Aug, then close' (#7) from drain-campaign into main 2026-07-18 22:41:35 +01:00
08fef91b57 Drain campaign: spend the balance by 10 Aug, then close the account
User decision 2026-07-18: use the full 2,638-point balance on southern
imaging, then cancel the membership before the 12 Aug renewal (GBP 32).
CAMPAIGN.md holds the plan: policy facts that force the order (points are
non-refundable and unusable without an active subscription, per support
article 142963), a three-phase allocation shaped by the moon (dark-night
LRGB now and 3-10 Aug, narrowband through the bright 24 Jul - 2 Aug week),
a per-session ledger to reconcile against account/history.aspx, the 10 Aug
data-download-then-cancel endgame, and the rule that the cancellation POST
itself is never submitted without explicit user confirmation on the day.

plans/ngc6744-t59.txt is the first paid run (T59, L 15x300 Bin1 + RGB
7x300 Bin2, ~180 min imaging, ~240 pts), already uploaded to T59 and
verified byte-identical. PROJECT.md and TODO.md updated to make the drain
the active objective for any future session.
2026-07-18 22:41:33 +01:00
3e9f4e5cff Merge pull request 'Rate card: Plan-40 per-scope points rates' (#6) from rate-card into main 2026-07-18 22:32:31 +01:00
c45d87c9d0 Rate card: per-scope Plan-40 points rates extracted from the portal
The UpdatePlan page embeds each telescope's dark-of-moon base rate in a
data-default-rate attribute; effective rate = base x (1 - plan
AccessDiscount from DataService.svc/GetMyAvailablePlans) x (1 - moon
discount from a server-side illumination table, not yet extracted). Records
the computed Plan-40 rates for all 25 listed scopes, the billing model
(points per imaging hour, billed per minute of exposure time, overhead
unbilled), the walk-in rule (idle scopes need no reservation; the ACP flow
is identical to the free scopes with points deducting), and the Cloudflare
constraint on the reservation planner.
2026-07-18 22:32:29 +01:00
e87c918f45 Merge pull request 'Second free-session plan: NGC 6752' (#5) from ngc6752-plan into main 2026-07-18 22:18:28 +01:00
ab184a7dcc Second free-session plan: NGC 6752 (Pavo globular)
plans/ngc6752.txt mirrors the 47 Tuc test recipe (L 4x120s, RGB 2x90s, Bin2,
~24 min all-in, inside the free daily 30 minutes) on NGC 6752 at RA 19.1811h
dec -59.9844: at RA ~19h it is well placed from mid-evening at Siding Spring
through the whole run window. Uploaded to T33's /plans/qisback/ and verified
byte-identical.

Scheduled for Sunday 2026-07-20 ~15:11 UK as an adaptive follow-up: if
Saturday's 47 Tuc first-light succeeded it images NGC 6752; if Saturday was
clouded out or the scope was contended, it retries the 47 Tuc test instead.
state/TODO.md records the dependency so whichever session fires can decide
from repo state alone.
2026-07-18 22:18:26 +01:00
422b36552c Merge pull request 'T33 first-light prep: 47 Tuc test plan and runbook' (#4) from t33-first-light into main 2026-07-18 22:09:27 +01:00
61aae7dca2 T33 first-light prep: 47 Tuc test plan and session runbook
plans/ngc104-test.txt is the ACP observing plan for the free 30-minute proof
run on T33: NGC 104 / 47 Tucanae, L 4x120s + RGB 2x90s at Bin2 (~17 min
exposure, ~24 min with overhead), modelled on the account's previous
one-click plan format (decimal-hours RA, decimal-degrees dec, #shutdown at
end). Already uploaded to the scope's /plans/qisback/ folder and verified to
round-trip byte-identical.

plans/README.md is the runbook: the T33 ACP endpoint map (status JSON,
console read, scope connect, plan upload/run, logs), the verified upload
procedure, the not-yet-exercised acquire POST with its Windows-path plan
value, the 47 Tuc run window (second half of the SSO night, ~14:00-19:30
UTC), and the step-by-step session procedure including the open questions a
first live run must answer (scope-connect requirement, abort control).

state/TODO.md tracks the test as in progress.
2026-07-18 22:09:24 +01:00
c6aeaa6af8 Merge pull request 'Automation recon: portal endpoints and ACP control servers' (#3) from automation-recon into main 2026-07-18 21:55:54 +01:00