fit_spin — DRIVE ball op

• Data kinds: signal × points × table → table

• Call: import fullseye as fs; fs.ledger.fit_spin(t, p, bp: 'dict', *, omega0=None, omega_max: 'float' = 1000.0, iters: 'int' = 20, dt: 'float' = 0.001) -> 'dict' (to call the implementation directly, import ballistics; ballistics.fit_spin(t, p, bp: 'dict', *, omega0=None, omega_max: 'float' = 1000.0, iters: 'int' = 20, dt: 'float' = 0.001) -> 'dict'; from the registry, opsdrive.get("fit_spin"))

Usage

Reads the spin from how the ball curves: fits the equations of motion with drag + Magnus to a trajectory (t_i, p_i) (an interval without bounces) with the 9 parameters (p₀, v₀, ω) by Gauss–Newton (the aerodynamic coefficients are the values in bp = assumed known).

The Magnus force is ω × v, so the component of ω parallel to v produces no force and cannot be determined from the trajectory (rotation about the direction of travel = part of sidespin). Because the direction of v changes during flight it is not completely indeterminate, but weakly so. In the return value, omega_perp = the component perpendicular to the initial velocity (the readable part), cond = condition number of the Jacobian, omega_axial_sensitivity = rms change [m] of the trajectory when the parallel component is moved by 1 rad/s (the smaller, the less readable). Return value {"p0", "v0", "omega", "omega_perp", "rms", "cond", "omega_axial_sensitivity", "iters", "t0"}. Feeding the true trajectory recovers ω to 1e-3 rad/s (gate). It is an independent second way of measuring, alongside the angular velocity measured from the pattern (balltrack.spin_from_markers).

omega_max [rad/s] = upper limit of the rotation magnitude (projected onto this ball after each step). ★The spin-ratio model C_L = 1/(2 + 1/S) saturates at 0.5 for large rotation, so a fit that sees too little force can make |ω| arbitrarily large (the magnitude is not determined). With a short trajectory dominated by noise it ran off to 1e5 rad/s (2026-09-30). Default 1000 rad/s ≈ 160 revolutions/s (above the upper limit for a hard hit). If at_bound in the return value is True, it is stuck at the limit = the magnitude could not be read.

References (sample data, literature)

• Sample-data catalog (download URLs / licences) — 2-D uses skimage.data (BSD/public domain) plus synthetic images; 3-D lists download URLs for real data sources (Stanford, PDS, …).

• Operator provenance and references — the sources of the research/methods this op family came from.

• The canonical algorithm (author, year) and its uses are named in the family usage guide above.

Runnable examples (verified samples that actually call this op)

• poc_table_tennis_spin — py -3.11 examples/poc_table_tennis_spin.py

Ops the type connects to (they accept table as input)

course_layout · course_occupancy · course_contains · world_build · world_camera · world_move · lidar_scan · rss_longitudinal_same

Same category (ball)

ball_params · impact_params · flight_vacuum · flight_ode · flight_simulate · flight_state_at · magnus_lift_coefficient · drag_coefficient_sphere


*Provenance: ballistics.py — DRIVE operator registry. This per-op note is generated by tools/opdocs.py md (do not hand-edit).*

© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.