π€AI-generated documentationβ curatedAI Generated
About content generation types
(e.g., docs generated from codebase analysis)
(e.g., livestream β blog post, meeting notes β docs)
(e.g., hand-written tutorial)
Module Architecture
Goalβ
One coherent module that owns "the kinematic and inertial state of a rigid body / the whole body," reusable by both pipelines, with clean boundaries so each piece can be understood and tested on its own.
Provenance: the bs kinematics_core ontologyβ
The bs client repo (clients/bs/python_code/kinematics_core) already contains a
well-designed, purely kinematic ontology that we will pull into freemocap and refactor
(β³ planned for Phase 3, when per-segment orientation first needs it β not required for the
Phase 1 point-mass ellipsoid, so not yet ported):
ReferenceGeometry+CoordinateFrameDefinitionβ build a body-fixed coordinate frame from named keypoints (origin + one exact axis + one approximate axis, third by cross product, Gram-Schmidt orthonormalized).RigidBodyStateβ full per-instant state (position, velocity, orientation, angular velocity, accelerations).RigidBodyKinematicsβ a pose trajectory that lazily computes velocity, acceleration, and angular velocity (global + local) by quaternion finite-differencing.Quaternion/ quaternion-trajectory + vectorized derivative helpers.
What it does not have is any notion of mass, inertia, or momentum. That is precisely the layer this proposal adds on top.
Proposed layoutβ
freemocap/core/kinematics/
βββ reference_geometry.py # β³ ported: ReferenceGeometry, CoordinateFrameDefinition, StaticPose
βββ rigid_body_state.py # β³ ported: RigidBodyState (per-instant)
βββ rigid_body_kinematics.py # β³ ported: RigidBodyKinematics (trajectory; lazy derivatives)
βββ quaternion.py # β³ ported: Quaternion (+ trajectory)
βββ derivatives.py # β³ ported: finite-difference helpers
β
βββ inertial/ # NEW β the layer bs does not have
β βββ anthropometry.py # β
de Leva 1996 table (mass, CoM, radii); F + M + mean default
β βββ segment_inertia.py # β³ per-segment inertia tensor Jα΅’ from anthropometry + bone length
β βββ composite_inertia.py # β
I_G (parallel-axis sum) + eigh + equimomental semi-axes
β βββ centroidal_momentum.py # β³ H_G (orbital + spin), Ο = I_Gβ»ΒΉ H_G
β βββ ground_reference.py # β
CoP (CoM projection), XCoM = capture point, CMP
β
βββ body_kinematics_state.py # β
the unified per-frame bundle (the public output)
βββ online/ # β
realtime per-frame adapter (rolling history)
βββ streaming_kinematics.py# β
per-frame I_G + ground-refs, 3-deep CoM history
Legend: β built (Phase 1) Β· β³ planned (Phase 2/3). The ontology files at the top are not yet ported β the point-mass ellipsoid and ground references need no orientation.
Shared core, two consumersβ
The math, the anthropometric tables, and the data structures are shared. The source of derivatives differs by pipeline:
| Concern | Realtime (built first) | Posthoc (later) |
|---|---|---|
| Velocities / angular velocity | per-frame finite differences over a small rolling history (the pattern already used for prev_com in the aggregator) | RigidBodyKinematics lazy whole-array derivatives (cleaner, less noisy) |
| Entry point | online/streaming_kinematics.py | trajectory ontology directly |
| Shared | inertial/* math, anthropometry, BodyKinematicsState | same |
This keeps a single source of truth for the physics while letting each pipeline use the derivative method that suits it.
Per-segment orientation strategyβ
The spin term of H_G (and a fully faithful inertia ellipsoid) needs each segment's
orientation. Locked decision:
- Limb segments (upper/lower arm, thigh, shank, foot): treat as rods aligned with the bone vector. A rod is axisymmetric, so its inertia about the long axis is negligible and spin about that axis contributes little β we need only the bone direction and its angular velocity (from the bone vector's rotation across frames).
- Trunk / pelvis / head: not rod-like, so build a real body frame with
ReferenceGeometry(shoulders + hips define the trunk frame; this is exactly what the ported ontology is for).
A full per-segment Kabsch/Procrustes pose fit is the heavier alternative we can adopt later if higher fidelity is needed; the rod+frame approach is the pragmatic default.
Anthropometric data β the one new assetβ
The CoM calculation today uses only segment mass fractions and CoM locations
(skellyforge's SegmentCenterOfMassDefinition). The inertia ellipsoid additionally needs
radii of gyration. β
anthropometry.py now holds the de Leva 1996 Table 4 values (8
primary segments; female + male tables; mean used by default) giving per-segment
(k_sagittal, k_transverse, k_longitudinal) as fractions of segment length, so:
Jα΅’ = mα΅’ Β· diag( (k_sagΒ·Lα΅’)Β², (k_transΒ·Lα΅’)Β², (k_longΒ·Lα΅’)Β² ) # about segment CoM, segment frame
This was the only genuinely new data the proposal required, and it has landed (mass
fractions verified to sum to 1.0). Phase 1 still ships the point-mass ellipsoid (Jα΅’ = 0);
Phase 2 feeds these radii in via segment_inertia.py.
Boundaries & testabilityβ
Each unit has one job and a clear interface:
- β
composite_inertia:composite_centroidal_inertia(...) β I_G,principal_axes_and_moments(I_G) β (moments, axes),equimomental_semi_axes(...) β semi-axes. Pure functions; unit-tested against analytic cases (two masses βdiag(0,2,2); uniform sphere β semi-axes = R). - β³
centroidal_momentum(Phase 2):H_G(orbital + spin) andΟ = I_Gβ»ΒΉ H_G. Will be tested against conservation cases (free-fall β constantH_G). - β
ground_reference:center_of_pressure_ground_projection,extrapolated_center_of_mass,centroidal_moment_pivot. Pure functions; unit-tested incl. the point-mass limit (zero CoM acceleration β CMP = CoP).
The realtime adapter and the trajectory ontology both call these same pure functions.