NOTE · 09/26
How the lunar mosaic is made, end to end
Written down properly after the 19 September run, because the method now has enough moving parts that I will not remember which order they go in, or why half of them are the way they are. This is the whole path from a cold tripod to a finished picture.
The rig
A 102 mm f/12.7 Maksutov at 1300 mm on the AZ-GTe, SV205 at prime focus, SV165 guidescope alongside for centring. Measured plate scale 0.222 arcseconds per pixel, which puts a single frame at 12.08' x 7.99' — about a third of the Moon's width. That is the whole reason a mosaic exists. The camera runs over USB 3 at SuperSpeed, uncompressed YUYV at the full 3264 x 2160 and fifteen frames a second; MJPEG was only ever a workaround for a cable that forced the link to USB 2.0, and JPEG ringing survives stacking.
Alignment, and its one great trap
The mount is homed with the tube level and pointed north, and everything downstream reads
raw encoder counts relative to that physical home — :j1 and :j2 decoded as degrees,
deliberately bypassing the firmware's own pointing model. That independence is what makes
the motion path safe. It also has a sharp edge: because the script never consults the model,
a sync or alignment in the SynScan app cannot correct anything it sees. Home set to
magnetic rather than true north bakes Goolwa's ~8 degrees of declination into every axis
reading, permanently. The only fixes are to re-home on true north — compass 352, not 0 — or
to declare the offset with --az-frame-offset.
The handover
The script never points from the ephemeris alone, because the axis counts carry that
residual. Instead I centre the Moon on the SV165 crosshair and hand over. The script reads
the axes, computes residual = axes - moon, and holds it constant for the whole run. Every
panel then targets moon_live + grid_offset + residual, moving by the difference from the
current reading. Absolute ephemeris, relative motion — the Moon's drift across the run
and the alignment error are both absorbed without either being modelled. A --shift
corrects for the crosshair sitting about 11' off the Mak's axis.
Three gates must pass before anything moves: Moon below 75 degrees, centring residual under 2.5 degrees, and a camera frame proving the telescope is on sunlit ground rather than black sky. That last one is phase-aware now — at 55% illumination a disc-centred frame reads 50-65% lit no matter how good the centring, so it accepts either a mostly-lit frame or unambiguous sunlit ground in view.
Dry runs, and what they cannot tell you
Every run is planned in dry mode first. It commands no motion and takes no exposures, simulates each axis position, checks every move against the step caps and aborts on any gate failure. It catches bad anchors, oversized moves, grid geometry errors.
What it structurally cannot catch is the defect that dominates the result, because that one is created entirely by the sky moving while the shutter is open. A dozen clean dry runs across two nights, and misaligned output every time.
Motion
Gotos go over the raw SynScan motor-controller protocol, not INDI, which sat at Busy for
two nights and never moved the mount. Per axis: stop, low-speed goto mode and direction,
offset in microsteps, ramp, start, poll until the running bit clears, read back. Every
command needs a trailing carriage return. An offset goto is bounded by construction — it
stops at its own target — which is why it is safe where a bad sync plus continuous tracking
was not. Each landing is checked against the encoders and the run aborts if a panel lands
more than 36" off. In practice all 72 panels across two grids landed within one arcsecond.
The serpentine
Across, down one, and back — rather than raster-scanning and returning to the start of each row. A plain raster's row-return is over half a degree; serpentine makes every move a single panel step. Long moves are staged into legs so no single command exceeds 0.40 degrees, and the legs respect the axis cap as well as the sky one: at 58 degrees altitude an azimuth command is 1.9 times the sky distance it represents.
The flaw at the heart of it
Every goto begins with :K, which also stops the firmware's tracking, and nothing restarts
it. So each panel is exposed with the mount standing perfectly still while the Moon runs at
13 arcseconds a second. At 0.222"/px the Moon crosses one pixel every 17 milliseconds. A
30-frame panel takes two seconds and therefore travels 117 pixels. The stacker aligns those
frames to each other so the tile looks sharp, but its centre now sits about 59 px from where
the mount aimed.
Untracked pixel precision is not difficult. It is arithmetically unreachable.
This is also why more frames per panel makes it worse, not better: at 250 frames the drift reaches 977 px, the selected frames span the whole sweep, phase correlation can no longer align them, and the tiles come out smeared. That grid could not even be registered afterwards.
Stacking
Each panel's frames are ranked by Laplacian variance and the sharpest 25% stacked with sub-pixel phase-correlation alignment, giving a linear float array per panel. Everything downstream works from those arrays and never from the per-panel PNGs, which are each stretched to their own percentile range — a stretch that turns sky into full-range noise and hands neighbouring tiles incompatible gains.
Stitching
Since the panels are misplaced, they cannot be laid out where they were commanded. Hugin's
cpfind detects features — actual craters — anywhere in the overlaps, and those control
points are fitted to a pure translation model with iterative outlier rejection. Hugin's
own autooptimiser is deliberately not used: it fits a camera rotating about a nodal point,
and these panels are a near-planar patch at 0.2 degrees a frame, so a spherical solver would
invent a nonsense focal length. A connectivity check refuses the job outright if the control
points leave any panel unlinked, because an unlinked panel's position would be invented
rather than measured.
Compositing assigns each output pixel to the single nearest panel centre, weighted by how well-exposed each panel is, so two tiles can never both draw the same limb arc. A wide feather softens the ownership boundaries, and one gain per panel is solved from the median ratio in each overlap — wide enough to rescue a panel that caught the exposure-reset trap and came out at a quarter brightness.
The result
| image | 7054 x 5213 px (36.8 Mpx) |
| field | 26.1' x 19.3' (1566" x 1157") |
| scale | 0.222 arcsec/pixel |
| subject | Moon, 30.0' diameter, 55.1% illuminated |
| panels | 15 used of 36 captured, 12.08' x 7.99' each |
| frames | 1080 captured (22.8 GB); 450 in the final; ~112 actually stacked |
| capture time | 5 min 13 s |
| goto accuracy | ±1 arcsec, mean error 0.43" |
| registration | 0.63 arcsec median residual, 385 control points over 29 pairs |
| gains | 0.905 – 3.241 |
The honest summary is that the pointing was never the problem. The mount is exact to an arcsecond. The entire difficulty lives in the two seconds after it arrives, when it stands still and the sky does not. Restarting firmware tracking after each goto would collapse the drift to nothing, put the tiles where they were commanded, and demote Hugin from a rescue to a polish.