Autofocus
HFR/HFD-based autofocus. The model is learn once, refocus fast: you run a wizard once per optical setup to learn the focus V and write a reusable Refocus recipe, then a fast Refocus re-traces a tight sweep around where you are without re-finding the whole V. There are two ways to learn: Adaptive First Light, which finds everything for you (step, range, backlash) with no numbers to enter, and the plain Wizard, where you drive the sweep yourself.
The engine was rebuilt across v20.28–v20.34 (linear multi-star sweep → two-button Wizard/Refocus → single-pass Wizard); the Adaptive First Light path arrived with v20.51 and the Refocus apex refinement with v20.75. Earlier builds used a two-phase HFD-target navigation model; that is gone from the buttons (the old calibrated path lingers in the backend for compatibility only).
One method, shared rules
Every path — both wizards and Refocus — shares the same global rules, so they take their measurements the same way:
- Stay on the field. Focusing happens on the field you're already pointed at — there's no slew to a bright catalog star unless you opt into Slew to focus star in Settings (see Before running a wizard).
- Multi-star median. Each sample is the median HFD of every detected star in the frame, not one tracked anchor. A defocusing population is averaged, not chased.
- Fixed exposure / binning. Defaults are 5 s, hardware-binned (
af_hw_binning, default 2), over a centred 30% subframe (af_hw_roi_pct) to keep frames quick — no auto-exposure ramp. Set these in Settings → Autofocus. - Always-inward sampling. Every measured position is reached by a final inward move, so backlash is taken up the same way at every point. The sweep over-travels in the OUT direction first, then clears backlash inward to the first sample. On the reference rig OUT = increasing focuser position, and best focus sits around position ~2500 — both are rig-specific; yours will differ.
The displayed metric is HFD = 2 × the internal HFR, so the numbers match Voyager (v20.30). Thresholds and the saved recipe are kept internally in HFR.
Adaptive First Light — the no-numbers wizard (recommended)
Adaptive First Light is the self-finding version of the wizard: you don't enter a step, a range, or a sweep offset — it discovers them. Start from your best manual focus with a star field in view and press the button; it does the rest and leaves the scope focused.
- Noise floor. It takes a few frames without moving to measure how much the HFD jitters from seeing, so it knows what a real change looks like versus noise.
- Focuser resolution probe. It steps outward by doubling amounts until the HFD changes by clearly more than that noise — that tells it the smallest useful step for your focuser (a fine focuser gets a small step, a coarse one a larger step, automatically).
- V-curve. With the step and sweep known, it runs the same single adaptive sweep the plain Wizard does, brackets the V, moves to best focus, and saves the Refocus recipe.
- Backlash measurement. It then measures the focuser's backlash (lost motion on reversal) from the V-arm slope and writes a focuser report with the recommended backlash steps/direction — see The focuser report below.
The saved recipe records what it learned (adaptive, the learned step, the measured noise, a fit-confidence figure) so Refocus benefits immediately. Because it moves the mount only if you asked it to (it never slews unless Slew to focus star is on), it's safe to run on the field you're already imaging. This is the button to reach for first; the plain Wizard below is there when you want to drive the sweep yourself.
Wizard — drive the sweep yourself (run once per setup)
Run the Wizard once per optical configuration, starting from a position that is already roughly focused. It is a single adaptive pass (v20.34) — one sweep that brackets the V and saves the step that worked. There is no separate coarse-then-fine derivation pass. Use it when you'd rather set the step and out-travel yourself; otherwise prefer Adaptive First Light above, which finds them for you.
- From the current position it racks OUT by the coarse offset (
coarse_out, default 250 in the runner; the Console drives a larger out-travel), then moves IN bybacklash_clearto the first sample. - It samples every
stepstepping inward, watching HFD drop to a minimum. - It does not climb the whole far arm: once there are ~5–6 real samples (
rise_past_min) past the low point, the V is mathematically determined — it stops, fits, and moves to best focus. - If it can't bracket a clean V (no clear minimum, or too few points to fit), it halves the step and restarts from the out position. After a couple of tries it gives up with a "re-center near focus" message rather than fitting junk.
step defaults to whatever worked last time (the saved recipe's step), else 50. Leave the step field blank to reuse it.
On success the Wizard moves to the fitted minimum and writes the Refocus recipe sidecar. The fit uses fit_method (default hybrid): straight V-line slopes for the navigation params, with the focus position refined toward a hyperbolic vertex and the lowest measured HFR reported as the minimum.
Refocus — fast, repeatable
Refocus assumes you are already near focus (you just ran a wizard, or you're partway through a night). Because of that it resolves the apex rather than re-surveying the whole V: it samples with a finer step over a tighter range than the wizard's coarse recipe (a wide coarse sweep gives the sharp bottom only one point and lets the noisy outer wings drag the fit), reusing the same number of exposures. It fits the V with the same fitter, moves to the mathematical minimum, and reports "focus point found at position X" — drawing the two convergence lines and the focus position on the graph. Tune it with [autofocus] refocus_step_frac (the finer step as a fraction of the recipe's step; 1.0 = legacy) and refocus_points_per_arm (how many samples each side of centre).
Pick a filter in the Focuser-card dropdown to refocus through that filter: Refocus moves the wheel there first and focuses at that filter's own exposure, then you're set for imaging through it. Leave it on Current filter to focus where you already are.
Because it re-uses the recipe's half_range / step / backlash_clear, Refocus is a handful of exposures, not a full V hunt. With no recipe it falls back to a sensible default range.
The Refocus recipe (focus_recipe.json)
Either wizard persists its result as a JSON sidecar, focus_recipe.json, next to config.toml (atomic write; JSON-safe). It carries:
focus_position— the fitted best-focus positionstep— the sweep step that bracketed the V (what Refocus re-uses)half_range— the symmetric Refocus range (=step × points_per_arm)points_per_arm— samples per arm for the Refocus sweep (4–6)backlash_clear— the over-travel used to take up backlash inwardout_offset— the Wizard's OUT racking distancerise_past_min— how many points past the minimum the Wizard requireddirection—OUTorINn_runs— how many times a wizard has run on this setupupdated_at— when it was last written
When Adaptive First Light wrote the recipe it also stores what it discovered: adaptive: true, the learned min_effective_step, the measured noise_hfr and baseline_hfr, a fit confidence, and the measured backlash (measured_backlash_steps / measured_backlash_direction / measured_backlash_approved). Approving the focuser report (below) flips measured_backlash_approved and sets backlash_clear to the measured value so Refocus uses it.
n_runs accumulates across runs: a fresh run reloads the prior recipe and bumps the count. There is no derivation / learned EMA block — the old two-pass derive-and-learn model was removed in the v20.34 single-pass redesign.
The calibration readout shows focus <pos> · step <n>, and Go to focus drives the focuser straight back to the last saved focus position (from the recipe, falling back to the newest calibration run) — handy after anything moves it off.
The focuser report (focuser_report.json)
When Adaptive First Light measures backlash it writes a second sidecar, focuser_report.json, next to the recipe. It's a human-readable characterisation of your focuser — the learned step, the noise floor, the fitted V slopes and their R², the measured backlash and a confidence figure, and the sampled focus curve — plus the recommended focuser settings (whether to enable backlash and how many steps in which direction).
The report lands awaiting your approval: nothing is applied until you press Approve measured backlash for Refocus. Approving copies the recommended backlash into [focuser] (so it's visible in Settings and saved with profiles), marks the report approved, and points Refocus's backlash_clear at the measured value. Until you approve it, the report is informational only.
Running it
From Console → Focuser card:
- Adaptive First Light… — opens the self-finding wizard modal (measures noise, probes the step, builds the V, measures backlash; writes the recipe and the focuser report, and leaves the scope focused).
- Wizard… — opens the plain Wizard modal, where you set the OUT-travel and (optionally) the step yourself.
- Filter dropdown — pick a filter to focus through. Refocus moves the wheel there first, then focuses at that filter's exposure; leave it on Current filter to focus where you are.
- Refocus — quick recipe-driven refocus around the current position (through the selected filter).
- Go to focus — moves the focuser straight back to the last saved focus position.
- Clear cal — wipes the saved focus state; you'll need to run a wizard again.
The Approve measured backlash for Refocus button (in the wizard modal, enabled once a focuser report exists) applies the report's recommended backlash — see The focuser report above.
Endpoints are POST /api/console/autofocus/adaptive-first-light, /focus-wizard, /refocus, /approve-focuser-report, /goto-focus, and /clear. Every path logs each sample to the ops log: pos … HFD=… (N stars) per point, the start → measured → moving → done phases, and focus point found at … on completion.
In sequences
The Sequencer triggers autofocus two ways, both of which run the validated run_refocus synchronously and cancelably (the sequence's Stop reaches it; v20.32):
- An
autofocusaction step, placed explicitly in the plan. - Refocus triggers. These fire a refocus before the next exposure whenever a condition is met: every N frames, every X minutes, on filter change, or on focuser temperature drift. They come from the plan's own focus policy, and — when you turn on the master Enable auto-refocus switch in Settings → Autofocus → Refocus triggers — from the global
[autofocus].refocus_*fields (refocus_every_n_frames,refocus_every_n_minutes,refocus_on_filter_change,refocus_on_temp_delta_c), which overlay onto whatever the plan set. The master switch is off by default, so no auto-refocus happens unless you ask for it. Afocuser_positionfallback drives to a fixed position when no calibrated autofocus is available.
Two extras apply after a focus completes:
- Focus final offset (
focus_final_offset_steps) — shifts the focuser by a fixed number of steps after every successful focus (for example, an AF-filter-to-imaging-filter offset). - Per-filter focuser offsets (
filter_offsets, e.g.{"L":0,"R":30,"Ha":120}) — on a filter change the focuser shifts by the delta between filters instead of running a full refocus, so a colour-filter swap doesn't cost a whole AF sweep. Empty = off.
A refocus failure mid-sequence is journaled but does not abort the run (fail-soft), and the camera is restored to full-frame / bin 1 afterwards so science captures are unaffected. The V-curve streams live into the sequence run view as it samples.
Before running a wizard
- Manually focus first. Start roughly focused. Both wizards sweep outward from where you are; start far off and the wizard may not bracket the V and will ask you to re-center.
- Aim at a star field. A normal field with a good spread of stars — the multi-star median is robust, so you don't need one perfect anchor. Avoid empty patches and dark nebulae.
- Tracking on, mount settled. Drift during the sweep smears the measurement.
- Pick a filter. Broadband gives the most stars; narrowband works if the field is rich enough to detect a usable population.
- Focus star (optional). Off by default, both wizards stay on the field you're pointed at. If you turn on Slew to focus star in Settings, the wizard slews to a bright catalog star (within your magnitude / altitude / distance limits, honouring the blacklist) first and slews back afterwards — this moves the mount, so it's opt-in.
Key tunables (Settings → Autofocus)
Measurement
exposure_s(5.0) /af_hw_binning(2) — fixed AF exposure and hardware binning for every sample (0= use the acquisition binning)af_hw_roi_pct(30) — centred hardware subframe % for AF frames; 0 or ≥100 = full framecentral_region_pct(75) — only stars within this central fraction of the (sub)frame are measureddetection_sigma(3.0) — star-detection threshold (lower than science = 5, so defocused stars are still found)star_flux_min_k/star_flux_max_k(×1000 ADU; 0 = off) — detection flux gates; a non-zero upper cap rejects near-clip stars that would skew the medianhfd_integration_near_hfd(0) — near focus, average several frames per point to beat seeing; the arms use one frame. 0 = integrate everywhere
Fit & motion
direction(OUT) — which way "out" is; sets the over-travel/backlash-clear conventionbacklash_steps(0) /backlash_direction(OUT) — focuser backlash handling; the sweep also clears backlash by always landing inwardnear_focus_hfd(4.0) — HFR at/below this is "near focus"; the arm-filter floor for the V-line fitvcurve_limit_hfd(12.0) — HFR above this is "at the edge" of the Vfit_method(hybrid) — V-curve fit:vlines|hyperbolic|hybridwizard_outlier_reject(true) — drop a single clear arm outlier before fitting
Wizard
wizard_verify(true) — after calibrating, run a self-check autofocus and leave the scope at best focustarget_peak_frac(0.75) /target_peak_hi(0.9) — when auto-setting the anchor exposure, aim the focus-star peak here (fraction of saturation), staying below clippingwizard_slew_to_focus_star(false) — opt-in slew to a bright catalog star before focusing, then back (moves the mount)focus_star_mag_min/focus_star_mag_max/focus_star_alt_min_deg/focus_star_max_dist_deg/focus_star_blacklist— which star the focus-star slew may pick
Refocus
refocus_step_frac(0.5) — Refocus samples finer than the recipe: step = this × the recipe step (1.0= legacy, reuse the recipe step verbatim)refocus_points_per_arm(5) — samples each side of centre → half-range = step × this
Refocus triggers (sequence; master refocus_enabled off by default)
refocus_every_n_frames,refocus_every_n_minutes,refocus_on_filter_change,refocus_on_temp_delta_c— the global triggers overlaid onto a running planfocus_final_offset_steps(0) — shift the focuser this many steps after every successful focusfilter_offsets({}) — per-filter focuser offsets; a filter change shifts by the delta instead of a full refocus
Behind the scenes
Pure NumPy — no photutils, no sep. Star detection is connected-components flood fill on a sigma-thresholded image with eccentricity, size, and saturation filtering. HFR is the radius at which cumulative flux (sorted by distance from the flux-weighted centroid) reaches half the total, within an aperture sized to the star (floor 6 px, grown for real defocus donuts; v20.30) over a local-annulus background. Frames captured during AF stay in memory — they are not written to the IMAGES directory.
What's deferred
See the Roadmap topic. Items still to come: a min/max focus-altitude guard, and on-rig confirmation of the default step / range / exposure for a given optical train. Per-filter focus offsets now ship (see In sequences). The older calibrated HFD-target run_autofocus path remains in the backend for compatibility but is not what the wizard/Refocus buttons use.