ik¶
[ik]: fit a NeuroMechFly-style model's joint angles to the 3D pose.
The solve is QuickIK's, an optional extra (ik._quickik). deeperfly holds the model (ik.pack, ik.template, ik.articulation), where the tracked points sit on it (ik.binding), the recording's measurements (ik.align), the body plan QuickIK solves (ik.bodyplan) and the fit's joint positions (ik.forward). See ADR-0041, ADR-0065 and ADR-0066.
Alignment
dataclass
¶
The per-leg geometry registering a recording to the template.
Source code in src/deeperfly/ik/align.py
Articulation
dataclass
¶
The loaded head/abdomen chains plus the anchor bodies that register the body.
anchors names model bodies (from the pack manifest) and anchor_neutral holds
their neutral positions, read out of bodies, which maps each body a marker may
attach to onto its neutral frame and chain depth. leg_rest is every leg DOF's
spring reference in radians, the plan's neutral (ADR-0065); empty for an asset
without one, which falls back to zero.
Source code in src/deeperfly/ik/articulation.py
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | |
anchor_points
property
¶
The tracked point observing each anchor body, from the binding.
Empty where the binding puts no point on an anchor's origin; the registration needs only three finite anchors.
chain ¶
load
classmethod
¶
load(
ref: str | Path = DEFAULT_ARTICULATION_PATH,
*,
binding: Binding
| str
| Path
| None = "fly38@neuromechfly",
anchors: tuple[str, ...] = _DEFAULT_ANCHORS,
fit: tuple[str, ...] | None = None,
bounds_overrides: dict[str, tuple[float, float]]
| None = None,
marker_overrides: dict[str, dict[str, dict]]
| None = None,
) -> Articulation
Load the baked articulation, with this run's restrictions applied.
binding's rows on the chain bodies are the chains' markers and its rows on
the anchor bodies say which point observes each; None loads the structure
with no markers, readable but not fittable. fit keeps a subset of the
chains, bounds_overrides maps flygym angle names to degree limits
(case-insensitive), and marker_overrides replaces a chain's binding rows
([ik.markers]).
Source code in src/deeperfly/ik/articulation.py
BodyPlan
dataclass
¶
A generated QuickIK body plan plus the bookkeeping its caller needs.
Attributes:
| Name | Type | Description |
|---|---|---|
plan |
dict
|
The body-plan mapping, JSON-serializable (see to_json). |
joint_names |
tuple[str, ...]
|
|
joint_point |
tuple[str | None, ...]
|
|
joint_row |
ndarray
|
|
joint_branch, |
dof_branch
|
|
angle_names |
tuple[str, ...]
|
|
neutral |
ndarray
|
|
body_sim |
tuple[ndarray, float, ndarray]
|
|
chain_scales |
dict[str, float]
|
|
chain_offsets |
dict[str, ndarray]
|
|
Source code in src/deeperfly/ik/bodyplan.py
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | |
fixed_base
property
¶
Whether the body is held in the model frame instead of fitted per frame.
joint_is_base
property
¶
(N,) which joints are their chain's base landmark.
A base landmark sits on its chain's rotation center, so it is no evidence about the chain's angles (ADR-0065). Read off the plan's own joints, so a plan rebuilt from JSON agrees; not "no DOF moves this joint", which a fixed base's thorax-coxae also satisfy.
kinematics ¶
to_json ¶
The plan as JSON, what QuickIK parses.
No default= fallback, so a stray numpy value raises instead of being
stringified. The x-deeperfly block describes what built the plan; QuickIK and
deeperfly both ignore it.
Source code in src/deeperfly/ik/bodyplan.py
to_model ¶
World points -> the model frame: R^T (p - t) / s.
Source code in src/deeperfly/ik/bodyplan.py
IkResult
dataclass
¶
The fitted joint angles and the model pose they imply.
Attributes:
| Name | Type | Description |
|---|---|---|
angles |
ndarray
|
Joint angles in radians, NaN for un-fittable frames. |
angle_names |
list[str]
|
The |
model_pts3d |
ndarray
|
Forward-kinematic joint positions in world coordinates, in the skeleton's point order (leg joints, antenna bases and abdomen markers filled, other points NaN), so they reproject with the skeleton's bones. |
alignment |
Alignment
|
The per-leg geometry measured from the recording, in the model frame. |
body_plan |
BodyPlan | None
|
The plan that was solved, to re-solve on exactly this geometry. |
chain_scales |
dict[str, float]
|
|
chain_offsets |
dict[str, ndarray]
|
|
residual |
float
|
The median distance from a fitted joint to its keypoint over the exactly placed points only, in model units (ADR-0065). |
body_scale |
float
|
The recording's body size relative to the model, constant over the recording (ADR-0066). |
Source code in src/deeperfly/ik/__init__.py
KinematicTemplate
dataclass
¶
A loaded, validated kinematic template (the leg chains).
Source code in src/deeperfly/ik/template.py
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 | |
model_point_names
property
¶
The skeleton points the legs predict (every leg joint, in chain order).
from_spec
classmethod
¶
from_spec(
spec: dict,
*,
legs: list[str] | None = None,
bounds_overrides: dict[str, tuple[float, float]]
| None = None,
rest_axis: tuple[float, float, float] | None = None,
binding: Binding | None = None,
) -> KinematicTemplate
Expand a parsed template mapping into per-leg chains.
Source code in src/deeperfly/ik/template.py
load
classmethod
¶
load(
ref: str | Path = "neuromechfly",
*,
legs: list[str] | None = None,
bounds_overrides: dict[str, tuple[float, float]]
| None = None,
rest_axis: tuple[float, float, float] | None = None,
binding: Binding
| str
| Path
| None = "fly38@neuromechfly",
) -> KinematicTemplate
Load a template by name or path, with this run's restrictions applied.
legs keeps a subset, bounds_overrides maps flygym DOF names to degree limits
(case-insensitive, after the per-side defaults), and rest_axis comes from the
pack manifest (NeuroMechFly's -z when None). binding names the point
observing each joint; None leaves them empty, readable but not fittable.
Source code in src/deeperfly/ik/template.py
MissingQuickIK ¶
Bases: ImportError
Raised when inverse kinematics is asked for without the ik extra installed.
An ImportError, so a caller that can fall back to a stored fit catches it like any
optional import.
Model
dataclass
¶
A [ik] model resolved against one skeleton: the pack and its three assets.
Loaded as one unit, since a template from one pack with an articulation from
another would be silently wrong; the run config's bounds and markers are
applied.
Attributes:
| Name | Type | Description |
|---|---|---|
pack |
ModelPack
|
The manifest and its asset paths. |
binding |
Binding
|
The (skeleton, model) pairing. |
template |
KinematicTemplate
|
The leg chains. |
articulation |
Articulation | None
|
The baked head/abdomen chains, or |
Source code in src/deeperfly/ik/__init__.py
ModelMesh
dataclass
¶
The baked NeuroMechFly overlay mesh and the data to pose it from a fit.
Attributes:
| Name | Type | Description |
|---|---|---|
vertices |
Float[ndarray, 'Nv 3']
|
|
faces |
Int[ndarray, 'Nf 3']
|
|
face_rgb |
ndarray
|
|
vert_slot |
Int[ndarray, Nv]
|
|
slot_prox |
Int[ndarray, S]
|
|
slot_dist |
Int[ndarray, S]
|
|
slot_chain |
Int[ndarray, S]
|
|
slot_depth |
Int[ndarray, S]
|
|
kp_neutral |
Float[ndarray, 'P 3']
|
|
anchor_idx |
Int[ndarray, 6]
|
|
vert_part |
Int[ndarray, Nv]
|
|
part_names |
tuple[str, ...]
|
The body-part names, indexed by |
Source code in src/deeperfly/ik/mesh.py
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 | |
hidden_face_mask ¶
(Nf,) bool: faces belonging to a body part named in hide_parts.
An unknown name hides nothing.
Source code in src/deeperfly/ik/mesh.py
load
classmethod
¶
Read a baked mesh asset. Absent optional arrays fall back to "none".
Source code in src/deeperfly/ik/mesh.py
pose ¶
pose(
pts3d: Float[ndarray, "P 3"],
angles: Float[ndarray, D] | None = None,
angle_names: list[str] | None = None,
*,
chain_scales: dict[str, float] | None = None,
chain_offsets: dict[str, ndarray] | None = None,
body_scale: float | None = None,
) -> tuple[Float[np.ndarray, "Nv 3"], np.ndarray]
Pose the mesh to one frame's fitted joints (and optional chain angles).
pts3d places the rigid body and skins the leg bones. With angles (named by
angle_names) the head and abdomen nodes articulate; without, they ride the
body at their neutral pose. chain_scales, chain_offsets and body_scale
are the fit's (ADR-0066), so the drawn chains sit where the angles were fitted;
body_scale=None re-fits the scale per frame.
Returns:
| Name | Type | Description |
|---|---|---|
verts |
Float[ndarray, 'Nv 3']
|
|
valid_faces |
ndarray
|
|
Source code in src/deeperfly/ik/mesh.py
load_model ¶
load_model(
skeleton: Skeleton,
*,
model: str = "neuromechfly",
binding: str | None = None,
chains: Sequence[str] = (),
bounds: Mapping[str, Sequence[float]] | None = None,
markers: Mapping[str, Mapping[str, Any]] | None = None,
) -> Model
Load a body-model pack, bound to skeleton.
model is a packaged pack name or a path to its model.toml, and binding
defaults to the pair skeleton and the pack name; the rest are [ik]'s keys.
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If the pack or the binding cannot be found; an unbound pair names both halves. |
ValueError
|
If the model predicts a leg joint this skeleton does not track, which would leave that joint fitted to nothing. |
Source code in src/deeperfly/ik/__init__.py
load_model_mesh
cached
¶
Load (and cache) the packaged NeuroMechFly overlay mesh.
observations ¶
observations(
pts3d: ndarray, plan: BodyPlan, weights: ndarray | None
) -> tuple[np.ndarray, np.ndarray]
Gather the plan's observations: (T, N, 3) model positions and (T, N) weights.
QuickIK reads a missing observation (a joint that tracks no keypoint, a NaN, a non-positive weight) from its zero weight, and its position is filled with zero.
Source code in src/deeperfly/ik/__init__.py
solve ¶
solve(
pts3d: Float[ndarray, "T P 3"],
skeleton: Skeleton,
template: KinematicTemplate,
*,
articulation: Articulation | None = None,
weights: Float[ndarray, "T P"] | None = None,
iterations: int = 60,
neutral_weight: float = 0.001,
damping: float = 0.1,
position_tolerance: float = 0.001,
angle_tolerance: float = 0.001,
fixed_body: bool = True,
symmetric_segments: bool = True,
absent_points: Bool[ndarray, "T P"] | None = None,
approximate_points: Sequence[str] | None = None,
) -> IkResult
Fit the model's joint angles to a 3D pose sequence.
pts3d is in world coordinates, in skeleton's point order. articulation adds
the head and abdomen chains; None fits the legs only. weights are
per-observation, and a non-finite or non-positive one marks the point unobserved.
absent_points declares keypoints that are not on this animal, per frame or for
the whole recording, and approximate_points the ones placed by a modeling
decision, read off articulation when None (ADR-0065). The rest are [ik]'s
keys.
Raises:
| Type | Description |
|---|---|
MissingQuickIK
|
If the optional |
Source code in src/deeperfly/ik/__init__.py
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 | |
unfittable_branches ¶
unfittable_branches(
obs_weights: Float[ndarray, "T N"],
plan: BodyPlan,
*,
never_placed: frozenset[str] = frozenset(),
absent_points: Bool[ndarray, "T P"] | None = None,
) -> dict[str, np.ndarray]
branch -> (T,) bool frames whose observations cannot support a fit.
A limb needs _MIN_VALID_JOINTS observed keypoints in a frame, and a leg in
never_placed (its coxa was never seen) is fitted in no frame. absent_points,
the (T, P) or (P,) declaration of keypoints not on this animal, lowers a
branch's bar to what it can still determine; a missed detection never does, and a
chain's base landmark never counts (ADR-0065).
Source code in src/deeperfly/ik/__init__.py
The model pack: the mechanical model the IK stage fits, selected by name.
A directory holding a model.toml manifest, the three assets it names (the leg
template, the baked articulation, the overlay mesh) and the model's rest axis and
registration anchors. A pack names no skeleton point: that is the binding's
(ADR-0041), and a test checks it.
ModelPack
dataclass
¶
A loaded model manifest: the three asset paths plus the model-wide facts.
Source code in src/deeperfly/ik/pack.py
load
classmethod
¶
Load a pack by packaged name, or by path to its model.toml or directory.
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If the pack cannot be found, or its manifest names an asset that is not beside it. |
Source code in src/deeperfly/ik/pack.py
The binding: where each tracked point of one skeleton sits on one model (ADR-0041).
A fact about the (skeleton, model) pair, so it is a file of its own,
data/bindings/<skeleton>@<model>.toml, and the only artifact where a skeleton point
name and a model body name appear together (a test checks it). One row per point,
lf_pretarsus = { body = "lf_tarsus5", offset = [0.0, 0.0, -0.107] }, the offset in
the body's frame; base = true nominates a chain's base landmark and
approximate = true a placement that is a modeling decision (ADR-0065).
Binding
dataclass
¶
A loaded binding: the rows, in the file's order, plus both halves' names.
Source code in src/deeperfly/ik/binding.py
approximate
property
¶
The points whose placement is a modelling decision, not a measurement.
load
classmethod
¶
Load a binding by "<skeleton>@<model>" or by path.
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If the pair is not packaged and the path does not exist; the message names both halves. |
ValueError
|
If two rows claim one body at its origin. |
Source code in src/deeperfly/ik/binding.py
origin_point_for ¶
The point sitting exactly on body's origin, or None.
The stricter lookup, for the registration anchors: a point at an offset on a body does not observe its position.
Source code in src/deeperfly/ik/binding.py
point_for ¶
The single point attached to body, or None (none, or more than one).
What a leg joint resolves through; a pretarsus resolves despite its offset, which is mesh geometry while the segment length is measured.
Source code in src/deeperfly/ik/binding.py
row ¶
The row for a tracked point, or None when the binding names it nowhere.
BindingRow
dataclass
¶
One tracked point's placement on the model.
Source code in src/deeperfly/ik/binding.py
at_body_origin
property
¶
Whether the point sits exactly on the body's origin (its parent joint).
bindings ¶
The packaged bindings, "<skeleton>@<model>" -> file.
The kinematic template: the NeuroMechFly-style leg model the IK fits.
A KinematicTemplate is what to fit, independent of a recording: the per-leg chains
of revolute DOFs with their axes and limits, and the model body each joint sits at.
Segment lengths are measured per recording (ik.align). The
packaged data/models/neuromechfly/template.toml (from
NeLy-EPFL/sequential-inverse-kinematics' body_config) expands one generic leg over
the six; a run overrides any bound from [ik.bounds].
Dof
dataclass
¶
One revolute degree of freedom: a rotation about axis bounded to [lo, hi].
axis is in the model frame and the limits are in radians. A DOF marked mirror
has its axis negated on the right side, as the model's is, so a fitted angle is
flygym's own on both sides.
Source code in src/deeperfly/ik/template.py
Joint
dataclass
¶
A joint in a leg chain: the model body it sits at, and its DOFs.
point is the tracked point observing it, resolved through the binding. segment
names the segment leading into it ("" for the root thorax-coxa), measured at
solve time. joint is flygym's <parent_body>-<child_body>. quat is the
constant (w, x, y, z) rotation from the parent's post-DOF frame, identity on
NeuroMechFly and not on flybody.
Source code in src/deeperfly/ik/template.py
KinematicTemplate
dataclass
¶
A loaded, validated kinematic template (the leg chains).
Source code in src/deeperfly/ik/template.py
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 | |
model_point_names
property
¶
The skeleton points the legs predict (every leg joint, in chain order).
from_spec
classmethod
¶
from_spec(
spec: dict,
*,
legs: list[str] | None = None,
bounds_overrides: dict[str, tuple[float, float]]
| None = None,
rest_axis: tuple[float, float, float] | None = None,
binding: Binding | None = None,
) -> KinematicTemplate
Expand a parsed template mapping into per-leg chains.
Source code in src/deeperfly/ik/template.py
load
classmethod
¶
load(
ref: str | Path = "neuromechfly",
*,
legs: list[str] | None = None,
bounds_overrides: dict[str, tuple[float, float]]
| None = None,
rest_axis: tuple[float, float, float] | None = None,
binding: Binding
| str
| Path
| None = "fly38@neuromechfly",
) -> KinematicTemplate
Load a template by name or path, with this run's restrictions applied.
legs keeps a subset, bounds_overrides maps flygym DOF names to degree limits
(case-insensitive, after the per-side defaults), and rest_axis comes from the
pack manifest (NeuroMechFly's -z when None). binding names the point
observing each joint; None leaves them empty, readable but not fittable.
Source code in src/deeperfly/ik/template.py
LegChain
dataclass
¶
One leg's serial chain (ThC -> CTr -> FTi -> TiTa -> Pretarsus).
Source code in src/deeperfly/ik/template.py
point_names
property
¶
The tracked point marking each joint, in chain order.
The baked head and abdomen chains the IK fits beyond the legs.
Their joints are not keypoints (a neck pivot, the abdominal hinges), so their geometry
comes from the model, baked once from the NeuroMechFly MJCF into the pack's
articulation.json. The head is a 3-DOF chain carrying the antenna bases, the abdomen a
5-DOF pitch chain carrying the abdomen markers. This module loads that geometry and
measures a chain's size (ADR-0066); the forward kinematics are
ik.forward's.
Articulation
dataclass
¶
The loaded head/abdomen chains plus the anchor bodies that register the body.
anchors names model bodies (from the pack manifest) and anchor_neutral holds
their neutral positions, read out of bodies, which maps each body a marker may
attach to onto its neutral frame and chain depth. leg_rest is every leg DOF's
spring reference in radians, the plan's neutral (ADR-0065); empty for an asset
without one, which falls back to zero.
Source code in src/deeperfly/ik/articulation.py
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | |
anchor_points
property
¶
The tracked point observing each anchor body, from the binding.
Empty where the binding puts no point on an anchor's origin; the registration needs only three finite anchors.
chain ¶
load
classmethod
¶
load(
ref: str | Path = DEFAULT_ARTICULATION_PATH,
*,
binding: Binding
| str
| Path
| None = "fly38@neuromechfly",
anchors: tuple[str, ...] = _DEFAULT_ANCHORS,
fit: tuple[str, ...] | None = None,
bounds_overrides: dict[str, tuple[float, float]]
| None = None,
marker_overrides: dict[str, dict[str, dict]]
| None = None,
) -> Articulation
Load the baked articulation, with this run's restrictions applied.
binding's rows on the chain bodies are the chains' markers and its rows on
the anchor bodies say which point observes each; None loads the structure
with no markers, readable but not fittable. fit keeps a subset of the
chains, bounds_overrides maps flygym angle names to degree limits
(case-insensitive), and marker_overrides replaces a chain's binding rows
([ik.markers]).
Source code in src/deeperfly/ik/articulation.py
Chain
dataclass
¶
One baked articulation chain (the head or the abdomen).
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
|
dof_names |
tuple[str, ...]
|
|
anchors, |
axes
|
|
bounds |
tuple[ndarray, ndarray]
|
|
marker_names |
tuple[str, ...]
|
|
marker_neutral |
ndarray
|
|
marker_depth |
tuple[int, ...]
|
|
marker_approximate |
tuple[bool, ...]
|
|
base_point |
str | None
|
The marker that measures where this chain's base sits, or |
Source code in src/deeperfly/ik/articulation.py
marker_index ¶
The column of point in this chain's marker arrays, or None.
body_similarity ¶
body_similarity(
neutral_anchors: ndarray, measured_anchors: ndarray
) -> tuple[np.ndarray, float, np.ndarray] | None
Similarity transform (R, s, t) from the neutral anchors onto the measured.
measured_anchors is (N, 3) (NaN where an anchor was not observed); at least
three finite anchors are needed. Returns None when too few are available.
Source code in src/deeperfly/ik/articulation.py
calibrate_chain ¶
calibrate_chain(
local_markers: ndarray,
chain: Chain,
*,
seed_scale: float,
base_shift: ndarray | None = None,
fit_root: bool = False,
n_frames: int = _CALIBRATION_FRAMES,
clamp: tuple[float, float] = (0.3, 3.0),
) -> tuple[float, np.ndarray]
A chain's size (and, optionally, its root) from its whole marker set.
Fits the chain's angles and size together against every marker, posture being the
nuisance parameter, seeded from seed_scale (the ruler) and reduced by the median
over n_frames sampled frames. fit_root also frees the root on top of
base_shift, for a chain with no base landmark; its lateral component is not
fitted on a midline chain. The measurements and the null direction: ADR-0066.
Returns:
| Name | Type | Description |
|---|---|---|
scale |
float
|
The median fitted size, clamped. |
shift |
ndarray
|
|
Source code in src/deeperfly/ik/articulation.py
445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 | |
chain_affine ¶
chain_affine(
chain, depth: int, theta: Float[ndarray, D]
) -> tuple[Float[np.ndarray, "3 3"], Float[np.ndarray, 3]]
The model-frame affine (A, b) carrying a depth-d point: A p + b.
The baked chains rotate about their neutral world anchors: A_d = A_{d-1} R_d and
b_d = A_{d-1}(c_d - R_d c_d) + b_{d-1}, so at zero angles every point stays at its
neutral position.
Source code in src/deeperfly/ik/forward.py
chain_markers ¶
chain_markers(
chain: Chain,
angles: ndarray,
scale: float,
shift: ndarray | None = None,
) -> np.ndarray
(M, 3) model-frame marker positions of a chain at angles, grown and shifted.
The one place the size and root conventions meet chain_fk: composing the rotations by hand in the other order is invisible on the abdomen, whose axes commute, and off by ~0.9 model units on the head.
Source code in src/deeperfly/ik/articulation.py
estimate_chain_scale ¶
estimate_chain_scale(
local_markers: ndarray,
chain: Chain,
*,
clamp: tuple[float, float] = (0.3, 3.0),
warn_if_unmeasurable: bool = True,
) -> float
A chain's size relative to the model, from lengths its joints cannot change.
The median over frames of the least-squares scale sum(D d) / sum(D^2) over the
chain's rigid rulers, with mirror pairs folded to their midpoint first; the
rulers, the fold and why the abdomen has none are ADR-0066. It seeds
calibrate_chain, which a fit uses. local_markers is the chain's markers in
the model frame (R^T (world - t) / s), NaN where unobserved. Returns 1.0 when
no ruler was observed, with a warning unless warn_if_unmeasurable is false, as
wherever a fitted size follows.
Source code in src/deeperfly/ik/articulation.py
load_articulation
cached
¶
load_articulation(
ref: str | Path = DEFAULT_ARTICULATION_PATH,
fit: tuple[str, ...] | None = None,
binding: str | Path | None = "fly38@neuromechfly",
) -> Articulation
Load (and cache) an articulation with no bounds overrides.
The default is the packaged pair, which is what the overlay and the leg spring
references read; a run resolves its own from [ik] binding.
Source code in src/deeperfly/ik/articulation.py
umeyama ¶
Least-squares similarity transform (rotation, scale, translation) src -> dst.
Umeyama (1991). Shared with ik.mesh, which fits the same transform to register a mesh's anchor vertices.
Source code in src/deeperfly/ik/articulation.py
Measure the per-recording leg geometry the kinematic template is built around.
body_alignment takes what does not change over a recording: each leg's coxa origin (the median thorax-coxa) and its measured segment lengths, optionally shared with its mirror image (ADR-0066). It measures in the model frame, where the body holds still: solve registers a free body there frame by frame (ADR-0098).
Alignment
dataclass
¶
The per-leg geometry registering a recording to the template.
Source code in src/deeperfly/ik/align.py
body_alignment ¶
body_alignment(
pts3d: ndarray,
skeleton: Skeleton,
template: KinematicTemplate,
*,
symmetric_segments: bool = False,
) -> Alignment
Estimate the per-leg origins and segment lengths from the pose (ADR-0066).
pts3d is the pose in the model frame, as solve registers
it; a pose the body moves in has no single coxa origin to measure.
Source code in src/deeperfly/ik/align.py
mirror_leg_pairs ¶
The template's legs paired with their mirror images, per the skeleton.
Two legs pair when every joint of one mirrors the joint at the same depth of the
other by the skeleton's symmetries, not by the l/r prefix: a skeleton that
declares none says its subject is not bilaterally symmetric. Each pair comes once,
in template order, with the partner second.
Source code in src/deeperfly/ik/align.py
symmetrize_seglens ¶
symmetrize_seglens(
seglens: dict[str, ndarray],
template: KinematicTemplate,
skeleton: Skeleton,
) -> dict[str, np.ndarray]
Give each leg and its mirror image one shared length per segment (ADR-0066).
The shared length is the mean of the two sides' medians. A segment measured on one
side only takes that side's length; one measured on neither stays 0.0 for the
model fallback. Returns a new mapping.
Source code in src/deeperfly/ik/align.py
Build the QuickIK body plan [ik] solves.
A body plan is a JSON kinematic tree in which every joint is a tracked keypoint, with
its offset from its parent, its ordered DOFs and any zero-DOF pseudo-joints for
keypoints mid-segment. build_body_plan assembles one per recording from the leg
template, the baked chains and the recording's measured geometry, all in the model
frame: the rig's world frame is about 23 degrees of pitch and a scale away from the
model's, and QuickIK is f32, which wants order-1 coordinates. A DOF's neutral is
the model's spring reference (ADR-0065).
BodyPlan
dataclass
¶
A generated QuickIK body plan plus the bookkeeping its caller needs.
Attributes:
| Name | Type | Description |
|---|---|---|
plan |
dict
|
The body-plan mapping, JSON-serializable (see to_json). |
joint_names |
tuple[str, ...]
|
|
joint_point |
tuple[str | None, ...]
|
|
joint_row |
ndarray
|
|
joint_branch, |
dof_branch
|
|
angle_names |
tuple[str, ...]
|
|
neutral |
ndarray
|
|
body_sim |
tuple[ndarray, float, ndarray]
|
|
chain_scales |
dict[str, float]
|
|
chain_offsets |
dict[str, ndarray]
|
|
Source code in src/deeperfly/ik/bodyplan.py
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | |
fixed_base
property
¶
Whether the body is held in the model frame instead of fitted per frame.
joint_is_base
property
¶
(N,) which joints are their chain's base landmark.
A base landmark sits on its chain's rotation center, so it is no evidence about the chain's angles (ADR-0065). Read off the plan's own joints, so a plan rebuilt from JSON agrees; not "no DOF moves this joint", which a fixed base's thorax-coxae also satisfy.
kinematics ¶
to_json ¶
The plan as JSON, what QuickIK parses.
No default= fallback, so a stray numpy value raises instead of being
stringified. The x-deeperfly block describes what built the plan; QuickIK and
deeperfly both ignore it.
Source code in src/deeperfly/ik/bodyplan.py
to_model ¶
World points -> the model frame: R^T (p - t) / s.
Source code in src/deeperfly/ik/bodyplan.py
build_body_plan ¶
build_body_plan(
template: KinematicTemplate,
skeleton,
alignment: Alignment,
body_sim: tuple[ndarray, float, ndarray],
*,
articulation: Articulation | None = None,
chain_scales: dict[str, float] | None = None,
chain_offsets: dict[str, ndarray] | None = None,
fixed_body: bool = True,
) -> BodyPlan
Assemble the body plan for one recording.
alignment is the measured leg geometry in the model frame, body_sim maps that
frame onto the world frame, and articulation adds the head/abdomen chains
(None builds a legs-only plan). chain_scales and chain_offsets grow and
shift each chain about its base (ADR-0066); a missing entry is model size on the
registered base. fixed_body holds the root in the model frame (a tethered fly)
instead of giving QuickIK a 6-DOF root per frame.
Source code in src/deeperfly/ik/bodyplan.py
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 | |
Forward kinematics in numpy: body plans, baked chains, and legs.
QuickIK's Python bindings return only angles and a root pose, and every consumer of
the fit needs joint positions, so deeperfly evaluates them here. PlanKinematics
walks a body plan by QuickIK's own convention (src/forward.rs): a joint's
offset_pos applies in its parent's frame after the parent's DOFs, its own DOFs
compose in listed order, and its keypoint sits at its origin before them.
chain_affine and chain_fk are the baked chains' kinematics and leg_fk a
leg's. Pure numpy, so a stored fit draws without the solver (ADR-0041).
PlanKinematics
dataclass
¶
A QuickIK body plan compiled for numpy forward kinematics over many frames.
Attributes:
| Name | Type | Description |
|---|---|---|
names |
tuple[str, ...]
|
|
parent |
ndarray
|
|
order |
ndarray
|
|
offset_pos |
ndarray
|
|
offset_quat |
ndarray
|
|
dof_start |
ndarray
|
|
dof_count |
ndarray
|
|
dof_axis |
ndarray
|
|
dof_hinge |
ndarray
|
|
Source code in src/deeperfly/ik/forward.py
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 | |
dof_names ¶
The x-deeperfly-angle label of every DOF, in flat DOF order.
QuickIK's parser ignores a DOF's own name, so the plan carries the flygym
angle name in an x- key.
Source code in src/deeperfly/ik/forward.py
from_plan
classmethod
¶
Compile a body-plan mapping (the parsed JSON) for evaluation.
Source code in src/deeperfly/ik/forward.py
joint_positions ¶
joint_positions(
dof_angles: Float[ndarray, "T D"],
root_pos: Float[ndarray, "T 3"] | None = None,
root_rot: Float[ndarray, "T 4"] | None = None,
) -> Float[np.ndarray, "T N 3"]
Every joint's position for a batch of poses, as QuickIK's evaluate_fwdkin.
The root frame is (root_pos, root_rot), identity when None (a fixed base).
A NaN angle makes its joint and everything distal NaN, so an unfittable limb
stays visibly absent. A single (D,) pose returns (1, N, 3).
Source code in src/deeperfly/ik/forward.py
axis_rmat ¶
axis_rmat(
axis: Float[ndarray, 3],
angle: float | Float[ndarray, "*batch"],
) -> Float[np.ndarray, "*batch 3 3"]
Rotation(s) by angle about the fixed unit axis, in Rodrigues' sin/cos form.
Smooth at angle = 0, where a joint routinely sits, unlike the axis-angle form.
Source code in src/deeperfly/ik/forward.py
chain_affine ¶
chain_affine(
chain, depth: int, theta: Float[ndarray, D]
) -> tuple[Float[np.ndarray, "3 3"], Float[np.ndarray, 3]]
The model-frame affine (A, b) carrying a depth-d point: A p + b.
The baked chains rotate about their neutral world anchors: A_d = A_{d-1} R_d and
b_d = A_{d-1}(c_d - R_d c_d) + b_{d-1}, so at zero angles every point stays at its
neutral position.
Source code in src/deeperfly/ik/forward.py
chain_fk ¶
chain_fk(
anchors: Float[ndarray, "D 3"],
axes: Float[ndarray, "D 3"],
depths: tuple[int, ...],
angles: Float[ndarray, D],
markers: Float[ndarray, "M 3"],
) -> Float[np.ndarray, "M 3"]
A baked chain's posed markers, chain_affine at each marker's depth.
Source code in src/deeperfly/ik/forward.py
leg_fk ¶
leg_fk(
angles: Float[ndarray, D],
axes: Float[ndarray, "D 3"],
seglens: Float[ndarray, J],
dof_counts: tuple[int, ...],
*,
rest_axis: Float[ndarray, 3] | None = None,
quats: Float[ndarray, "J 4"] | None = None,
) -> Float[np.ndarray, "J 3"]
One leg's joint positions in the leg-local frame, from its joint angles.
Each joint enters its parent's post-DOF frame through quats (identity when
None, as for NeuroMechFly) and rotates about its DOF axes in order, and the next
segment runs along rest_axis; all angles zero is the rest pose, by the body
plan's own convention. seglens[0] is unused (the root sits at the origin), and
dof_counts is the DOFs per joint ((3, 2, 1, 1, 0)).
Source code in src/deeperfly/ik/forward.py
quat_to_rmat ¶
Rotation matrix of a quaternion given as (w, x, y, z) (the body-plan order).
Source code in src/deeperfly/ik/forward.py
rmat_to_quat ¶
Quaternion (w, x, y, z) of a rotation matrix (Shepperd's method).
Picks the branch off the largest diagonal term rather than always solving for w,
so it stays conditioned at a 180-degree rotation (where w -> 0).
Source code in src/deeperfly/ik/forward.py
Pose the bundled NeuroMechFly mesh to a fitted pose, for the 2D overlay.
The pack's baked mesh.npz is posed straight from a fit's joint positions and chain
angles, with no MuJoCo at runtime. A leg bone is mapped from its neutral endpoints
onto the live ones, its roll fixed by the body's dorsal axis; the head and abdomen
nodes follow their chains' forward kinematics; the rigid body is one similarity fit
to the six thorax-coxa keypoints. ModelMesh.pose returns one frame's vertices.
ModelMesh
dataclass
¶
The baked NeuroMechFly overlay mesh and the data to pose it from a fit.
Attributes:
| Name | Type | Description |
|---|---|---|
vertices |
Float[ndarray, 'Nv 3']
|
|
faces |
Int[ndarray, 'Nf 3']
|
|
face_rgb |
ndarray
|
|
vert_slot |
Int[ndarray, Nv]
|
|
slot_prox |
Int[ndarray, S]
|
|
slot_dist |
Int[ndarray, S]
|
|
slot_chain |
Int[ndarray, S]
|
|
slot_depth |
Int[ndarray, S]
|
|
kp_neutral |
Float[ndarray, 'P 3']
|
|
anchor_idx |
Int[ndarray, 6]
|
|
vert_part |
Int[ndarray, Nv]
|
|
part_names |
tuple[str, ...]
|
The body-part names, indexed by |
Source code in src/deeperfly/ik/mesh.py
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 | |
hidden_face_mask ¶
(Nf,) bool: faces belonging to a body part named in hide_parts.
An unknown name hides nothing.
Source code in src/deeperfly/ik/mesh.py
load
classmethod
¶
Read a baked mesh asset. Absent optional arrays fall back to "none".
Source code in src/deeperfly/ik/mesh.py
pose ¶
pose(
pts3d: Float[ndarray, "P 3"],
angles: Float[ndarray, D] | None = None,
angle_names: list[str] | None = None,
*,
chain_scales: dict[str, float] | None = None,
chain_offsets: dict[str, ndarray] | None = None,
body_scale: float | None = None,
) -> tuple[Float[np.ndarray, "Nv 3"], np.ndarray]
Pose the mesh to one frame's fitted joints (and optional chain angles).
pts3d places the rigid body and skins the leg bones. With angles (named by
angle_names) the head and abdomen nodes articulate; without, they ride the
body at their neutral pose. chain_scales, chain_offsets and body_scale
are the fit's (ADR-0066), so the drawn chains sit where the angles were fitted;
body_scale=None re-fits the scale per frame.
Returns:
| Name | Type | Description |
|---|---|---|
verts |
Float[ndarray, 'Nv 3']
|
|
valid_faces |
ndarray
|
|
Source code in src/deeperfly/ik/mesh.py
load_model_mesh
cached
¶
Load (and cache) the packaged NeuroMechFly overlay mesh.
Access to the optional QuickIK solver, behind one explicit gate.
QuickIK publishes no wheels, so the deeperfly[ik] extra builds a Rust extension and a
plain install stays Rust-free (ADR-0041). Everything that solves calls
require_quickik inside a function body, never at module scope; the body plan, the
forward kinematics and the mesh import nothing from here, so a stored fit renders
without the extra.
MissingQuickIK ¶
Bases: ImportError
Raised when inverse kinematics is asked for without the ik extra installed.
An ImportError, so a caller that can fall back to a stored fit catches it like any
optional import.
require_quickik
cached
¶
The quickik module, or MissingQuickIK saying how to install it.