Skip to content

feat(camera): clubhead pose fit, scores and delivered angles - #250

Draft
HarjotDhanota wants to merge 2 commits into
open-flight:mainfrom
HarjotDhanota:feat/clubpose-fit
Draft

feat(camera): clubhead pose fit, scores and delivered angles#250
HarjotDhanota wants to merge 2 commits into
open-flight:mainfrom
HarjotDhanota:feat/clubpose-fit

Conversation

@HarjotDhanota

@HarjotDhanota HarjotDhanota commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Stacked on #245: the first commit here is #245's geometry stage, and this PR stays a draft until #245 merges, after which it rebases to the estimation commit alone.

What does this PR do?

Second stage of markerless clubface impact location, stacked on the geometry PR. Adds the estimation half of openflight.camera.clubpose and the technical report on where the work stands.

  • Sequence pose fit (fit.py) — fits the mesh to segmented silhouettes across frames with shared physical bounds and temporal smoothness, after independent per-frame fits produced angles jumping over 100° between adjacent frames.
  • Pose scores (scores.py) — boundary-distance and rotation-consistency scores. Silhouette IoU was measured running inversely to pose correctness on real masks, so it is deliberately not the objective.
  • Delivered angles (angles.py) — converts a fitted pose to dynamic loft, face angle and lie, with plausibility envelopes that fail closed.
  • Clubhead/shaft separation (head_split.py) and rigid-motion primitives (motion.py).
  • The report (docs/clubface-impact-location-report.md) and its entry document, which covers setup and running against your own captures. The web version carries eleven figures (real frames with the model's own projections overlaid), and the frame-by-frame fusion view shows the current state on two real shots in about thirty seconds.

The substantive positive result is the camera+IWR6843 fusion. Neither sensor can measure clubhead velocity alone: the camera sees image-plane motion but has no depth, the IWR6843 measures range rate along its line of sight but cannot see lateral motion. Combining them as dP/dt = (dr/dt)·ray + r·d(ray)/dt recovers the full 3D velocity. Checked against the OPS243, which takes no part in the fit: across 6 fitted shots the fused-to-OPS243 speed ratio is mean 0.970, sd 0.029, spread 0.941–1.015, worst shot 5.9% from unity. This is the architecture Trackman uses for impact location, demonstrated on this hardware. The same fusion showed the clubhead's radar range is not constant — it sweeps 347–438 mm across the fitted frames — so range comes from the radar's own range rate, reducing the pose fit from five free parameters to four.

Also validated: ball detection on 21 of 22 captures, impact timing agreeing between camera and radar to 0.66 frames, and camera attitude measured rather than assumed.

Not working: clubhead orientation. Face angle, dynamic loft and impact location have no accuracy figure against truth, and this PR does not claim one. The reason is measured, not suspected: the first 5° of face angle change the projected silhouette by zero pixels, one pixel of segmentation error is worth roughly 10° of face angle, and the club is segmentable for only ~10 pre-impact frames of which the extractor keeps 3 to 5, against a four-parameter fit.

Nothing is wired into the shot pipeline. The experiment scripts and recorded results behind each figure stay on the contributing fork, linked from the report. Two related changes are deliberately deferred to their own PRs: surfacing the trajectory metrics the ballistics simulator already computes, and the club-delivery extension to the Trackman comparison tool.

Why was this required?

Whether this hardware can produce impact location had never been answered with evidence. The answer is specific: the sensors and the fusion are sufficient for clubhead velocity, and the binding constraint on orientation is mask precision and observation count, not the fitting method. That reframes the next step from "better estimator" to "more pixels".

Additional captures are the most useful contribution, in this order: a session recorded alongside a Trackman (nothing here has been scored against a reference instrument, so no club metric has an accuracy figure — this is the one measurement that would change that); captures from other rigs spanning driver-to-wedge speeds (the current session is 7-iron and 9-iron with no speed overlap, which starves several discriminating tests of power); improved clubhead segmentation (recovering more of the ~10 frames roughly doubles observations per shot); and a capture at 1280×800 1:1, which doubles plate scale at the same frame rate.

Automated tests

Six suites, 62 tests: the fit's range-grid behaviour and hard range pinning, pose scores ranking a known pose first on synthetic masks (explicitly not an accuracy claim), delivered angles including that the mesh's own frame is not square (the trap that sent an earlier fit into the wrong hemisphere), head/shaft separation on constructed and measured shapes, and rigid-motion round trips.

Manual (human) testing

Every figure in the report was produced from the 21-shot reference session and cross-checked across all shots rather than the shot that suggested it; the report's appendix lists ten figures published and withdrawn when a cross-set check contradicted them. The fusion result was verified against the OPS243, which takes no part in the fit. Rendered the model's own projection over real frames and stepped through them, which caught two errors the numbers hid (a projection not tracking the club, and an impact frame wrong by 3.89 frames on every shot); both fixes are reflected here. The reference capture session behind the figures is available on request for anyone who wants to reproduce them.

Checklist

  • Single feature/fix — the estimation stage of one subsystem
  • Automated tests included — 62 tests across six suites
  • Manual testing described — cross-set checks and visual verification above
  • Python tests pass (uv run pytest tests/ -v)
  • Pylint passes (uv run pylint src/openflight/ --fail-under=9)
  • Ruff passes (uv run ruff check src/openflight/)
  • UI builds — n/a, no UI changes
  • UI lint passes — n/a, no UI changes
  • Updated docs and CHANGELOG
  • No unrelated changes mixed in

Club mesh with provenance-checked normalization, calibrated camera model with
silhouette projection, and sub-pixel teed-ball location that anchors the world
frame. First stage of the clubface impact-location work; not yet wired into
the shot pipeline.
Sequence fitting with physical bounds and temporal smoothness,
boundary-distance and rotation-consistency scores (silhouette IoU measured
inversely related to pose correctness on real masks), clubhead/shaft
separation, and delivered loft/face/lie with plausibility envelopes. Includes
the technical report: clubhead orientation has no accuracy figure against
truth, and this change does not claim one.
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