Polar alignment
Starship includes a plate-solve polar alignment routine that helps you align an equatorial mount's polar axis to the celestial pole. You run it once during setup: it solves one frame, asks you to turn the mount about 80 degrees in RA by hand, solves a second frame, and (by default) asks whether you want to add a third position as a cross-check; from the rotation between those frames it works out where your polar axis really points. It then keeps solving and shows the error live, so you turn the altitude and azimuth knobs and watch the number fall.
Starship never moves the mount. No slew is ever commanded. You do the turning; Starship does the measuring.
The math lives in core/polar.py; the run is driven by a background worker (run_polar_align in core/console/workers.py) started by the console polar_align() command, so it runs as a cancellable job like a capture or a slew.
What it does
The routine has three steps.
Step 1 - solve where you are. Starship exposes a frame and plate-solves it (ASTAP). It records not just the RA/Dec but the roll angle of the frame, recovered from the solver's CD matrix. That roll is what makes the whole method work with only two positions.
Step 2 - you turn the mount. Starship asks you to turn the mount roughly 80 degrees in RA and press I've turned it - continue. The direction does not matter. Nothing else should move - do not touch Dec, and do not touch the alt/az knobs yet.
Step 3 - solve again and fit the axis. Turning in RA rotates the whole camera frame rigidly about the mount's polar axis. So the rotation that carries frame 1 onto frame 2 is a rotation about that axis, and its axis is exactly what we are trying to measure. Starship extracts it, precesses to the equator of date (IAU 1976 - the pole of date sits ~8.7' from the J2000 pole in 2026, far bigger than the alignment target), compares it to the pole, and reports the altitude and azimuth error in arcminutes plus the total on-sky error.
Step 3b - the optional third position (recommended). After position 2 Starship asks Add a third position (recommended) / Continue with two. Two frames have no spare information: the rotation that carries frame 1 onto frame 2 is, by construction, some rotation, so a declination slip during your turn is simply read as polar error - about 1.3' of slip looks like 1' of error at an 80 degree turn - and nothing in the pair can reveal it. A third position gives two independent turns, and the axis measured from each turn (and from the 1-to-3 pair) must agree. Starship reports the disagreement as the spread and refuses the measurement if it exceeds max_spread_arcmin (0.75' by default); below that it combines the three into one axis, weighted by how well each pair pins it down. The minutes between solves are accounted for: the mount axis is bolted to the ground, which turns under the sky, so each solve is carried to the instant of the last one before the axes are compared - a mount a few degrees off the pole would otherwise show a spread of its own and be refused. For the second turn it asks for a further ~60 degrees (anywhere from 40 to 80 is fine) in the same direction as the first, RA only; turning part-way back is refused as a reversal, not reported as spread. Continue with two runs exactly the two-position measurement above.
Then it enters the live readout: it re-solves every adjust_interval_s, advances the stored axis by the elapsed sidereal angle (the mount axis is ground-fixed, so in equatorial coordinates it rotates about the true pole at the sidereal rate), applies the observed displacement constrained to the physical alt/az knob axes, and refreshes the numbers. You turn the knobs; the display updates. The loop ends when the total error drops at or below target_arcmin, the max_adjust_min timeout expires, or you stop it.
By default it aims at the refracted pole (atmospheric refraction lifts the apparent pole by ~1.5' at the latitudes Starship targets), computed with Bennett's formula at the pole's altitude. That is what you want for the most accurate unguided tracking, so leave Apply refraction on.
Why it does not tell you which bolt to turn
It shows you the error and refreshes it continuously instead. Which knob moves which way is mount-specific and easy to get backwards; a number that falls while you turn is unambiguous. If it rises, turn the other way. That is the whole technique.
The readout does say where the axis is - "axis too high", "axis too far east" - because that is a statement about the measurement, not an instruction about your hardware.
Requirements
The worker checks these up front and fails fast with a clear message if any is missing:
- Site latitude/longitude set (Settings -> Mount / Observatory). A site of
exactly 0,0 is treated as unconfigured.
- ASTAP available for plate solving.
- A connected camera.
- Enough stars in the field to solve, in both positions.
Note what is not required: Starship does not need to be able to slew, or even to read RA/Dec, so this works with mounts Starship cannot drive - including a mount on a hand controller with no ASCOM connection at all.
Tracking must be ON during the live readout. Once the axis is measured, Starship attributes any movement of the star field to your knob adjustments. If tracking is off the field drifts on its own, and — this is the dangerous part — the displayed number does not run away. It converges tidily onto your target while the real axis walks off the pole at arcminutes per minute. You would be congratulated while being steered wrong.
So Starship watches the solved frames for the signature of an untracked field (the new pointing landing exactly where a sidereal rotation of the previous one predicts) and stops with an explanation rather than showing you a number it no longer trusts. That check works even with no mount connected, which matters because the hand-controller case is exactly where a Tracking flag is unavailable. If a mount is connected, its Tracking flag is shown too — and if it cannot be read, the panel says that rather than staying quiet.
Tracking during the 80-degree turn itself is harmless; it simply adds to the turn.
Using it
There are two ways in, both driving the same run:
- The dedicated Polar Alignment page (in the left-hand navigation) - a
focused screen with Start polar alignment and Stop buttons, the live readout, and a Configure -> shortcut to the settings below.
- Settings -> Mount -> Polar alignment, where you set the run options and
can also start/stop from the same panel.
Then:
- Point the mount somewhere with usable sky and stars enough to solve. It does
not have to be near the pole.
- Set exposure, binning and the target accuracy. The defaults (4 s, bin 1,
80 degree turn, 1' target) are a sensible start.
- Start polar alignment. It captures and solves position 1, then asks you to
turn the mount.
- Turn the mount about 80 degrees in RA - by hand, by hand controller, by
whatever means you normally use. Turn in RA only: do not touch Dec, and do not touch the alt/az knobs yet. See Why the turn must be pure RA below. Then press I've turned it - continue.
- It solves position 2 and asks Add a third position (recommended) /
Continue with two. (With positions = 2 it does not ask and goes straight to the readout.)
- Turn a further ~60 degrees in the same direction, RA only, and confirm;
it solves position 3 and shows the measured error with the spread between the positions: a plot with the pole at the centre and your axis as a dot, plus altitude, azimuth and total in arcminutes. Continue with two shows the same readout straight away, from the two positions.
- Turn the altitude and azimuth knobs and watch the dot walk toward the centre.
The reading refreshes every adjust_interval_s. A short trail shows which way it is moving.
- When the total reaches your target the job ends on its own with
"within target". Otherwise stop it manually (the Stop button / polar_align_stop() command), or walk away and let the max_adjust_min timeout end it.
If you reload the page mid-run, the Polar Alignment screen and the Settings panel both reconnect to the running job and resume showing its live readout.
Only one polar alignment job runs at a time; starting a second while one is active is rejected.
The plate-solve frames use your camera's plate-solve gain and binning overrides (Settings -> Camera) if you have set them, so pointing/polar solves can run at a different gain or binning from your science frames.
If you turn too little
The axis is recovered from the rotation between the two frames. A small rotation makes that extraction numerically ill-conditioned - the answer becomes mostly solver noise. Rather than report a confident but wrong pole, Starship refuses and tells you how far you actually turned:
> the mount only turned 11.4 degrees - at least 20 degrees is needed (about 80 > works best). Turn it further and run again.
A wrong pole you trust is worse than no answer, which is exactly why this check exists.
Why the turn must be pure RA
Two solved frames carry no redundancy. The rotation that maps frame 1 onto frame 2 carries the boresight along with it by construction, so nothing inside the pair can reveal that the turn was not pure RA - a declination slip during the turn is mathematically indistinguishable from polar misalignment. At an 80-degree turn it takes only about 1.3' of Dec contamination to fabricate 1' of polar error, and the sensitivity is roughly 3.7x worse at the 20-degree minimum, so a big turn is protective as well as better conditioned.
If a mount is connected, Starship reads its declination either side of the turn and refuses the measurement if it moved more than 5'. That is information from outside the pair, which is the only place such a check can come from. On a rig with no mount connection there is nothing to compare against, so the purity of the turn is unverified - the run records this rather than implying it was checked. Keep your hands off the Dec axis and it is a non-issue. With a third position the mount-Dec check runs on both turns.
The third position is the real answer to this: it is the only check that works with no mount connection at all. Two turns give two independent measurements of the axis, and a slip in either turn - or a bad solve at any position - makes them disagree.
If the three positions disagree
> the three positions do not agree on where the mount axis is: the turn-to-turn > spread is 1.01' (limit 0.75'). Something other than RA moved between positions > (declination, or the alt/az knobs), or a solve was bad. Run it again, turning > in RA only.
Causes, roughly in order of likelihood:
- Dec was touched during a turn (a hand on the wrong clutch, or a hand
controller button in the wrong axis).
- The alt/az knobs were touched between positions.
- A bad solve at one position - wrong roll, wrong field. Poor focus, trailing
or a very short exposure make this more likely.
- A position below about 30 degrees altitude. Starship does not remove
atmospheric refraction from the solved fields (neither did the two-position method - it simply could not see the effect), and near the horizon refraction bends the field enough on its own to produce 0.5-3' of spread. The refusal names the low position and its altitude when this is the likely cause.
The fix is the same in every case: run it again higher in the sky, hands off Dec, turning in RA only. The margin is modest by design - a 1.3' slip gives a spread of about 1' against the 0.75' limit, so a slip below about 1' can pass the gate and leave up to ~0.5' of undetected error. That is half the default target; if you need better, lower max_spread_arcmin and expect to repeat runs more often.
Reading the numbers
- Altitude - positive means the axis points above the pole.
- Azimuth - positive means the axis points east of the pole. This is the
true on-sky angle, already scaled by cos(altitude), so it is directly comparable with the altitude figure.
- Total is the combined on-sky angle between the fitted axis and the
(refracted) pole. That is the single number the job drives down to target_arcmin.
A few arcminutes is fine for visual use; for long unguided subs aim for under ~1-2'. Seeing and mount mechanics set a practical floor - chasing the last fraction of an arcminute usually is not worth it.
Settings reference (the [polar] config section)
Measurement:
exposure_s(4.0) - exposure for each solve frame. Clamped to 0.5-120 s.binning(1) - camera binning for solve frames. If you have set a
plate-solve binning override (Settings -> Camera), that override wins and this value is ignored for the solve frames.
turn_deg(80.0) - how far to ask you to turn the mount in RA. Around 80
degrees conditions the measurement well.
min_turn_deg(20.0) - below this the axis cannot be determined reliably and
Starship refuses rather than reporting a wrong pole. Applies to the second turn too, and to the angle between positions 1 and 3.
positions(3) - 3 = after position 2, offer a third position that
cross-checks the measurement (you can still continue with two); 2 = never ask.
max_spread_arcmin(0.75) - three-position runs refuse when the axes
measured from the pairs of positions disagree by more than this. A 1.3' declination slip shows as about 1' of spread. Keep it below your target.
Live readout / target:
target_arcmin(1.0) - the run succeeds once total error is at or below this.apply_refraction(true) - aim at the refracted pole. Leave on.adjust_interval_s(3.0) - seconds between solves (floored at 1).max_adjust_min(30.0) - auto-end the live phase after this many minutes.
Motorized adjustment (PENDING)
The motorized fields are present in PolarConfig but not yet wired:
motorized_enabled(false)motorized_alt_steps_per_arcmin(0.0)motorized_az_steps_per_arcmin(0.0)
The loop is deliberately built around an adjuster abstraction so a powered alt/az adjuster can drop in without redesign, but the only implementation today is the ManualAdjuster, which is measure-only: it computes and reports the residual and you turn the knobs by hand. No code drives a motor from these fields, and the steps-per-arcmin values are meaningless until calibrated against real motor moves, so they are inert. For now polar alignment is a fully manual procedure.
How it ties into the rest of Starship
- It reuses the same plate-solve (ASTAP) path and capture/job machinery as
precise pointing and the rest of the console.
core/polar.pyis shared infrastructure: its vector, precession, and
pixel-to-RA/Dec (WCS) helpers also back precise-slew offset math and click-to-center.
- It is a setup-time facility - run it when you set
up the rig or after the mount is physically disturbed, not on every session.
Safety and gotchas
- Starship does not slew. You move the mount, so you control where it goes -
mind the pier, walls, meridian and cable slack as you turn it.
- Tracking should stay ON for the live readout - see Requirements above.
- Do not touch the alt/az knobs between the measurement positions. That is the
one window where the method assumes the polar axis is fixed.
- Keep the measurement positions above about 30 degrees altitude: refraction
is not removed from the solved fields, and low down it alone can make three positions disagree (or, with two, add an error you cannot see).
- Turning too little is refused, not guessed at.
- Transient solve failures during the live loop are tolerated (it logs and
retries on the next interval) - clouds drifting through will pause progress rather than abort.
- The reported error is only as good as the solves; poor focus, trailing, or too
short an exposure will make the numbers noisy.
Status and limitations
The polar math is unit-tested: synthetic two-position observations with a known injected axis error are fed in and the routine recovers the correct axis and the correct alt/az arcminutes in both hemispheres, across a range of operator turn sizes, and a too-small turn is verified to be refused rather than guessed. The sidereal-advance and knob-constrained update in the live loop are verified against a rigid-rotation mount model over many successive adjustments.
The three-position maths is unit-tested the same way: a mount aligned exactly on the pole of date reads ~0' from three positions in both hemispheres; a 1.3' declination slip that two positions report as 1' of polar error is detected as spread above the limit; a too-small or reversed second turn is refused; the solve times are honoured (three solves 90 s apart on a mount 3 degrees off fit with no spread, in both hemispheres); and Continue with two produces a result identical to a positions = 2 run.
Refraction of the solved fields is not removed (a follow-up), so keep the measurement positions above ~30 degrees altitude - see If the three positions disagree.
What that does not cover is on-sky behaviour - real plate-solve frames, the live loop against an actual mount, and the (PENDING) motorized path. Treat the on-sky workflow as functional-but-unproven until you have run it under the stars.
History
Before v22.1 this routine slewed the mount itself across 3-5 RA positions and fitted a circle to the solved pointings. That version was replaced because it was hostage to everything about the slew: mounts Starship could not drive, slew completion reporting, and - most damagingly - a meridian flip landing mid-sweep, which puts the measurement points on two different arcs and yields an axis nowhere near the pole. The two-position method removes the slew entirely, and with it that whole class of failure.