Skip to content

Focus-quality (HFD) indicator on the Focus screen#449

Merged
brickbots merged 5 commits into
mainfrom
worktree-focus-quality-indicator
Jun 3, 2026
Merged

Focus-quality (HFD) indicator on the Focus screen#449
brickbots merged 5 commits into
mainfrom
worktree-focus-quality-indicator

Conversation

@brickbots
Copy link
Copy Markdown
Owner

What

Adds a quantitative, graphical focus aid to the Focus screen (UIPreview). It tells the user how well-focused they are and makes the point of best focus obvious as they sweep through it — independent of plate solving, across the full defocus range.

How

  • PiFinder/focus.py — pure, UI-free module: a self-contained Half-Flux Diameter (HFD) detector (detect_stars, half_flux_diameter, focus_hfd). Runs in the main process on the raw frame; tuned to accept broad/defocused blobs and reject hot pixels / oversized blobs. No dependency on the solver's centroids or SQM (see ADR 0005).
  • UIPreview — a focus strip: fixed log-axis V-curve over a 10 s window, best-focus marker, past-best "BACK UP" cue, HFD/exposure/detected-count/matched-count HUD. Replaces per-frame autocontrast with a background-anchored, EMA-smoothed display stretch. SQUARE toggles the strip; it persists across zoom (HFD is zoom-independent).
  • Testspython/tests/test_focus.py (11 unit tests: Gaussian HFD vs theory, saturated core, blank frame, oversized→too-defocused, monotonicity, median robustness, hot-pixel rejection).
  • Docs — Quick Start "Setting Focus & First Solve" reframed around the HFD readout + V-curve, with HUD example screenshots.

Design references already on main: docs/adr/0005-focus-hfd-self-contained-in-ui.md and the "Focus indicator" section of docs/ax/ui/CONTEXT.md.

Verification

  • ruff / ruff format / mypy clean; pytest -m unit (190, incl. 11 new) and -m smoke (5) pass.
  • Verified live in the headless app: strip renders with live HFD/detected/matched/exposure, empty frames show /det 0, SQUARE toggles the strip, zoom persists it with the relocated Zoom xN label, no crashes.

Notes

  • New UI strings are _()-wrapped and fall back to English until the next nox -s babel; the Babel catalog regeneration is intentionally not included (it swept up unrelated catalog drift).

🤖 Generated with Claude Code

brickbots and others added 5 commits May 29, 2026 11:34
Self-contained Half-Flux Diameter detector (PiFinder/focus.py) plus a focus
strip in UIPreview: log-axis V-curve over a 10s window, best-focus marker,
past-best "BACK UP" cue, and a background-anchored display stretch replacing
per-frame autocontrast. The detector runs in the main process on the raw frame
and does not depend on plate solving, so it works across the full defocus range.

See docs/adr/0005-focus-hfd-self-contained-in-ui.md and the "Focus indicator"
section of docs/ax/ui/CONTEXT.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reframe "Setting Focus & First Solve" around the HFD readout and its V-curve
rather than zooming in to judge stars by eye. Add a focus-strip walkthrough and
HUD example images (focused vs unfocused, with the strip composited over the
existing example frames).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…drop back-up cue

- HFD readout is now a large, right-justified number filling the strip height
  (the hero element); the V-curve and small labels move to the freed left
  region. The no-reading states fall back to a small dim hint ("—" / "keep
  going") instead of a giant placeholder glyph.
- V-curve axis is 4 → 20 px (was 1 → 50): ~4 px is about the best a real
  camera/lens hits and ~20 px is clearly soft, so the trend spends its range
  where it's useful. Out-of-range readings clamp; the numeric readout is exact.
- Display stretch is calmer: lower EMA alpha + larger minimum span cut the
  frame-to-frame brightness swings, and a little uniform dither breaks the
  8-bit banding a narrow stretch otherwise posterised into.
- Remove the past-best "BACK UP" cue and its supporting machinery.
- Update the Quick Start prose and the UI CONTEXT glossary to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The HFD readout and V-curve now carry the focus workflow, so the central
concentric-circle reticle no longer adds anything on the Focus screen.

- Drop draw_reticle() and its zoom-0 call site, plus the unused reticle_mode
  attribute and the orphaned camera_reticle config option (no settings UI).
- Fix a stale comment: the display stretch is numpy + dither, not a LUT.
- Note in the Positioning doc that solve_pixel(screen_space=True) now has no
  UI consumer (the reticle was its only one); the accessor stays.

The Chart/Align Telrad reticle (chart_reticle) is unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolves conflicts from main's #429 solver/integrator dataclass refactor:
- preview.py: keep the reticle removal + focus-strip work; port
  _matched_star_text() to the new PointingEstimate API
  (solution.solve_source / .estimate_time / .diagnostics.Matches),
  mirroring main's own refactor of that logic.
- docs/ax/positioning.md: take main's solve_pixel -> target_pixel rename,
  keep the note that the Focus-screen reticle (its only consumer) is gone.
- docs/source/quick_start.rst: keep the focus-strip documentation, adopt
  main's leaner opening/closing prose and its +/- zoom tip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@brickbots brickbots merged commit fe07eb0 into main Jun 3, 2026
1 check passed
brickbots added a commit that referenced this pull request Jun 5, 2026
The focus-quality (HFD) strip was added in #449 before the
resolution-flexible UI work in #453. #453 converted the camera preview's
image resize and star selectors to derive from the display resolution but
never touched draw_focus_strip(), which kept 128-only literals:

  strip_top = 90      # bottom-band top
  plot_top  = +9      # V-curve top clearance
  plot_bottom = -10   # V-curve bottom clearance
  bottom label = -9

On the 176x176 SSD1333 panel strip_top=90 lands mid-screen (~51% down)
instead of as a bottom band, stranding the exposure/matched-star labels
high up with a large empty gap above the readout, and the bottom
detected-star label clips at the screen edge.

Derive the geometry instead (ADR 0009):

  - strip_top: a fixed fraction of screen height (res_y - res_y*38/128),
    so the band stays ~30% of the screen on any panel.
  - label-row clearances: from self.fonts.small.height, so the rows track
    the (larger) small font on the 176 panel and never collide with the
    V-curve.

The 128 small font is 9 px, so strip_top+9 == strip_top+small_h,
res_y-10 == res_y-small_h-1, and res_y-9 == res_y-small_h: the 128 layout
is reproduced pixel-for-pixel (zero regression), while the 176 layout
becomes a correct proportional bottom band. Verified headless on both
DisplayHeadless (128) and DisplayHeadless176 (176).

Docs: update the CONTEXT.md focus-strip glossary entry, which described a
fixed "~38 px" band, to note it scales with the display.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant