pose2d¶
Turn video into (x, y) keypoint peaks per (view, point). The subsystem is modular:
swappable backbones and heads are composed by an assembler into a detector, with the field
geometry and confidence shared across families. The networks -- which run externally-trained
checkpoints -- stay behind a torch-free seam so import deeperfly.pose2d never imports
torch; the seam and the networks install with the detect extra.
The 2D keypoint detectors and their heatmap decode.
Backbones and heads are composed by detect into a detector
(ADR-0008, ADR-0010, ADR-0011), with the field geometry and the confidence shared
across families. Torch stays behind a seam, imported lazily, so
import deeperfly.pose2d needs no torch (ADR-0009); only the torch-free surface is
re-exported here. A window, a resize and the input-normalized space are how a detector
reads a frame and stay inside the stage: every point it hands out is in the raw frame
of its video (ADR-0076).
FieldGeometry
dataclass
¶
The affine between a heatmap field and input-normalized (x, y).
Field cell c is input pixel c * stride - origin, divided by the input size.
Attributes:
| Name | Type | Description |
|---|---|---|
input_hw |
tuple[int, int]
|
The reported frame's |
stride |
float
|
Input pixels per field cell. |
origin_xy |
tuple[float, float]
|
The input-pixel location |
field_hw |
tuple[int, int]
|
Padded field size in cells, |
pad_px |
tuple[int, int]
|
|
Source code in src/deeperfly/pose2d/geometry.py
padded
classmethod
¶
The grid for an input of input_hw extended by margin on every side.
margin is a fraction of the input, rounded to whole cells.
Source code in src/deeperfly/pose2d/geometry.py
padded_px
classmethod
¶
The grid for an input extended by pad_px = (y, x) pixels on every side.
Pixels rather than a fraction, so the two axes can pad the same amount; each pad must be a whole number of cells.
Source code in src/deeperfly/pose2d/geometry.py
to_field_cells ¶
Input-normalized (..., 2) -> field cells in (x, y): the inverse.
The map training targets render against; torch or NumPy.
Source code in src/deeperfly/pose2d/geometry.py
to_input_normalized ¶
Field cells (..., 2) in (x, y) -> input-normalized; torch or NumPy.
Source code in src/deeperfly/pose2d/geometry.py
Device, dtype and tensor plumbing shared by every detector class.
It imports torch at module scope, behind the torch-free seam, so
import deeperfly.pose2d never imports it.
accumulation ¶
Float16 matrix products accumulate in dtype inside the block (ADR-0150).
"float16" sets torch's process-wide allow_fp16_accumulation and puts back what
it was on the way out; "float32", or a torch without the switch, changes nothing.
Source code in src/deeperfly/pose2d/runtime.py
as_torch ¶
Network inputs as a torch tensor, on-device when possible.
A torch tensor passes through, another DLPack-capable array is bridged zero-copy, and host NumPy is copied to a writable tensor.
Source code in src/deeperfly/pose2d/runtime.py
autocast_dtype ¶
The autocast dtype for the forward, or None to run in float32.
Only on CUDA, CPU and MPS staying on the float32 reference; "auto" is bfloat16
with tensor cores and float16 below.
Source code in src/deeperfly/pose2d/runtime.py
detector_device ¶
The device the detector's parameters live on ("cuda:0", "cpu").
Read off the parameters, so torch is never imported; "cpu" for a parameterless
model.
Source code in src/deeperfly/pose2d/runtime.py
device ¶
The best torch device available: "cuda", "mps" (Apple Metal) or "cpu".
has_bf16_tensor_cores ¶
Whether the CUDA device runs bfloat16 on tensor cores (capability 8.0 and up).
Not is_bf16_supported, which answers true on Turing, where bfloat16 is emulated
(ADR-0069).
Source code in src/deeperfly/pose2d/runtime.py
set_precision ¶
Record the forward precision on the detector, where the forward reads it.
"float32" (the reference), "float16" or "bfloat16" (CUDA autocast), or
"auto", resolved per card at forward time (ADR-0069).
Source code in src/deeperfly/pose2d/runtime.py
The released detectors: which exist, and each one's copy in the cache (ADR-0151).
RELEASED is the catalog this deeperfly ships with (data/released.toml): the
detectors and locators published on Hugging Face, each pinned to a revision of the
repository and to the SHA-256 of its checkpoint. A config names one where it would
otherwise give a path, and artifact_file
turns either into a file, downloading a model the cache does not hold yet
(ADR-0153). Only download reaches the network;
deeperfly models download calls it ahead of a run, and deeperfly models remove
deletes what it fetched (remove). Torch-free.
Released
dataclass
¶
One released model: a folder of the repository and the checkpoint in it.
Source code in src/deeperfly/pose2d/released.py
artifact_file ¶
The file a detector reference names: a released model's, or the path itself.
A released model the cache does not hold yet is downloaded first (download), so the first use of one needs a network.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If it names a released model that cannot be downloaded. |
Source code in src/deeperfly/pose2d/released.py
download ¶
Fetch the released models named into the cache; their files.
A model's whole folder comes with it: the checkpoint, its README.md, the
training recipe and report. One already there is not fetched again, and every
checkpoint is checked against the catalog's SHA-256 either way.
Raises:
| Type | Description |
|---|---|
KeyError
|
If a name is not a released model. |
RuntimeError
|
If the |
Source code in src/deeperfly/pose2d/released.py
remove ¶
Delete the released models named from the cache; the bytes each held.
A model's whole folder goes, and a model the cache does not hold is left out of
the result. A models/ that is a link to a shared copy is refused, as
clear leaves what a link points at alone; that is also
what deletes every model at once.
Raises:
| Type | Description |
|---|---|
KeyError
|
If a name is not a released model. |
RuntimeError
|
If |
Source code in src/deeperfly/pose2d/released.py
The padded-field affine: heatmap cells <-> input-normalized coordinates.
A heatmap field may extend past the input by a margin, so a joint pushed off the frame
still has a cell to peak in. FieldGeometry is the one place that padding is undone,
for the monocular grid (stride 4) and the multiview net's upsampled one (stride 1).
Coordinates are unclamped: outside [0, 1] is an off-frame joint, which is signal.
FieldGeometry
dataclass
¶
The affine between a heatmap field and input-normalized (x, y).
Field cell c is input pixel c * stride - origin, divided by the input size.
Attributes:
| Name | Type | Description |
|---|---|---|
input_hw |
tuple[int, int]
|
The reported frame's |
stride |
float
|
Input pixels per field cell. |
origin_xy |
tuple[float, float]
|
The input-pixel location |
field_hw |
tuple[int, int]
|
Padded field size in cells, |
pad_px |
tuple[int, int]
|
|
Source code in src/deeperfly/pose2d/geometry.py
padded
classmethod
¶
The grid for an input of input_hw extended by margin on every side.
margin is a fraction of the input, rounded to whole cells.
Source code in src/deeperfly/pose2d/geometry.py
padded_px
classmethod
¶
The grid for an input extended by pad_px = (y, x) pixels on every side.
Pixels rather than a fraction, so the two axes can pad the same amount; each pad must be a whole number of cells.
Source code in src/deeperfly/pose2d/geometry.py
to_field_cells ¶
Input-normalized (..., 2) -> field cells in (x, y): the inverse.
The map training targets render against; torch or NumPy.
Source code in src/deeperfly/pose2d/geometry.py
to_input_normalized ¶
Field cells (..., 2) in (x, y) -> input-normalized; torch or NumPy.
Source code in src/deeperfly/pose2d/geometry.py
Per-point confidence strategies, and the name-keyed registry over them.
A detector records its strategy as confidence_kind. peak_amax is the raw peak
value, cheap but blind to a broad peak; softmax_window_mass the softmax mass near the
peak (the soft-argmax decode's); peak_mass the same on a probability field itself
(the DARK decode's), about 0.85 for one blob of the training sigma. Torch is imported
lazily.
peak_amax ¶
peak_mass ¶
Probability mass within radius cells of each channel's peak, in [0, 1].
Source code in src/deeperfly/pose2d/confidence.py
peak_mass_at ¶
peak_mass at the given cells, for the candidate path.
prob is (N, H, W) and cells integer (N, M, 2) in (x, y); the result is
(N, M). One box filter over the field, gathered at the cells.
Source code in src/deeperfly/pose2d/confidence.py
softmax_window_mass ¶
softmax_window_mass(
prob,
locs,
*,
sigma: float = WINDOW_SIGMA,
num_stds: float = WINDOW_NUM_STDS,
)
The softmax mass within floor(sigma * num_stds) cells of each peak.
prob is a (B, C, H, W) spatial softmax and locs the (B, C, 2) peaks in
(x, y) cells, fractional for an expectation; the window is centered on the
nearest cell, since a cell's coordinate is its center (ADR-0055). The result is
(B, C) in [0, 1].
Source code in src/deeperfly/pose2d/confidence.py
The backbone-head coupling: feature maps selected by output stride.
A head names its levels by stride, not index: HRNet returns a stride-2 stem at index 0 where HGNetV2 and ResNet start at stride 4, so an index would feed the head the wrong maps and evaluate a plausible, wrong model.
select_by_stride ¶
select_by_stride(
feature_info,
want_reductions: tuple[int, ...],
*,
pinned: tuple[int, ...] | None = None,
name: str = "",
) -> tuple[tuple[int, ...], tuple[int, ...]]
(indices, channels) of feature_info's levels at want_reductions.
pinned checks the widths trained weights assume (None accepts timm's, which
the strict=True load then confirms); name is for the error message.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If a wanted stride is absent, or |
Source code in src/deeperfly/pose2d/backbones/base.py
The CNN pyramid backbone: a timm features_only net at strides 4 to 32.
create returns the raw model, bound unwrapped for strict=True loading, and the
levels a head needs; forward_features returns them finest first, NCHW.
create ¶
Build the timm backbone and its stride selection.
in_chans is 1 for grayscale video. Returns (model, sel, channels): the model,
the feature_info indices for WANT_REDUCTIONS, and each level's width.
Source code in src/deeperfly/pose2d/backbones/cnn_pyramid.py
forward_features ¶
Run model on x: the selected feature maps, finest first, NCHW.
NHWC backbones (swin, maxvit) are detected from the tensor and permuted.
Source code in src/deeperfly/pose2d/backbones/cnn_pyramid.py
The multiview ViT backbone: every view of a frame encoded together.
A timm ViT whose blocks follow a cross-view attention schedule, a regrouping of the
token axis between blocks with no per-view parameters: view-local ("l", tokens
(B*V, P + N, dim)) or global ("g", (B, V*P + V*N, dim), every view's prefix
tokens first). A rope backbone (DINOv3) gets its table tiled V times, so a patch has
one position in every view. With no per-view parameters the function is
permutation-equivariant over views and V is free; padded views (view_valid) are
masked out of every global block's keys.
create ¶
create(
backbone: str,
*,
input_hw: tuple[int, int],
attn_scopes: str,
pretrained: bool = False,
drop_path_rate: float = 0.0,
)
Build the timm ViT and validate the schedule against its block count.
The backbone sees the reported frame only; the margin is added in the head. The
pretrained prefix tokens (class token, registers) are kept, since timm's
pretrained load assumes them, and dropped in encode. Returns
(model, dim, patch): the model, its embed dim and its (ph, pw) patch.
Source code in src/deeperfly/pose2d/backbones/vit_multiview.py
encode ¶
encode(
model,
attn_scopes,
images,
dim,
*,
norm_mean=0.0,
norm_std=1.0,
view_valid=None,
mask_token=None,
mask_ratio: float = 0.0,
pad_tokens: tuple[int, int] = (0, 0),
pad_token=None,
global_rope: bool = True,
checkpoint: bool = False,
)
(B, V, 1, H, W) synchronized views -> (B*V, dim, Hp, Wp), batch-major in V.
The patch and positional embeds run per view, then the block schedule regroups the
tokens at each "l"/"g" transition; the prefix tokens are dropped after the final
norm. mask_ratio (a number or a (B*V,) tensor) replaces that fraction of each
view's patches with mask_token before the positional embed, so a masked patch
keeps its position and loses its content. pad_tokens (py, px) of pad_token
surround each view's grid before the blocks, giving the margin cells attention can
write. global_rope=False withholds the rope from the global blocks, and
checkpoint recomputes each block in the backward pass.
Source code in src/deeperfly/pose2d/backbones/vit_multiview.py
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 | |
The heatmap heads: a 2D field per point, and its sub-pixel readouts.
concat (multi-scale laterals at stride 4, one 3x3 block) and unet (a coarse-to-fine
decoder) are the monocular heads, read out by argmax_parabola; upsample_head is the
probability-field head of mvt and fast, read out by dark_decode. The factories return
the modules the assembler binds, so a checkpoint loads strict=True with no key
translation.
argmax_parabola ¶
Hard peak and parabolic sub-pixel refinement, (..., 2) (x, y) in cells.
The second difference is clamped negative and the shift to half a cell, so a flat neighborhood gives no shift and the refinement never leaves the peak's cell.
Source code in src/deeperfly/pose2d/heads/heatmap.py
build_head ¶
build_head(
kind: str,
channels: tuple[int, ...],
n_keypoints: int,
*,
lat: int = 96,
mid: int = 128,
)
(laterals, head, decoder) for head kind, to bind on the assembled detector.
kind is "concat" or "unet"; channels are the backbone's widths, finest
first; lat and mid are the lateral and hidden widths. An empty piece is still a
real module, so the state-dict keys are stable.
Source code in src/deeperfly/pose2d/heads/heatmap.py
coarse_reach ¶
reach 3x3 convs (GELU between) on the coarsest lateral, for the margin's reach.
Empty for reach = 0, so the shipped concat head's parameters are unchanged.
Source code in src/deeperfly/pose2d/heads/heatmap.py
dark_at ¶
dark_refine's step at M given cells per field: (N, M, 2) in cells.
Source code in src/deeperfly/pose2d/heads/heatmap.py
dark_decode ¶
A probability field -> input-normalized (..., 2) peaks and confidence.
dark_modulate then dark_refine, inverted through geom, and peak_mass for the
confidence: the share of probability near the peak.
Source code in src/deeperfly/pose2d/heads/heatmap.py
dark_modulate ¶
Gaussian-smooth each channel of a (..., H, W) field: DARK's modulation.
The training kernel restores the single Gaussian shape dark_refine's Taylor step
assumes; the reference's rescale to the original peak is skipped, since the
log-derivatives are scale-free.
Source code in src/deeperfly/pose2d/heads/heatmap.py
dark_refine ¶
Hard peak and one Newton step on the log-heatmap: (..., 2) (x, y) in cells.
DARK (Zhang et al., CVPR 2020): the offset to the mode is -H^-1 g from 3x3 finite
differences of the log-heatmap, exact for a Gaussian blob. A border peak, or one
whose Hessian is not negative definite, keeps its cell, and the step is clamped to
one cell.
Source code in src/deeperfly/pose2d/heads/heatmap.py
forward_field ¶
Backbone features feats (finest first) -> the padded heatmap field.
The margin (pad_cells, (py, px)) is zero-padded: nothing is there, and
reflecting would paste a mirrored fly where an off-image joint belongs. Padded on
the fused stride-4 features it sees one cell of content; with reach the coarsest
lateral is padded at its own stride, run through the reach convs and upsampled, so
the whole margin is extrapolated from coarse features.
Source code in src/deeperfly/pose2d/heads/heatmap.py
masked_heatmap_loss ¶
Weight-masked, foreground-weighted Gaussian-heatmap MSE.
Each pixel is weighted 1 + fg_weight * target, since a plain MSE on sparse targets
is minimized by predicting background. weight (B, K) is continuous, and 0
removes a channel from numerator and denominator alike; kp_cells may be NaN there.
Returns (loss, aux) with heatmap_mse and weight_sum.
Source code in src/deeperfly/pose2d/heads/heatmap.py
parabola_at ¶
argmax_parabola's refinement at M given cells per field, (N, M, 2).
For the candidate path; iy and ix are integer (N, M) cell indices.
Source code in src/deeperfly/pose2d/heads/heatmap.py
render_gaussian_targets ¶
Peak-vis separable Gaussian targets from keypoints in field cells.
Rendered at the fractional cell, since rounding would bake in a half-cell bias.
kp_cells (..., K, 2) may be non-finite where unlabeled, and vis (..., K)
scales the amplitude, 0 giving an exactly zero channel. Returns
(..., K, height, width).
Source code in src/deeperfly/pose2d/heads/heatmap.py
soft_argmax ¶
(B, C, Hm, Wm) field -> input-normalized (B, C, 2) peaks and confidence.
Upsampled to the input (downsample_factor 2x steps), softmaxed sharp and reduced
by expectation, with the padding inverted, so a coordinate outside [0, 1] is off
the frame. The reference readout: its upsample costs as much as the forward, for a
mode DARK reaches from a 3x3 neighborhood. margin is a scalar or a (my, mx) pair
of off-frame input pixels.
Source code in src/deeperfly/pose2d/heads/heatmap.py
spatial_softmax ¶
Softmax over the spatial axes of (B, C, H, W), scaled by temperature.
upsample_head ¶
upsample_head(
dim: int,
n_keypoints: int,
*,
final_softmax: bool = True,
pad_tokens: tuple[int, int] = (0, 0),
reach: int = 0,
kind: str = "upsample",
width: int | None = None,
) -> nn.Module
A token-to-field head: PixelShuffle and a transposed conv, or ViTPose's deconvs.
The field is the token grid times 4, a temperature-1 spatial softmax with
final_softmax. pad_tokens (py, px) are zero-padded around the tokens before
the upsampling, so the field extends past the frame, and reach adds that many 3x3
token convs after the pad, each letting the margin see one more token of content.
kind is "upsample" (the shipped head) or "deconv" (two stride-2 transposed
convs at DECONV_WIDTH). width adds a 1x1 projection before the pad, for a conv
backbone 512 wide at stride 16, where one reach conv costs 2.26 GMAC a view.
Source code in src/deeperfly/pose2d/heads/heatmap.py
The SimCC head: per-axis 1D coordinate classification (Li et al., ECCV 2022).
Two 1D distributions per point, over x and y bins at split_ratio times the input
resolution, each read out by soft-argmax: sub-pixel resolution with no 2D field, and a
differentiable readout. No checkpoint has shipped, so the key layout is ours, and
predict_heatmaps refuses rather than fabricate a field.
bins_for ¶
(y_bins, x_bins) at split_ratio bins a pixel, with margin px each side.
With a margin the bins span [-margin, size + margin], so an off-frame coordinate
has a bin.
Source code in src/deeperfly/pose2d/heads/simcc.py
simcc_decode ¶
simcc_decode(
x_logits,
y_logits,
*,
split_ratio: float,
input_hw: tuple[int, int],
margin: int = 0,
)
Bin logits -> input-normalized (N, K, 2) peaks and (N, K) confidence.
Each axis is softmaxed and reduced by expectation; bin b is input pixel
b / split_ratio - margin. The confidence is the geometric mean of the two axes'
peak probabilities.
Source code in src/deeperfly/pose2d/heads/simcc.py
simcc_head ¶
simcc_head(
in_channels: int,
n_keypoints: int,
feat_hw: tuple[int, int],
bins: tuple[int, int],
) -> nn.Module
A head mapping the finest feature map to per-axis bin logits.
(N, in_channels, Hf, Wf) -> ((N, K, x_bins), (N, K, y_bins)), with feat_hw the
feature map's size in cells and bins (y_bins, x_bins).
Source code in src/deeperfly/pose2d/heads/simcc.py
simcc_kl_loss ¶
Weight-masked KL between the target bins and the logits' softmax, per axis.
weight == 0 drops a point from numerator and denominator. Returns (loss, aux).
Source code in src/deeperfly/pose2d/heads/simcc.py
simcc_peaks ¶
simcc_peaks(
x_logits,
y_logits,
k: int,
*,
split_ratio: float,
input_hw: tuple[int, int],
margin: int = 0,
radius: int = 1,
)
The k strongest distinct peak pairs, (N, K, k, 2) and (N, K, k) scores.
The joint of two independent marginals is their outer product, whose local maxima
are pairs of 1D maxima: each axis's maxima over 2 radius + 1 bins, refined by a
parabola, paired and ranked by the geometric mean of their peak probabilities.
Missing slots are NaN with score 0.
Source code in src/deeperfly/pose2d/heads/simcc.py
simcc_targets ¶
simcc_targets(
coords_norm,
*,
input_hw: tuple[int, int],
split_ratio: float,
sigma: float = 6.0,
margin: int = 0,
)
Per-axis 1D Gaussian bin targets, each summing to 1, from normalized coords.
A label at input-normalized x centers on bin (x * W + margin) * split_ratio,
with margin matching the decode's; a non-finite label goes to bin 0, masked by
weight in the loss. sigma is in bins.
Source code in src/deeperfly/pose2d/heads/simcc.py
The assembler: compose a backbone and a head into a Detector.
Four families share one Detector and one decode and predict surface: monocular
(a CNN pyramid and a heatmap head, one image at a time, the deeperfly-dense38-1
checkpoint layout, a parabolic readout), simcc (the same pyramid and a per-axis
coordinate-classification head, no 2D field), mvt (a multiview ViT over the V
synchronized views of a frame, a probability field read out by DARK) and fast (the
mvt's tensor contract and head over a per-view CNN pyramid). A monocular checkpoint's
field margin, readout and normalization are silent wrong answers if they disagree
with training, so each is recorded in the artifact or refused.
Detector ¶
Bases: Module
A backbone and a head, one of the four families.
monocular binds backbone, laterals, head and decoder (the names a
deeperfly-dense38-1 checkpoint expects) and returns a one-element list; simcc
returns the (x_logits, y_logits) bin pair; mvt and fast return
(B, V * K, Hm, Wm), view c // K and keypoint c % K. K is num_classes,
whose last channel is the animal-center field with center_channel.
Source code in src/deeperfly/pose2d/detect.py
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 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 | |
n_points
property
¶
Tracked points: every output channel but the center one, if there is one.
__init__ ¶
__init__(
family: str,
backbone,
n_keypoints: int,
*,
backbone_name: str | None = None,
sel=None,
channels=None,
head_kind: str = "concat",
readout: str = "argmax",
geom: FieldGeometry | None = None,
margin: float = 0.0,
attn_scopes: str | None = None,
dim: int | None = None,
input_hw=None,
downsample_factor: int | None = None,
margin_px: int = 0,
pad_tokens: tuple[int, int] = (0, 0),
final_softmax: bool = True,
reach: int = 0,
split_ratio: float = 2.0,
token_mask: float = 0.0,
token_pad: int = 0,
global_rope: bool = True,
level: int = 0,
feature_stride: int = 0,
width: int | None = None,
)
Bind the backbone and head for family (see the build_* assemblers).
Source code in src/deeperfly/pose2d/detect.py
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 | |
forward ¶
Image(s) -> the family's output: a list, an (x, y) bin pair or a tensor.
view_valid (a (B, V) mask) and mask_ratio (a number or a (B*V,) tensor)
are the mvt's: padded views stay out of the real views' attention, and
mask_ratio overrides token_mask for this call. fast takes the same tensor
and ignores both. Dispatch is on family: fast keeps V but is not
cross-view.
Source code in src/deeperfly/pose2d/detect.py
heatmaps_by_view ¶
(B, V, 1, H, W) -> (B, V, K, Hm, Wm) (mvt only).
Source code in src/deeperfly/pose2d/detect.py
build_fast ¶
build_fast(
num_keypoints: int,
*,
backbone: str = "efficientvit_b1",
input_hw: tuple[int, int] = (256, 512),
downsample_factor: int = 2,
margin: int = 0,
final_softmax: bool = True,
reach: int = 0,
width: int | None = None,
pretrained: bool = False,
head: str = "upsample",
norm_mean: float | None = None,
norm_std: float | None = None,
) -> Detector
Assemble a single-view convolutional detector, the fast family.
The mvt's tensor contract and head over a timm pyramid on each view alone. The
views never inform each other, which costs a measured 0.23 px overall (0.52 on
side-view legs), and a small conv net is 3 to 6 times cheaper per view than the
smallest usable ViT.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
num_keypoints
|
int
|
Heatmap channels per view. |
required |
backbone
|
str
|
A |
'efficientvit_b1'
|
input_hw
|
tuple[int, int]
|
The network input |
(256, 512)
|
downsample_factor
|
int
|
log2 of the field's stride; the head upsamples 4x, so it
reads the pyramid level at |
2
|
margin
|
int
|
Input pixels of field past the frame on every side, in whole feature cells. |
0
|
final_softmax
|
bool
|
Emit a temperature-1 spatial-softmax field. |
True
|
reach
|
int
|
3x3 convs at the feature stride between the pad and the upsampling, one
feature cell of margin each; |
0
|
width
|
int | None
|
A 1x1 projection before the reach convs: on a 512-wide stride-16 map one reach conv costs more than the rest of a small net. |
None
|
pretrained
|
bool
|
Load |
False
|
head
|
str
|
|
'upsample'
|
norm_mean
|
float | None
|
As for the mvt. |
None
|
norm_std
|
float | None
|
As for the mvt. |
None
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the backbone lacks the pyramid or the level, or |
Source code in src/deeperfly/pose2d/detect.py
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 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 | |
build_monocular ¶
build_monocular(
n_keypoints: int,
backbone: str = "hrnet_w32",
*,
pretrained: bool = False,
head: str = "concat",
readout: str = "argmax",
input_hw: tuple[int, int] = (256, 512),
margin: float | None = DEFAULT_MARGIN,
margin_px: int | tuple[int, int] | None = None,
reach: int = 0,
) -> Detector
Assemble a monocular Detector from named parts.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_keypoints
|
int
|
Output heatmap channels (one per tracked point). |
required |
backbone
|
str
|
A |
'hrnet_w32'
|
pretrained
|
bool
|
Load |
False
|
head
|
str
|
|
'concat'
|
readout
|
str
|
|
'argmax'
|
input_hw
|
tuple[int, int]
|
Network input |
(256, 512)
|
margin
|
float | None
|
Field margin as a fraction of the input, the shipped checkpoints' convention. |
DEFAULT_MARGIN
|
margin_px
|
int | tuple[int, int] | None
|
Field margin in input pixels per side, |
None
|
reach
|
int
|
3x3 convs on the padded coarsest lateral so the margin sees image
content (see |
0
|
Source code in src/deeperfly/pose2d/detect.py
build_mvt ¶
build_mvt(
num_keypoints: int,
*,
backbone: str = "vit_base_patch16_224",
input_hw: tuple[int, int] = (256, 512),
attn_scopes: str = "llgllgllgllg",
downsample_factor: int = 2,
margin: int = 0,
final_softmax: bool = True,
reach: int = 0,
pretrained: bool = False,
drop_path: float = 0.0,
token_mask: float = 0.0,
token_pad: int = 0,
norm_mean: float | None = None,
norm_std: float | None = None,
global_rope: bool = True,
head: str = "upsample",
) -> Detector
Assemble a multiview-transformer Detector.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
num_keypoints
|
int
|
Heatmap channels per view. |
required |
backbone
|
str
|
A |
'vit_base_patch16_224'
|
input_hw
|
tuple[int, int]
|
The network input |
(256, 512)
|
attn_scopes
|
str
|
One |
'llgllgllgllg'
|
downsample_factor
|
int
|
log2 of the field's stride in input pixels (2: stride 4). |
2
|
margin
|
int
|
Model pixels of field past the frame on every side, in whole patches. |
0
|
final_softmax
|
bool
|
Emit a temperature-1 spatial-softmax map from the head. |
True
|
reach
|
int
|
Token-level 3x3 convs between the padding and the upsampling, so the margin sees image content; 0 is the shipped head, 2 the training policy. |
0
|
pretrained
|
bool
|
Load |
False
|
drop_path
|
float
|
Stochastic depth rate across the blocks (training only). |
0.0
|
token_mask
|
float
|
Fraction of each view's patch tokens replaced by a learned mask token in training; 0 masks nothing. |
0.0
|
token_pad
|
int
|
Tokens per side of |
0
|
norm_mean
|
float | None
|
Subtracted from the |
None
|
norm_std
|
float | None
|
The divisor after |
None
|
global_rope
|
bool
|
Apply the tiled rope in the global blocks; |
True
|
head
|
str
|
|
'upsample'
|
Source code in src/deeperfly/pose2d/detect.py
400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 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 | |
build_simcc ¶
build_simcc(
n_keypoints: int,
backbone: str = "hrnet_w32",
*,
pretrained: bool = False,
input_hw: tuple[int, int] = (256, 512),
split_ratio: float = 2.0,
margin: int = 0,
) -> Detector
Assemble a SimCC Detector on a CNN pyramid.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_keypoints
|
int
|
Output points, one 1D distribution pair each. |
required |
backbone
|
str
|
A |
'hrnet_w32'
|
pretrained
|
bool
|
Load |
False
|
input_hw
|
tuple[int, int]
|
Network input |
(256, 512)
|
split_ratio
|
float
|
Bins per input pixel per axis. |
2.0
|
margin
|
int
|
Input pixels the bin range extends past the frame on every side. |
0
|
Source code in src/deeperfly/pose2d/detect.py
export_monocular ¶
Save a monocular model as a deeperfly-dense38-1 checkpoint; return the dict.
point_names defaults to the model's own.
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/deeperfly/pose2d/detect.py
load_monocular ¶
Load a deeperfly-dense38-1 checkpoint as an eval-mode detector on dev.
The architecture comes from the artifact, loaded strict=True; the checkpoint
carries its own normalization, so mean must be 0.0.
Raises:
| Type | Description |
|---|---|
SystemExit
|
On a nonzero |
Source code in src/deeperfly/pose2d/detect.py
618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 | |
Running an assembled detector: input prep, decode dispatch, and the predict surface.
detect is what a detector is; this is how to run it, with
the readout dispatched on the model's output_kind. The monocular family folds every
leading axis into the batch, and the multiview transformer keeps V.
decode_candidates ¶
Forward outputs -> the k strongest distinct peaks per cell, by output_kind.
(N, K, k, 2) input-normalized peaks, NaN where a cell has fewer, and (N, K, k)
scores in the family's confidence units, each refined and scored as its argmax
readout is. radius_px is the separation, in input pixels, below which two maxima
are one.
Source code in src/deeperfly/pose2d/infer.py
decode_points ¶
Forward outputs -> input-normalized peaks and confidence, by output_kind.
"heatmap" by parabolic sub-pixel refinement over the padded field,
"heatmap_softargmax" by DARK refinement
(dark_decode), "simcc" by per-axis
soft-argmax; an off-frame coordinate falls outside [0, 1].
Source code in src/deeperfly/pose2d/infer.py
joint_channels ¶
A flat (N, C, h, w) forward output without its center channel: (N, K, h, w).
The center field is the last channel, and every joint readout drops it first; a model without one gets its output back untouched, whatever the family wraps it in.
Source code in src/deeperfly/pose2d/infer.py
predict_heatmaps ¶
Final heatmaps as host NumPy, (..., K, Hf, Wf) or mvt's (B, V, K, Hm, Wm).
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
For a family with no 2D field (SimCC). |
Source code in src/deeperfly/pose2d/infer.py
predict_points ¶
Fused forward and decode -> input-normalized peaks and confidence.
method and radius are ignored: the readout is the one the targets trained for.
For mvt the V axis is kept, and views decodes only those views.
Source code in src/deeperfly/pose2d/infer.py
prepare_images ¶
(..., H, W[, C]) frames -> (..., 1, h, w) ViT input, on device.
A plain bilinear resize of one gray plane; the model normalizes it and adds the off-frame margin in its head.
Source code in src/deeperfly/pose2d/infer.py
Top-K distinct peaks per cell: the detector side of the candidate path (ADR-0038).
The single-peak readout commits a cell to its strongest peak, and when that is the
wrong one (a crossed leg, a left/right confusion) nothing downstream gets the joint
back. The candidate path keeps the K strongest distinct maxima, refined and scored as
the head's own readout, so pictorial can prefer a weaker
peak the other views agree with; slot 0 is the decoded point.
field_peaks ¶
The k strongest local maxima per (N, H, W) field, as cells and values.
(N, k, 2) integer (x, y) cells and (N, k) values. A cell is a maximum when it
equals its (2 radius + 1) square's maximum and exceeds its minimum, so a flat
plateau keeps none; missing slots carry -inf at (0, 0).
Source code in src/deeperfly/pose2d/candidates.py
merge_candidates ¶
One set of k distinct candidates per row: the anchor, then the pooled peaks.
The anchor (anchor_xy (M, 2), anchor_score (M,)) takes slot 0, a NaN one
none; the pooled peaks follow strongest first, each dropped within radius of one
already kept. Returns (M, k, 2) and (M, k), NaN at score 0 where fewer exist.
Source code in src/deeperfly/pose2d/candidates.py
split_shared_peaks ¶
split_shared_peaks(
field: ndarray,
cells: ndarray,
scores: ndarray,
animals: int,
*,
sigma: float = LOCATOR_SIGMA_CELLS,
iterations: int = 20,
) -> tuple[np.ndarray, np.ndarray]
One view's locator peaks, a blob that holds several animals split among them.
A locator for several animals trains each view's field on one sigma-wide
Gaussian per animal, renormalized (ADR-0127), so animals far apart are blobs of
1 / animals of the probability each, and animals touching in the view are one
stretched blob holding all of it, whose single maximum is one peak. While fewer
blobs than animals hold half an animal's share, the heaviest is fit by as many
sigma-wide Gaussians as it holds animals (EM on its probability, started across
its long axis), whose centers replace its peak, each with the peak's score over
their count (ADR-0131). field is the view's (H, W) probability, cells its
(k, 2) peaks in field cells and scores theirs, NaN and 0 where missing; the
result keeps the shape, extra peaks dropping off the end.
Source code in src/deeperfly/pose2d/candidates.py
The optimizer schedule: a cosine-with-warmup LR factor and two parameter groups.
A backbone learning-rate multiplier is reachable, 1.0 (uniform) being the tested recipe. Ported from dfpose with the numerics unchanged.
apply_lr ¶
Set each param group's LR to its base times factor (zipped 1:1, in order).
Source code in src/deeperfly/pose2d/training/optim.py
build_param_groups ¶
The head and backbone.* parameter groups and their base LRs.
The backbone's LR is scaled by backbone_lr_mult; frozen parameters are skipped.
Source code in src/deeperfly/pose2d/training/optim.py
cosine_warmup_factor ¶
cosine_warmup_factor(
step: int,
total_steps: int,
warmup_steps: int,
min_frac: float = 0.01,
) -> float
A linear warmup over warmup_steps, then a cosine decay to min_frac.
Source code in src/deeperfly/pose2d/training/optim.py
Losses over decoded coordinates, whatever head produced them.
coordinate_loss is the direct supervised term and triangulation_loss Lightning Pose
3D's geometric one (ADR-0072); both take decoded, augmentation-inverted coordinates,
the geometric one in video pixels.
coordinate_loss ¶
Weight-masked squared (or huber) error between (..., K, 2) coordinates.
A weight == 0 point, whose gt may be NaN, counts in neither numerator nor
denominator. Returns (loss, aux) with coord_loss and weight_sum.
Source code in src/deeperfly/pose2d/training/losses.py
triangulate_pairs ¶
DLT over view pairs -> (B, P, K, 3) world points.
With the homogeneous coordinate fixed at 1, each pair's four rows are a 4x3 least
squares, solved through its ridge-regularized normal equations. xy (B, V, K, 2)
and proj (B, V, 3, 4) are in video pixels; pairs is (P, 2).
Source code in src/deeperfly/pose2d/training/losses.py
triangulation_loss ¶
triangulation_loss(
pred_xy,
gt_xy,
weight,
proj,
*,
view_valid=None,
group_ok=None,
huber: float = 0.0,
max_world: float = 0.0,
)
LP3D's 3D loss: prediction and ground truth triangulated through the same pairs.
pred_xy and gt_xy are (B, V, K, 2) video pixels, weight (B, V, K) (a pair
supervises where both views do), proj (B, V, 3, 4); view_valid and group_ok
mask views and groups. huber replaces the square and max_world clips a point's
world error (ADR-0072). Returns (loss, stats).
Source code in src/deeperfly/pose2d/training/losses.py
view_pairs ¶
(P, 2) long: every unordered pair of view indices, P = V(V-1)/2.
A learned manifold of fly poses, and the loss that keeps predictions on it.
Ported from Lightning Pose's pca_singleview loss. A wrong-limb attachment is not a
small perturbation of a real pose but a configuration off the manifold, which the
residual penalizes while barely touching a few-pixel error; it needs no calibration and
supervises unlabeled points too. Fitted in a frame normalized for translation and
per-axis scale, so it encodes shape.
PosePCA ¶
Bases: Module
Penalizes the part of a predicted pose that leaves the fitted subspace.
The mean squared reconstruction residual in the normalized frame, zero for any pose the labels could have produced.
Source code in src/deeperfly/pose2d/training/pose_pca.py
fit_pose_pca ¶
(N, K, 2) complete normalized poses -> (mean (2K,), comps (C, 2K), evr (C,)).
n_components fixes C, else the smallest C explaining var_target. Only complete
poses: an imputed joint would teach the subspace the imputation.
Source code in src/deeperfly/pose2d/training/pose_pca.py
normalize_pose ¶
(B, K, 2) poses centered and scaled to unit RMS radius per axis.
Per axis, since a window resized per axis stretches the animal; rotation stays.
Source code in src/deeperfly/pose2d/training/pose_pca.py
Composable 2D affine augmentation for the detectors, plus photometric jitter.
Every geometric augmentation is one Affine2D: composed with @ into a single map,
the image is resampled once, and its inverse() takes a prediction back to video
pixels, which the triangulation loss needs. A flip also permutes the keypoint channels
(Skeleton.flip_perm), so a mirrored left leg trains the right channel; inverse()
undoes the geometry but not the channel order, which is consistent across a group
that flips together. Photometric jitter touches no coordinate.
Affine2D
dataclass
¶
A 2x3 affine on 2D pixel coordinates: dst = matrix @ [x, y, 1].
a @ b applies b, then a. Pixel (x, y) sits at continuous coordinate
(x, y), so a W-wide frame spans [-0.5, W - 0.5]: the align_corners=False
convention of grid_sample and of the inference resize, which every constructor
honors.
Source code in src/deeperfly/pose2d/training/augment.py
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 | |
__matmul__ ¶
apply ¶
Transform (..., 2) (x, y) points, preserving the leading shape.
In the higher of the point and matrix dtypes, so float64 video coordinates keep their precision; NaN propagates.
Source code in src/deeperfly/pose2d/training/augment.py
crop
classmethod
¶
crop(
box: tuple[float, float, float, float],
out_hw: tuple[int, int],
*,
device=None,
dtype=torch.float32,
) -> Affine2D
Map the window box = (x0, y0, x1, y1), slice bounds, onto out_hw.
As F.interpolate(image[..., y0:y1, x0:x1], out_hw, align_corners=False):
pixel centers map as (x - x0 + 0.5) * sx - 0.5.
Source code in src/deeperfly/pose2d/training/augment.py
flip_x
classmethod
¶
Horizontal mirror of a width-wide frame: x -> width - 1 - x.
Pixel 0 mirrors onto pixel width - 1; width - x would shift it a pixel.
Source code in src/deeperfly/pose2d/training/augment.py
grid_theta ¶
affine_grid theta: output-normalized -> input-normalized coordinates.
The inverse of this affine between the two images' [-1, 1] frames; in_hw is
the canvas sampled, so a frame placed in a bigger buffer changes no pixel.
Source code in src/deeperfly/pose2d/training/augment.py
inverse ¶
resize
classmethod
¶
resize(
in_hw: tuple[int, int],
out_hw: tuple[int, int],
*,
device=None,
dtype=torch.float32,
) -> Affine2D
Map a full in_hw frame onto a full out_hw frame, as F.interpolate does.
Pixel centers map as (x + 0.5) * out / in - 0.5; a plain scale about the
origin would be (out / in - 1) / 2 pixels off.
Source code in src/deeperfly/pose2d/training/augment.py
rotate
classmethod
¶
rotate(
radians: float,
*,
center: tuple[float, float] = (0.0, 0.0),
device=None,
dtype=torch.float32,
) -> Affine2D
Rotate by radians about center: +x toward +y, clockwise on screen.
Source code in src/deeperfly/pose2d/training/augment.py
scale
classmethod
¶
scale(
sx: float,
sy: float,
*,
center: tuple[float, float] = (0.0, 0.0),
device=None,
dtype=torch.float32,
) -> Affine2D
Scale by (sx, sy) about center.
Source code in src/deeperfly/pose2d/training/augment.py
to ¶
This affine on another device or dtype, to compose across sources.
translate
classmethod
¶
Shift by (tx, ty).
warp_image ¶
warp_image(
image: Tensor,
out_hw: tuple[int, int],
*,
mode: str = "bilinear",
padding_mode: str = "zeros",
) -> torch.Tensor
Resample image through this affine onto an out_hw canvas.
grid_sample with align_corners=False; "zeros" padding leaves off-canvas
regions black, as at inference. Returns the input's rank.
Source code in src/deeperfly/pose2d/training/augment.py
Augment
dataclass
¶
The training-time augmentation, mild by default, as [augment] writes it.
Each field maps onto sample_affine or photometric_jitter.
Source code in src/deeperfly/pose2d/training/augment.py
photometric ¶
The torchvision transform this spec's image-only jitter builds.
Source code in src/deeperfly/pose2d/training/augment.py
flip_channels ¶
Reorder (..., K, 2) keypoints and (..., K) weights by flip_perm.
After a mirror a left joint sits where the right one belongs; flip_perm is an
involution, the identity on unpaired points.
Source code in src/deeperfly/pose2d/training/augment.py
photometric_jitter ¶
photometric_jitter(
*,
brightness: float = 0.0,
contrast: float = 0.0,
gamma: tuple[float, float] | None = None,
blur_sigma: tuple[float, float] | None = None,
blur_kernel: int = 7,
noise_std: float = 0.0,
p: float = 0.5,
) -> v2.Transform
A torchvision transforms.v2 pipeline of image-only, coordinate-free jitter.
Each enabled stage fires with probability p: ColorJitter brightness and
contrast (safe on gray), a log-uniform gamma, a Gaussian blur (blur_kernel odd)
and additive noise on a [0, 1] image. torchvision draws once per call, so apply
it per sample, before collation.
Source code in src/deeperfly/pose2d/training/augment.py
sample_affine ¶
sample_affine(
hw: tuple[int, int],
*,
degrees: float = 0.0,
scale: tuple[float, float] = (1.0, 1.0),
aspect: tuple[float, float] = (1.0, 1.0),
translate: tuple[float, float] = (0.0, 0.0),
flip_p: float = 0.0,
generator: Generator | None = None,
device=None,
dtype=torch.float32,
) -> tuple[Affine2D, bool]
A random geometric augmentation on an hw canvas: one affine and a flip flag.
An optional flip first, then rotation ([-degrees, degrees]), isotropic scale and
an area-preserving, log-uniform aspect jitter about the canvas center, then a
translation (a fraction of the canvas). When the flag is set the caller must
permute the keypoint channels (flip_channels). Compose the affine with
Affine2D.crop or resize to start from video pixels.
Source code in src/deeperfly/pose2d/training/augment.py
warp_sample ¶
warp_sample(
image,
kp,
vis,
affine: Affine2D,
out_hw: tuple[int, int],
*,
flipped: bool = False,
flip_perm=None,
mode: str = "bilinear",
padding_mode: str = "zeros",
)
Apply one geometric augmentation to an (image, kp, vis) sample.
Warps the image and the source-pixel keypoints through affine, permuting the
channels by flip_perm when flipped; coordinates stay in out_hw pixels.
Returns (image, kp, vis) in the augmented frame.
Source code in src/deeperfly/pose2d/training/augment.py
The frame cache: a project's labeled frames, decoded once, on disk.
Labels are sparse (a few dozen frames of thousands), so each is decoded once, cropped and resized to the input by the detectors' own anti-aliased resize, and kept as grayscale uint8 in an HDF5 file keyed by the crop and input size.
FrameCache ¶
The labeled frames of one project, on disk.
One HDF5 file per (project, crops, input_hw, videos), its digest in the name,
so a changed crop plan or video never reads a stale cache. With input_hw each
view's window is stored resized to the input; without it, the source mode stores
each video's native frame once and the window mode (with crops) each view's
window at video resolution. Nothing is flipped on the way in: a mirrored view's crop
is reflected by the sample's window, as at inference. ensure decodes what is
missing.
Source code in src/deeperfly/pose2d/training/cache.py
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 297 298 299 300 301 302 303 | |
frame_hw
property
¶
Each view's frame (height, width), probed once when not given.
source_mode
property
¶
Whether frames are stored whole, one per source (see the class docs).
__init__ ¶
__init__(
project: Project,
name: str,
*,
input_hw: tuple[int, int] | None,
crops: dict[str, tuple[int, int, int, int]]
| None = None,
cache_dir: str | Path | None = None,
frame_hw: Mapping[str, tuple[int, int]] | None = None,
) -> None
Open (or create) the cache of project for input_hw and crops.
name is the project's name in the training set, the cache's directory
under <cache_dir>/frames/ (<project>/cache by default); frame_hw is
each view's frame (height, width), probed from the videos when absent.
Source code in src/deeperfly/pose2d/training/cache.py
crop_affine ¶
Video pixels -> input pixels for one view, in the resizing mode only.
Source code in src/deeperfly/pose2d/training/cache.py
digest ¶
What the cache is keyed by: input size, per-view crops and video paths.
A video path is keyed relative to the project folder, both resolved up to the file itself, so moving or relinking a project's tree keeps its caches (ADR-0124); a video that is a symlink keeps its own name.
Source code in src/deeperfly/pose2d/training/cache.py
ensure ¶
Decode every frame of frames not yet cached and append it.
Source code in src/deeperfly/pose2d/training/cache.py
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 | |
get ¶
The cached (H, W) uint8 image one view of one frame is drawn from.
Source code in src/deeperfly/pose2d/training/cache.py
image_affine ¶
Video pixels -> stored-image pixels for one view; None when they are one.
The crop-and-resize, the window's translation, or, in the source mode, nothing: the stored image is the view's video frame.
Source code in src/deeperfly/pose2d/training/cache.py
mirrored ¶
stored_hw ¶
window_affine ¶
Video pixels -> stored-window pixels (a translation), in window mode.
Source code in src/deeperfly/pose2d/training/cache.py
full_frame_crops ¶
full_frame_crops(
project: Project,
frame_hw: Mapping[str, tuple[int, int]] | None = None,
) -> dict[str, tuple[int, int, int, int]]
The whole-frame crop window (x0, y0, x1, y1) of every view of a project.
frame_hw is each view's (height, width); without it the videos are probed.
Source code in src/deeperfly/pose2d/training/cache.py
Training samples from projects' labels: per-view samples and view groups.
LabelDataset is one item per (project, frame, view), each with its image (from a
FrameCache), input-normalized
keypoints, a per-keypoint weight and the geometry to invert it to video pixels. A cell
weighs 0 when unlabeled, hidden, absent or past the detector's off-frame margin; a
cell inside the margin keeps its weight, so the padded field learns to extrapolate.
GroupedLabelDataset carries every view of one moment together for the multiview
transformer, with one flip decision per group, since a half-mirrored rig is not a rig.
A training set is several projects (ADR-0085), given as name -> Project in the
order the collection names them (project_names);
the name is what a split, a report and the frame cache call the project. A sample's
project index is the project's position in that mapping.
GroupedLabelDataset ¶
Bases: Dataset
All views of one (project, frame) as (V, ...) tensors: the multiview shape.
One flip decision per group, with the targets permuted as the predictions are, so a
flipped group still feeds the geometric loss. With view_drop_p an augmented group
keeps a uniform number of views in [view_min, V], the dropped slots invalid, so
the model trains at every view count it may be served.
Source code in src/deeperfly/pose2d/training/dataset.py
1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 | |
__getitem__ ¶
Group g as stacked per-view tensors, one flip decision for all views.
Source code in src/deeperfly/pose2d/training/dataset.py
__init__ ¶
Group the items of base by (project, frame).
Source code in src/deeperfly/pose2d/training/dataset.py
__len__ ¶
group_projects ¶
LabelDataset ¶
Bases: SampleDataset
One item per (project, frame, view) of the projects' labeled frames.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
projects
|
Mapping[str, Project]
|
|
required |
skeleton
|
Skeleton | None
|
The skeleton every project labels; the first project's by default. |
None
|
animals
|
int | None
|
Instance slots per frame; the most any project holds by default. |
None
|
input_hw
|
tuple[int, int]
|
Network input |
required |
margin_px
|
int | tuple[int, int]
|
The detector's off-frame margin in input pixels, |
0
|
augment
|
Augment | None
|
Training-time augmentation, or |
None
|
crops
|
dict[str, dict[str, tuple[int, int, int, int]]] | None
|
Per-project, per-view video windows; the whole frame when absent (see label_windows). |
None
|
keypoint_weights
|
Optional |
None
|
|
cache_dir
|
str | Path | None
|
Where frame caches live ( |
None
|
supervised_only
|
bool
|
Drop |
False
|
fill_weight
|
float
|
Weight of the fills (ADR-0070); 0 trains and scores GT only, as an evaluation set must. |
0.0
|
off_view_weight
|
float
|
Weight of a target outside its own view's image. |
1.0
|
fill_min_views
|
int
|
Labeled views a keypoint needs before it is filled. |
2
|
fill_max_residual_px
|
float
|
Skip the fill of a keypoint whose 3D reprojects further than this from one of its own labels. |
10.0
|
crop_tightness
|
tuple[float, float] | None
|
|
None
|
crop_jitter
|
float
|
Training-time center jitter, as a fraction of the crop. |
0.1
|
crop_eval_tightness
|
float
|
The crop size used without augmentation. |
1.25
|
view_aspect
|
dict[str, float] | None
|
View name -> the aspect its crop box is drawn at. |
None
|
crop_min_points
|
int
|
Below this many finite points a view falls back to the largest window of the frame at the input's aspect. |
4
|
crop_full_p
|
float
|
Probability of a letterboxed whole-frame crop instead. |
0.1
|
mark_others
|
bool
|
Mark the other animals' centers in each sample ( |
False
|
mark_jitter
|
float
|
Training-time error of a marked center, in input pixels. |
6.0
|
frames
|
Mapping[str, Sequence[int]] | None
|
|
None
|
exclude_frames
|
Mapping[str, Sequence[int]] | None
|
|
None
|
Source code in src/deeperfly/pose2d/training/dataset.py
657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 | |
__init__ ¶
__init__(
projects: Mapping[str, Project],
*,
skeleton: Skeleton | None = None,
animals: int | None = None,
input_hw: tuple[int, int],
margin_px: int | tuple[int, int] = 0,
augment: Augment | None = None,
crops: dict[str, dict[str, tuple[int, int, int, int]]]
| None = None,
keypoint_weights=None,
cache_dir: str | Path | None = None,
supervised_only: bool = False,
fill_weight: float = 0.0,
fill_max_residual_px: float = 10.0,
fill_min_views: int = 2,
off_view_weight: float = 1.0,
crop_tightness: tuple[float, float] | None = None,
crop_jitter: float = 0.1,
crop_eval_tightness: float = 1.25,
crop_min_points: int = 4,
crop_full_p: float = 0.1,
view_aspect: dict[str, float] | None = None,
mark_others: bool = False,
mark_jitter: float = 6.0,
frames: Mapping[str, Sequence[int]] | None = None,
exclude_frames: Mapping[str, Sequence[int]]
| None = None,
) -> None
Index the projects' labeled frames, fill their caches and solve the fills.
Source code in src/deeperfly/pose2d/training/dataset.py
696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 | |
LocatorDataset ¶
Bases: SampleDataset
One item per (project, frame, video): where every view sees the animal.
The full-frame locator's training shape (ADR-0064): a sample is a whole source
frame letterboxed, never cropped, and channel k carries view k's animal center
(the center channel's target, ADR-0070), weighing 0 where view k does not read
this video. At one animal a channel is one Gaussian; above it, a channel is the
center channel's mixture with a peak per animal of the frame (ADR-0127), carried
as the item's centers, (C * A, 2) channel-major.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
projects
|
Mapping[str, Project]
|
|
required |
views
|
Sequence[str]
|
The channel order, one view name per channel, naming every view of every project. |
required |
input_hw
|
tuple[int, int]
|
Network input |
required |
margin_px
|
int | tuple[int, int]
|
The detector's off-frame margin in input pixels. |
0
|
augment
|
Augment | None
|
Training-time augmentation, or |
None
|
cache_dir
|
str | Path | None
|
Where frame caches live ( |
None
|
fill_max_residual_px
|
float
|
Skip a keypoint whose 3D reprojects further than this from its own labels, before the center is taken. |
10.0
|
fill_min_views
|
int
|
Labeled views a keypoint needs before it is triangulated. |
2
|
frames
|
Mapping[str, Sequence[int]] | None
|
|
None
|
exclude_frames
|
Mapping[str, Sequence[int]] | None
|
|
None
|
animals
|
int | None
|
Instance slots per frame; the most any project holds by default. |
None
|
Source code in src/deeperfly/pose2d/training/dataset.py
853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 | |
__init__ ¶
__init__(
projects: Mapping[str, Project],
*,
views: Sequence[str],
input_hw: tuple[int, int],
margin_px: int | tuple[int, int] = 0,
augment: Augment | None = None,
cache_dir: str | Path | None = None,
fill_max_residual_px: float = 10.0,
fill_min_views: int = 2,
frames: Mapping[str, Sequence[int]] | None = None,
exclude_frames: Mapping[str, Sequence[int]]
| None = None,
animals: int | None = None,
) -> None
Index the labeled frames of the projects, one item per video.
Source code in src/deeperfly/pose2d/training/dataset.py
879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 | |
SampleDataset ¶
Bases: Dataset
The per-view sample logic every label source shares.
A source subclasses it, fills self.entries (one per project: objects with
name, view_names and a (V, 3, 4) video-pixel proj or None) and
self.items ((project, frame, view, instance) tuples), and implements
_image, _target and _base_affine. sample turns an item into the
training dict.
Source code in src/deeperfly/pose2d/training/dataset.py
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 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 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 | |
__getitem__ ¶
__init__ ¶
__init__(
skeleton,
*,
input_hw: tuple[int, int],
margin_px: int | tuple[int, int],
augment: Augment | None,
keypoint_weights=None,
crop_tightness: tuple[float, float] | None = None,
crop_jitter: float = 0.1,
crop_eval_tightness: float = 1.25,
crop_min_points: int = 4,
crop_full_p: float = 0.1,
view_aspect: dict[str, float] | None = None,
animals: int = 1,
mark_others: bool = False,
mark_jitter: float = 6.0,
) -> None
Set the sample knobs every source shares; the subclass fills the items.
crop_* shape the training crop drawn around an animal (see crop_box),
view_aspect gives a view its own crop aspect, animals is how many
instance slots a frame of this source may hold, and mark_others marks the
other animals' centers in each sample (mark_others), off by mark_jitter
pixels when training.
Source code in src/deeperfly/pose2d/training/dataset.py
__len__ ¶
crop_box ¶
crop_box(
ref: ndarray,
frame_hw: tuple[int, int],
*,
train: bool,
aspect: float | None = None,
) -> tuple[float, float, float, float]
A video-pixel window (x0, y0, x1, y1) around the animal (ADR-0046).
The finite ref points' bounding box grown to the input's aspect (or aspect)
and scaled by a tightness, log-uniform in crop_tightness for training (below 1
a deliberate bad crop) and crop_eval_tightness, centered, for evaluation;
training centers jitter by crop_jitter. Never narrower than half the input,
never clamped to the frame. With probability crop_full_p, or fewer than
crop_min_points points, the sample is the whole frame letterboxed instead.
Source code in src/deeperfly/pose2d/training/dataset.py
groups ¶
Item indices by (project, frame, animal) group, each in view order.
Source code in src/deeperfly/pose2d/training/dataset.py
project_name ¶
sample ¶
Item i, optionally forcing the mirror decision (for a consistent group).
turn is the (x, y) axis flip of this view under a rig symmetry the group
drew (rig_symmetries), with flip saying whether it is a mirror.
A dict of image (1, H, W) in [0, 1], kp (K, 2) input-normalized (NaN where
unlabeled), weight (K,), in_frame (K,), affine (2, 3) video -> input
pixels, gt_video_px (K, 2) in kp's channel order (mirrored when flipped),
proj (3, 4) with calibrated, flipped, the item's indices, and the
input-normalized centers (A, 2) of every animal this view sees, with
center_valid (A,).
Source code in src/deeperfly/pose2d/training/dataset.py
557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 | |
symmetries ¶
Project r's rig_symmetries, found once; empty without a calibration.
Source code in src/deeperfly/pose2d/training/dataset.py
view_name ¶
ViewBucketSampler ¶
Bases: Sampler
Batches of group indices that share a view count, so none needs padding.
Buckets are shuffled (with shuffle), cut into batches and the batches shuffled
across buckets. With images_per_batch a bucket of v views takes
images_per_batch // v groups a batch, so every step sees about as many images.
repeats, one per group, is how often an epoch holds each: its whole part, and
once more with the chance of its fraction, drawn anew every epoch (ADR-0136).
Source code in src/deeperfly/pose2d/training/dataset.py
1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 | |
__init__ ¶
__init__(
n_views: Sequence[int],
batch_size: int,
*,
shuffle: bool = True,
drop_last: bool = False,
generator: Generator | None = None,
images_per_batch: int | None = None,
repeats: Sequence[float] | None = None,
) -> None
Bucket n_views (one per group) into batches of batch_size.
Source code in src/deeperfly/pose2d/training/dataset.py
__iter__ ¶
active_rig ¶
active_rig(
project: Project,
frame_hw: Mapping[str, tuple[int, int]] | None = None,
) -> Rig | None
The project's active calibration on its manifest's views, as the GUI's rig.
None when no calibration is active; frame_hw spares a probe of the videos.
Source code in src/deeperfly/pose2d/training/dataset.py
collate_flat ¶
Stack flat items into a (B, ...) batch; scalars become 1D tensors.
Source code in src/deeperfly/pose2d/training/dataset.py
collate_groups ¶
Stack groups into a (B, Vmax, ...) batch, padding short groups.
A padded slot has view_valid 0 and weight 0, so it enters no loss; the model
keeps it out of the real views' attention.
Source code in src/deeperfly/pose2d/training/dataset.py
fill_targets ¶
fill_targets(
rig: Rig | None,
detections: Detections | None,
labels,
frames: Sequence[int],
*,
view_boxes: ndarray | None = None,
fill_weight: float = 1.0,
max_residual_px: float = 10.0,
min_views: int = 2,
off_view_weight: float = 1.0,
instance: int = 0,
) -> dict[
int,
tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray],
]
One animal's (V, K, 2) targets, (V, K) weights, reference points and center.
GT where the operator clicked; elsewhere the stabilized two-click solve through
rig reprojected, weighing fill_weight (ADR-0070), where the clicked views, or
the detections agreeing with them, place the keypoint in depth (ADR-0130). The
reference points are every solved position, whatever its weight, which a per-sample
crop is drawn around; the center is the (V, 2) projection of the per-axis median
of the solved 3D keypoints, NaN without a rig. instance is the animal filled,
stabilized by the slot of the project's merged detections (ADR-0083) its clicks
sit on in each frame (ADR-0128); the other animals are plain background.
view_boxes are the (V, 4) whole-frame boxes an off_view_weight other than 1
needs.
Source code in src/deeperfly/pose2d/training/dataset.py
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 | |
label_windows ¶
label_windows(
projects: Mapping[str, Project],
*,
aspect: float = 2.0,
pad: float = 0.15,
quantiles: tuple[float, float] = (0.5, 99.5),
min_points: int = 50,
min_hw: tuple[int, int] = (256, 512),
) -> dict[str, dict[str, tuple[int, int, int, int]]]
Per-project, per-view video windows centered on the labels, at a fixed aspect.
The corpus mixes rigs (side views 512x1984 and axial ones 1008x1600 on the
ball-tracking recordings), so a whole-frame resize squashes the fly 3.9x one way and
2x the other. Per view, the labels' box (the quantiles, padded by pad) is grown
to aspect, centered and fitted inside the frame, never smaller than min_hw and
never stretched; a view with fewer than min_points labels gets the largest
centered window.
Source code in src/deeperfly/pose2d/training/dataset.py
locator_skeleton ¶
The point set a full-frame locator trains against: one point per view.
Channel k is view k's picture of the animal, so the pose path reads the views as
keypoints. No edges and no mirror pairs: a locator learns the rig's fixed layout,
which a mirrored frame never shows.
Source code in src/deeperfly/pose2d/training/dataset.py
rig_symmetries ¶
rig_symmetries(
proj: ndarray, tol_deg: float = 15.0
) -> list[tuple[bool, tuple[tuple[int, int], ...]]]
The world isometries that carry a rig's views onto each other by axis flips.
A mirror or a turn of the world that maps every view onto some view of the rig,
up to a flip of that view's image axes and within tol_deg, is a picture the rig
could have taken: the views stay consistent with each other, which a
flip of every view about its own x axis is not on a rig whose cameras are turned
against each other. A model that does not know which view is which never sees
the views change places, so a symmetry is one axis flip per view.
Returns:
| Type | Description |
|---|---|
list[tuple[bool, tuple[tuple[int, int], ...]]]
|
|
list[tuple[bool, tuple[tuple[int, int], ...]]]
|
(left and right keypoints change places), |
list[tuple[bool, tuple[tuple[int, int], ...]]]
|
view |
Source code in src/deeperfly/pose2d/training/dataset.py
Label sources outside a project: the df3d corpus and a Lightning Pose export.
Pretraining stages, read into a project's SampleDataset shape with no calibration:
no fill, no geometric loss. Df3dDataset reads the DeepFly3D annotations (Gunel et
al. 2019, cleaned in the previous training repo), keypoint columns mapped onto the
skeleton by name. LightningPoseDataset reads a multiview Lightning Pose export, its
window affine taking the labels back to video pixels.
Df3dDataset ¶
Bases: SampleDataset
The df3d corpus as (experiment, frame, camera) items grouped by frame.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
str | Path
|
The corpus root ( |
required |
skeleton
|
The detector's skeleton; corpus columns are matched to it by name. |
required | |
input_hw
|
tuple[int, int]
|
Network input |
required |
margin_px
|
int | tuple[int, int]
|
The detector's off-frame margin in input pixels. |
0
|
augment
|
Augment | None
|
Training-time augmentation, or |
None
|
experiments
|
Sequence[str] | None
|
Which experiments to read (all by default). |
None
|
drop_thorax_coxa_sources
|
Sequence[str]
|
Annotation sources whose |
('gizem_2022',)
|
Source code in src/deeperfly/pose2d/training/sources.py
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 | |
__init__ ¶
__init__(
root: str | Path,
skeleton,
*,
input_hw: tuple[int, int],
margin_px: int | tuple[int, int] = 0,
augment: Augment | None = None,
experiments: Sequence[str] | None = None,
drop_thorax_coxa_sources: Sequence[str] = (
"gizem_2022",
),
crop_tightness: tuple[float, float] | None = None,
crop_jitter: float = 0.1,
crop_eval_tightness: float = 1.25,
crop_min_points: int = 4,
crop_full_p: float = 0.1,
) -> None
Index every annotated (experiment, frame, camera) of the corpus.
Source code in src/deeperfly/pose2d/training/sources.py
LightningPoseDataset ¶
Bases: SampleDataset
A multiview Lightning Pose project as (recording, moment, view) items.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
str | Path
|
The project directory ( |
required |
skeleton
|
The detector's skeleton; the CSV |
required | |
input_hw
|
tuple[int, int]
|
Network input |
required |
margin_px
|
int | tuple[int, int]
|
The detector's off-frame margin in input pixels. |
0
|
augment
|
Augment | None
|
Training-time augmentation, or |
None
|
views
|
Sequence[str]
|
The view names, in the project's order ( |
('rh', 'rm', 'rf', 'f', 'lf', 'lm', 'lh', 'h')
|
exclude
|
Sequence[str]
|
Recording names to leave out (a scored animal, say). |
()
|
min_visible
|
float
|
The |
1.0
|
Source code in src/deeperfly/pose2d/training/sources.py
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 | |
__init__ ¶
__init__(
root: str | Path,
skeleton,
*,
input_hw: tuple[int, int],
margin_px: int | tuple[int, int] = 0,
augment: Augment | None = None,
views: Sequence[str] = (
"rh",
"rm",
"rf",
"f",
"lf",
"lm",
"lh",
"h",
),
exclude: Sequence[str] = (),
min_visible: float = 1.0,
) -> None
Index every (recording, moment, view) with an image and a bounding box.
Source code in src/deeperfly/pose2d/training/sources.py
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 | |
Unlabeled moments as training samples: a teacher's consensus (ADR-0071).
A teacher's 2D over every view of a sampled moment is triangulated robustly and the consensus reprojected into every view as the target, the cells it corrected flagged. UnlabeledDataset serves them through the labeled pipeline's crops, augmentation, margin rule and grouped batches, prism mirrors included, from a native FrameCache.
Without a teacher artifact the teacher is each project's merged detections (ADR-0083), and in both cases the windows are the static crops those detections were read through.
UnlabeledDataset ¶
Bases: SampleDataset
One item per (project, frame, view) of teacher-consensus targets.
Built by build_unlabeled from projects whose targets are solved. The crop is drawn around the consensus points, its tightness capped below the labeled range since the stored window is only 1.5x the animal; no whole-frame letterbox.
Source code in src/deeperfly/pose2d/training/unlabeled.py
__init__ ¶
__init__(
skeleton,
entries: Sequence[_UnlabeledEntry],
*,
input_hw: tuple[int, int],
margin_px: int | tuple[int, int],
augment,
crop_tightness: tuple[float, float] = (0.75, 1.6),
crop_jitter: float = 0.1,
crop_eval_tightness: float = 1.25,
crop_min_points: int = 4,
) -> None
Index every view of every solved moment of entries.
Source code in src/deeperfly/pose2d/training/unlabeled.py
build_unlabeled ¶
build_unlabeled(
projects: Mapping[str, Project],
*,
skeleton: Skeleton | None = None,
teacher: str | Path | None,
n_per_project: int = 200,
agree_px: float,
min_views: int,
conf_min: float,
input_hw: tuple[int, int],
margin_px,
augment,
geom_weight: float = 1.0,
skip_frames: Mapping[str, Sequence[int]] | None = None,
guard_frames: int = 0,
crop_tightness: tuple[float, float] = (0.75, 1.6),
cache_dir: str | Path | None = None,
device: str | None = None,
log=print,
) -> UnlabeledDataset
Sample, cache, teach, solve the consensus: the dataset over projects.
Per project (name -> Project), n_per_project frames spread over the video
(the labeled and skip_frames ones skipped, guard_frames either side) are
cached behind the discovered windows, taught and solved through the active
calibration, and the targets stored beside the frame cache. teacher=None takes
the points from each project's merged detections, sampling only detected frames
(ADR-0071, ADR-0083). A project without an active calibration is skipped.
cache_dir is <project>/cache/unlabeled by default.
Source code in src/deeperfly/pose2d/training/unlabeled.py
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 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 | |
consensus_targets ¶
consensus_targets(
rig,
xy: ndarray,
conf: ndarray,
*,
agree_px: float,
min_views: int,
conf_min: float,
geom_weight: float = 1.0,
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]
Teacher points (V, K, 2) and confidences -> the consensus targets (ADR-0071).
Returns (targets, weights, reference, corrected): the consensus reprojected into
every view, 1 where a consensus exists, the target or else the teacher's point for
the crop, and the cells whose own detection lay more than agree_px away.
geom_weight blends the target toward the teacher's own pixel.
Source code in src/deeperfly/pose2d/training/unlabeled.py
detected_crops ¶
detected_crops(
detections: Detections, n: int = CROP_SAMPLES
) -> dict[str, tuple[int, int, int, int]] | None
The one static crop per view the detections were read through, or None.
Compared over n covered frames spread over the project; None when nothing is
covered, a run stored no crops, or the crop moves (a per-frame window, or runs
that discovered different ones).
Source code in src/deeperfly/pose2d/training/unlabeled.py
discovered_windows ¶
discovered_windows(
project: Project,
input_hw: tuple[int, int],
*,
detections: Detections | None = None,
whole_view: bool = False,
frame_hw: Mapping[str, tuple[int, int]] | None = None,
) -> dict[str, tuple[int, int, int, int]]
Per-view (x0, y0, x1, y1) video windows around the animal.
The static crop the detections were read through
(detected_crops); failing
that, each view whole with whole_view, for a stream whose points come from the
detections, or else the largest centered window at the input's aspect.
frame_hw is each view's frame (height, width), probed when absent.
Raises:
| Type | Description |
|---|---|
ValueError
|
Without a static crop or |
Source code in src/deeperfly/pose2d/training/unlabeled.py
run_teacher ¶
run_teacher(
model,
cache: FrameCache,
frames: Sequence[int],
*,
flip_perm,
flip_tta: bool = True,
moments_per_batch: int = 4,
) -> tuple[np.ndarray, np.ndarray]
The teacher's (T, V, K, 2) video-pixel points and (T, V, K) confidences.
Every view's stored window resized to the model input, as a run's [pose2d] does,
the whole group at once; with flip_tta the mirrored group is averaged in. The
points are on the pixel-center grid whatever the teacher was trained on (ADR-0078).
Source code in src/deeperfly/pose2d/training/unlabeled.py
sample_frames ¶
n frame indices spread evenly over the video, skipping the labeled ones.
guard widens each skipped frame into a band either side, for a fold that holds
out frames of a project the stream still draws from.
Source code in src/deeperfly/pose2d/training/unlabeled.py
Batch plumbing for the loop and the evaluator: view flattening, the inverse affine.
A batch is flat ((B, 1, H, W) images, (B, K, ...) labels) or grouped
((B, V, 1, H, W), (B, V, K, ...) and a view_valid (B, V) mask); forward_flat
hands every consumer the flat layout.
flatten_views ¶
A grouped batch in the flat (B * V, ...) layout; a flat batch as is.
Per-group fields repeat per view; padded slots stay in (weight 0), and the second
return, (B, V), folds the rows back into groups.
Source code in src/deeperfly/pose2d/training/batch.py
forward_flat ¶
forward_flat(
model, batch: dict, *, mask_ratio=None
) -> tuple[object, dict, tuple[int, int] | None]
Run model on batch and return (outputs, flat_batch, group_shape).
The outputs are flat whatever the model: a joint-views model is fed the grouped
images, refuses a flat batch and takes mask_ratio; any other sees the flat images.
Source code in src/deeperfly/pose2d/training/batch.py
to_device ¶
Every tensor of batch moved to device (non-blocking); the rest as is.
to_video_px ¶
Input-normalized (N, K, 2) -> video pixels through the inverse of affine.
affine (N, 2, 3) is the sample's video -> input map, flip included.
Source code in src/deeperfly/pose2d/training/batch.py
The training loop for every detector family, and the per-head supervised loss.
supervised_loss matches the model's output_kind: the heatmap head's Gaussian MSE,
SimCC's per-axis KL, or a probability field's cross-entropy and coordinate term, read
through a local expectation (ADR-0072). batch_loss adds the pair-triangulation loss
to a calibrated grouped batch, unlabeled_loss is the distillation term (ADR-0071),
and fit is a minimal AdamW and cosine loop with bf16 autocast, an optional EMA, the
unlabeled stream and a periodic evaluation.
batch_loss ¶
batch_loss(
model,
batch: dict,
*,
sigma: float = 2.0,
fg_weight: float = 30.0,
field_weight: float = 0.0,
center_weight: float = 0.25,
coord_weight: float = 0.0,
coord_radius: int = 3,
coord_huber: float = 4.0,
tri_weight: float = 0.0,
tri_huber: float = 0.0,
tri_max_world: float = 0.0,
train_decode: str = "local",
)
The training loss of one batch, flat or grouped: (loss, aux).
The supervised term runs over every flat row (a padded view weighs 0). With
tri_weight > 0 and a calibrated grouped batch the pair-triangulation loss is added
per group_ok group, read through training_decode(kind=train_decode).
Source code in src/deeperfly/pose2d/training/loop.py
center_cross_entropy ¶
Cross-entropy between the center channel and the frame's animal centers.
Every valid center of (N, A, 2) renders the joints' Gaussian, and their
renormalized sum is the target: one field with a peak per animal, as the top-k
readout reads it back. A row with no valid center weighs 0.
Source code in src/deeperfly/pose2d/training/loop.py
field_cross_entropy ¶
Cross-entropy between a probability field and a normalized Gaussian target.
The cells are placed by the geometry the readout inverts; raw logits go through a spatial log-softmax.
Source code in src/deeperfly/pose2d/training/loop.py
fit ¶
fit(
model,
batches,
*,
steps: int,
base_lr: float = 0.0002,
weight_decay: float = 0.05,
betas: tuple[float, float] = (0.9, 0.95),
warmup_steps: int = 0,
backbone_lr_mult: float = 1.0,
sigma: float = 2.0,
fg_weight: float = 30.0,
field_weight: float = 0.0,
center_weight: float = 0.25,
coord_weight: float = 0.0,
coord_radius: int = 3,
coord_huber: float = 4.0,
tri_weight: float = 0.0,
tri_huber: float = 0.0,
tri_max_world: float = 0.0,
train_decode: str = "local",
unlabeled_batches=None,
unlabeled_weight: float = 0.0,
mask_ratios: tuple[float, ...] = (),
masked_weight: float = 1.0,
unmasked_weight: float = 0.5,
grad_clip: float = 0.0,
amp: bool = False,
ema_decay: float = 0.0,
eval_every: int = 0,
eval_fn: Callable[[Module], dict] | None = None,
select_best: str | None = None,
checkpoint_fn: Callable[[int, Module], None]
| None = None,
log_every: int = 0,
log: Callable[[str], None] = print,
) -> dict
Train model on batches for steps AdamW steps.
batches and unlabeled_batches are cycled; each step adds unlabeled_weight
times unlabeled_loss. The LR is a linear warmup then a cosine decay. amp runs
the forward in bf16 on CUDA, the geometric loss in float32. With ema_decay > 0 the
EMA (ramped from 0, as timm does) is what eval_fn sees and what model holds at
the end, the raw weights under history["raw_state_dict"]. eval_fn and
checkpoint_fn run every eval_every steps and at the end.
Returns:
| Type | Description |
|---|---|
dict
|
|
Source code in src/deeperfly/pose2d/training/loop.py
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 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 | |
local_expectation ¶
Differentiable sub-cell peak: the field's expectation around its argmax.
(N, K, h, w) -> (N, K, 2) (x, y) in cells. The detached argmax only places the
2 * radius + 1 window, so mass on another limb cannot pull the expectation.
Source code in src/deeperfly/pose2d/training/loop.py
supervised_loss ¶
supervised_loss(
model,
out,
kp_norm,
weight,
*,
sigma: float = 2.0,
fg_weight: float = 30.0,
simcc_sigma: float = 6.0,
field_weight: float = 0.0,
center_weight: float = 0.25,
coord_weight: float = 0.0,
coord_radius: int = 3,
coord_huber: float = 4.0,
centers=None,
center_valid=None,
)
The per-head supervised loss of one flat forward output, by output_kind.
kp_norm is (N, K, 2) input-normalized ground truth (NaN where unlabeled) and
weight (N, K). For a probability field field_weight weighs the Gaussian-target
cross-entropy and coord_weight a Huber loss (coord_huber) on the local
expectation of coord_radius cells (ADR-0072); a center channel adds
center_weight times center_cross_entropy on centers and center_valid
(ADR-0063). A locator for several animals is that cross-entropy per channel alone
(ADR-0127). Returns (loss, aux).
Source code in src/deeperfly/pose2d/training/loop.py
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 | |
training_decode ¶
The differentiable coordinate readout the geometric loss trains through.
(N, K, 2) input-normalized: "local" is local_expectation through the field's
geometry, "softargmax" the first chain's upsampled readout, kept reproducible
(ADR-0072); SimCC decodes through its own soft-argmax.
Raises:
| Type | Description |
|---|---|
ValueError
|
For a hard-argmax head, which has no gradient, or an unknown |
Source code in src/deeperfly/pose2d/training/loop.py
unlabeled_loss ¶
unlabeled_loss(
model,
batch: dict,
*,
sigma: float = 2.0,
mask_ratios: tuple[float, ...] = (),
masked_weight: float = 1.0,
unmasked_weight: float = 0.5,
)
The distillation term on one grouped batch of teacher targets: (loss, aux).
Each view draws a mask ratio from mask_ratios, one per group left whole; the field
cross-entropy weighs masked_weight on masked views and unmasked_weight on whole
ones (ADR-0071). Probability-field heads only.
Source code in src/deeperfly/pose2d/training/loop.py
Keypoint accuracy metrics over decoded coordinates, and the relations they split on.
The caller passes per-point errors in any unit; breakdown reduces them under any
grouping, since a pooled number hides what matters (two claw channels carried 44% of
the squared error on one animal), and relation names the grouping that tracks the
hard cells: ipsilateral, contralateral, midline, or an axial view by name.
breakdown ¶
error_summary per distinct key (zipped 1:1 with errors), in key order.
Source code in src/deeperfly/pose2d/training/metrics.py
error_summary ¶
Per-point errors -> {median, mean, p90, pck, n}; an empty input gives zeros.
Source code in src/deeperfly/pose2d/training/metrics.py
keypoint_side ¶
"left" or "right" for a paired point named l* or r*, else "midline".
Source code in src/deeperfly/pose2d/training/metrics.py
metric_at ¶
One number out of an evaluation report, and whether larger is better.
path is dotted ("overall.pck", "per_view.east.median") and must end in one of
error_summary's metric names, which fix the direction.
Raises:
| Type | Description |
|---|---|
KeyError
|
If the path names nothing in this report, or ends in no metric name. |
Source code in src/deeperfly/pose2d/training/metrics.py
relation ¶
relation(
view_name: str,
keypoint: int,
skeleton,
*,
view_sides: Callable[[str], str | None]
| dict[str, str | None]
| None = None,
) -> str
The relation of a keypoint to the view that sees it.
"ipsilateral", "contralateral" or "midline" for a side view, the view's own
name for an axial one; view_sides overrides view_side's naming convention.
Source code in src/deeperfly/pose2d/training/metrics.py
view_side ¶
A view's side from its name's leading l or r; None for an axial view.
Evaluate a detector over a batch stream: decode, error per cell, break it down.
The inference decode, each sample's affine inverted so the error is in the video pixels the label was clicked in, reduced per keypoint, view, relation, project and in-frame against off-frame. Raw 2D detections are scored, never a reprojection.
evaluate ¶
evaluate(
model,
batches,
*,
dataset,
pck_px: float = 10.0,
view_sides=None,
return_cells: bool = False,
flip_tta: bool = False,
) -> dict
Score model on unaugmented batches from dataset; errors in video pixels.
return_cells adds the per-cell arrays under "cells", for a caller's own
statistics (bootstrap CIs over frames or animals); flip_tta averages in the
mirrored pass. Returns overall, in_frame, off_frame and model_px summaries
and per_keypoint, per_view, per_relation, per_project and
off_frame_per_view breakdowns.
Source code in src/deeperfly/pose2d/training/eval.py
18 19 20 21 22 23 24 25 26 27 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 | |
flip_average ¶
Average pred_norm with the unmirrored prediction on the mirrored batch.
On coordinates, not fields, since the cell grid is not symmetric about the frame
center (ADR-0072); channels permuted by flip_perm.
Source code in src/deeperfly/pose2d/training/eval.py
A training from its config: data, model, loop, held-out evaluation, artifacts.
What deeperfly train drives. A training over projects (ADR-0085) takes them from
[data] projects or from the paths the command is given, names them as a collection
does, and requires one skeleton of them all. It writes into its output directory the
config it ran (with the projects it was given, when they replaced the document's), the
per-step history, the held-out report (per keypoint, view, relation and project, in
video pixels, beside each project's folder) and the weights: a deeperfly-dense38-1
checkpoint for the monocular family, a deeperfly-detector-1 artifact for the others,
which models loads for inference.
boost_repeats ¶
boost_repeats(
owners: Sequence[str],
projects: Sequence[str],
share: float,
) -> tuple[list[float], str]
How often an epoch holds each group under [data.boost], and a log line.
owners names each group's project. The groups of the projects projects selects
are repeated by one factor, the one that makes them share of the epoch, and
never less than once (a selection already past its share is left alone); every
other group is held once.
Raises:
| Type | Description |
|---|---|
ConfigError
|
As match_names, when a pattern selects no project with a labeled moment in the training set. |
Source code in src/deeperfly/pose2d/training/run.py
build_model ¶
Assemble the detector [model] names, its family keys checked at load.
Source code in src/deeperfly/pose2d/training/run.py
dataset_margin ¶
The (y, x) off-frame margin in input pixels the model's field actually has.
Source code in src/deeperfly/pose2d/training/run.py
load_artifact ¶
Rebuild a deeperfly-detector-1 artifact's module on the CPU, in eval mode.
The artifact's [model] table goes through the same assembler (never with
pretrained) and the state dict loads strictly; kwargs override its keys.
Source code in src/deeperfly/pose2d/training/run.py
load_artifact_state ¶
Warm-start model from a run's artifact, non-strictly.
Mismatches are reported, and a start where under half of the tensors came from the
file is refused: that is a different model. A head wider than the parent's takes
the parent's rows (_widened); any other mismatched tensor is dropped whole.
Source code in src/deeperfly/pose2d/training/run.py
locator_animals ¶
How many animals a locator trained on projects puts a peak for per channel.
locator_views ¶
Every view of the projects, by name order, first seen first: the channels.
Read off every project, labeled or not, so an artifact stays readable on a project the training never saw.
Source code in src/deeperfly/pose2d/training/run.py
model_table ¶
The [model] table as the artifact records it (no warm-start reference).
Source code in src/deeperfly/pose2d/training/run.py
out_dir ¶
Where a training writes: out, else [train] out, else beside its config.
Beside the config is <config dir>/<stem>/ (ADR-0085).
Source code in src/deeperfly/pose2d/training/run.py
save_artifact ¶
Write the trained weights: the shipped checkpoint format when one exists.
Source code in src/deeperfly/pose2d/training/run.py
shared_skeleton ¶
The skeleton every project labels (ADR-0085).
Raises:
| Type | Description |
|---|---|
ConfigError
|
If two projects' skeletons differ, naming them. |
Source code in src/deeperfly/pose2d/training/run.py
train ¶
train(
config: TrainConfig,
out_dir: str | Path,
*,
projects: Mapping[str, Project] | None = None,
log=print,
) -> dict
Run one training and return its report (also written to out_dir).
projects (name -> Project, as
training_projects gives them)
replace the document's [data] projects, and the copy of the document in
out_dir then lists their folders in its place.
Source code in src/deeperfly/pose2d/training/run.py
599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 | |
training_projects ¶
training_projects(
cfg: TrainConfig,
paths: Sequence[str | Path] | None = None,
) -> dict[str, Project]
The projects a training takes, by their names in the collection.
paths replace [data] projects and are opened as a collection is (each a
project or a directory of them); the document's patterns expand against its
directory, a match that is no project skipped.
Raises:
| Type | Description |
|---|---|
ConfigError
|
If a pattern matches nothing, a path is no project, or no project is left. |
Source code in src/deeperfly/pose2d/training/run.py
The one loader: a detector's class, weights and input contract, on a device.
A ModelSpec is the torch-free description and
load_model turns it into a
LoadedModel; weights is an already resolved
path. load_models and
load_locator are what a prediction run calls.
LoadedModel ¶
A loaded detector plus its input contract and fused forward and decode.
Wraps the torch module behind the torch-free seam; the resize and normalization are the model's own.
Source code in src/deeperfly/pose2d/models.py
accepts_gray
property
¶
Whether the model takes one-channel frames (both shipped classes do).
joint_views
property
¶
Whether the V axis is coupled: views computed together (the multiview net).
False for a per-view detector, whose V axis is spare batch.
padded_field
property
¶
Whether a peak may land outside the model input: an off-frame joint.
__init__ ¶
device ¶
predict_heatmaps ¶
predict_points ¶
Fused forward and decode -> input-normalized peaks and confidence.
A peak may fall outside [0, 1] on a padded field: an off-frame joint.
Source code in src/deeperfly/pose2d/models.py
set_precision ¶
Set the forward precision (float32 / float16 / bfloat16).
ModelSpec
dataclass
¶
A torch-free description of one detector model.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The model's name, referenced by a pathway's |
cls |
str
|
The registry key selecting the model class (e.g. |
weights |
str | None
|
A resolved checkpoint path. |
input_size |
tuple[int, int]
|
The network input |
mean |
float
|
Scalar subtracted from the |
n_out_channels |
int | None
|
Output heatmap channels (validated against the weights). |
precision |
str | None
|
Per-model forward precision, or |
kwargs |
dict
|
Extra class-specific construction kwargs. |
Source code in src/deeperfly/pose2d/models.py
artifact_grid ¶
The pixel_grid a detector artifact was trained on.
An artifact that records none predates the field, when every label was on the
corner grid, so it reads as corner (ADR-0078); that also serves the released
detectors on shared storage, which nobody rewrites.
Raises:
| Type | Description |
|---|---|
SystemExit
|
If the artifact records a grid this version does not know. |
Source code in src/deeperfly/pose2d/models.py
first_model ¶
The module (the first member of an ensemble) whose geometry the run follows.
grid_shift ¶
The video-pixel shift that puts models' output on the pixel-center grid.
The mean over an ensemble's members, which is exact for a mean combine; a module built for training and never loaded from an artifact is on the pixel-center grid.
Source code in src/deeperfly/pose2d/models.py
load_locator ¶
load_locator(
weights: str | Path,
*,
device: str | None = None,
precision: str = DEFAULT_PRECISION,
) -> tuple[object, torch.device]
Load a full-frame locator artifact onto a device (ADR-0064).
Returns (model, device).
Raises:
| Type | Description |
|---|---|
ValueError
|
If the artifact is a pose detector rather than a locator. |
Source code in src/deeperfly/pose2d/models.py
load_model ¶
Build a LoadedModel from a ModelSpec.
Raises:
| Type | Description |
|---|---|
SystemExit
|
On an unknown class, or one with no loadable checkpoint yet. |
ValueError
|
If the module's channel count or trained input size disagrees with the spec. |
Source code in src/deeperfly/pose2d/models.py
load_models ¶
load_models(
weights: str | Path | Sequence[str | Path],
*,
device: str | None = None,
precision: str = DEFAULT_PRECISION,
) -> tuple[object, torch.device]
Load one multiview artifact, or an ensemble of them, onto a device.
precision defaults to "auto" here, not to the float32 reference, which is about
3x slower (ADR-0069). Returns (model, device): the module, or the list of modules
of an ensemble.
Raises:
| Type | Description |
|---|---|
ValueError
|
If an artifact is not a joint-views (multiview) model, or the ensemble members disagree on input size, keypoints or the center channel. |
Source code in src/deeperfly/pose2d/models.py
marks_others ¶
Whether the detector's inputs mark the other animals' centers (ADR-0148).
An ensemble's members agree (load_models), so the first one answers.
Source code in src/deeperfly/pose2d/models.py
model_point_names ¶
The keypoint names the artifact was trained on, or [] if it recorded none.
The knobs of a prediction run, as the library reads them.
A run config writes them as [pose2d] and [pose2d.tracking], where
Pose2dStage checks them.
PredictOptions
dataclass
¶
How a recording is predicted: a run's [pose2d] knobs, apart from its inputs.
Attributes:
| Name | Type | Description |
|---|---|---|
window |
str
|
|
animals |
int
|
How many animals each frame holds; above one the detector needs a center channel and the run tracks them. |
tracking |
Tracking
|
The multi-animal tracker's knobs. |
crop_aspect |
Mapping[str, float] | None
|
Per named view, the aspect its window is drawn at instead of
the input's (a run config's |
combine |
str
|
How an ensemble's members are collapsed: |
candidates |
int
|
Distinct peaks kept per cell besides the decoded point; 0 for none. |
tightness |
float
|
How much wider than the animal a discovered window is: the static windows, and the size a per-frame window keeps. |
batch_frames |
int
|
Frames per block of the stream, each predicted once per animal. |
flip_perm |
Any | None
|
The skeleton's left/right permutation (a |
point_names |
Sequence[str] | None
|
Keypoint names to fall back on when the artifact recorded none. |
compile |
bool
|
Put the pose net's forward and readout through |
Source code in src/deeperfly/pose2d/options.py
__post_init__ ¶
Refuse a window or a combine rule that is not one of the two.
Source code in src/deeperfly/pose2d/options.py
Tracking
dataclass
¶
How center peaks become animals and stay themselves, in video pixels (ADR-0063).
The defaults are [pose2d.tracking]'s (Tracking).
Attributes:
| Name | Type | Description |
|---|---|---|
min_score |
float
|
What a center peak must score to count as a detection. |
min_views |
int
|
Views that must see a center before it is an animal. |
gate_px |
float
|
A view supports a 3D center when its nearest peak reprojects within this. |
min_separation_px |
float
|
Two centers closer than this in every view are one animal found twice (touching flies on hexaview are 85-95 px apart in the views across them, ADR-0131). |
max_step_px |
float
|
The frame-to-frame link gate, as the largest reprojected jump over the views. |
hold_frames |
int
|
Frames a track keeps its last position without a detection before the slot is freed. |
Source code in src/deeperfly/pose2d/options.py
Where the detector looks: the per-view windows, their warp, and the planners.
WindowGeometry is a moment's windows as one precomputed warp and its inverse (ADR-0069). The planners draw a window per frame around the animals a coarse pass found: Locator for one animal from the pose net's joints, Tracker for several from its center channel (ADR-0063) or a multi-animal locator (ADR-0127), and FullFrameLocator from a locator net over the whole source frame (ADR-0064).
FullFrameLocator ¶
Bases: MeasuredPlanner
Per-frame windows from a locator net of its own, over the whole source frame.
The planner of a rig whose views share a source frame (ADR-0064). Each channel's
peak passes through its view's mirror into view pixels and the measured window is
centered there; a channel below conf_min keeps the previous box, then the
letterbox. Two views of one video whose peaks are within SHARED_PEAK_FRACTION
of the window keep only the stronger one's, and the other is missing in that
frame. One animal only.
Source code in src/deeperfly/pose2d/windows.py
__init__ ¶
The locator's own source-frame geometry; input_hw is the pose net's.
Source code in src/deeperfly/pose2d/windows.py
coarse ¶
The locator over this block's source frames, at the locator's own input.
Returns (T, V, 1, 3) video-pixel peaks.
plan ¶
(T, V, 1, 3) video-pixel peaks -> the one animal's window per view.
Source code in src/deeperfly/pose2d/windows.py
Locator ¶
Bases: WindowPlanner
Per-frame windows: each view's box around the animal the coarse pass found.
Per (frame, view): the box of the confident coarse points, else the previous
frame's, else the letterbox; causal, since at 125 fps a walking fly moves about
20 px a frame inside a window of hundreds. The single-animal planner: no rig, no
center channel, and its one slot is never lost. On a rig that folds several views
into one frame its coarse pass cannot tell the views' images apart (ADR-0064).
Source code in src/deeperfly/pose2d/windows.py
__init__ ¶
__init__(
videos,
input_hw,
*,
crop_aspect=None,
tightness: float = 1.5,
conf_min: float = 0.3,
min_points: int = 4,
) -> None
Size the boxes from the pose net's input_hw over videos' frame sizes.
Source code in src/deeperfly/pose2d/windows.py
boxes ¶
(T, V, K, 2) coarse points + (T, V, K) conf -> (T, V, 4) boxes.
Source code in src/deeperfly/pose2d/windows.py
plan ¶
The one animal's box per (frame, view); no centers, and nothing lost.
Source code in src/deeperfly/pose2d/windows.py
MeasuredPlanner ¶
Bases: WindowPlanner
A planner whose window is one measured size per view, around a located animal.
The window travels with the animal and its scale does not, so the detector sees the
animal at the scale it trained on. Until discover_sizes runs, sizes is as wide
as the input at the view's aspect.
Attributes:
| Name | Type | Description |
|---|---|---|
aspects |
list[float]
|
Per view, the width / height its window is drawn at. |
min_w |
float
|
Half the input's width, so a view is never upsampled more than 2x. |
sizes |
ndarray
|
|
Source code in src/deeperfly/pose2d/windows.py
__init__ ¶
Size the windows of videos for a pose net taking input_hw.
Source code in src/deeperfly/pose2d/windows.py
SourceLocator ¶
A locator net over each whole source frame, its peaks in view pixels (ADR-0064).
Channel k of the net is the view point_names[k], so a peak comes back already
attributed to a view. A source is decoded, uploaded and run once however many views
read it. Shared by FullFrameLocator,
one peak a view, and Tracker, several.
Source code in src/deeperfly/pose2d/windows.py
645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 | |
__init__ ¶
The locator's own source-frame geometry over videos.
Source code in src/deeperfly/pose2d/windows.py
peaks ¶
This block's source frames -> (T, V, k, 3) video-pixel (x, y, score).
k = 1 is the argmax readout; above it, the k strongest distinct maxima of
each channel, NaN at score 0 where a channel has fewer, a blob that holds
several of the animals split among them
(split_shared_peaks).
Source code in src/deeperfly/pose2d/windows.py
Tracker ¶
Bases: MeasuredPlanner
The animals of each frame, and the windows the fine pass runs on (ADR-0063).
One per recording, so the tracks and sizes survive the block loop. A step is
assign_centers, then
link_step, then
center_windows, with the run config's
[pose2d.tracking] knobs. The center peaks come from the pose net's center
channel, or, given a locator, from a locator trained on several animals, which a
rig whose views share a frame needs (ADR-0127).
Source code in src/deeperfly/pose2d/windows.py
733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 | |
__init__ ¶
__init__(
rig: Rig,
videos,
input_hw,
*,
animals: int,
tracking: Tracking,
crop_aspect=None,
locator=None,
device=None,
) -> None
Plan animals slots through rig with the tracking knobs.
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/deeperfly/pose2d/windows.py
coarse ¶
The locator's (T, V, n_centers, 3) peaks, or None for the pose net's.
plan ¶
(T, V, C, 3) video-pixel center peaks -> this block's animals.
Returns:
| Type | Description |
|---|---|
|
|
Source code in src/deeperfly/pose2d/windows.py
reset ¶
Forget every track, so the next step starts the slots over.
WindowGeometry
dataclass
¶
Everything constant about a moment's warp and its inverse, precomputed on device.
Built by build and shared by every moment of a recording.
Attributes:
| Name | Type | Description |
|---|---|---|
input_hw |
tuple[int, int]
|
The network input |
frame_hw |
tuple[tuple[int, int], ...]
|
Per window, the |
source_of |
tuple[int, ...]
|
Per window, which of the frames |
shrink_hw |
tuple[tuple[int, int], ...]
|
Per view, the pre-shrink size, |
canvas_hw |
tuple[int, int]
|
The common |
windows |
tuple[Affine2D, ...]
|
Per view, the window affine (video pixels -> input pixels), mirror included. |
grid |
Tensor
|
|
inv_linear |
Tensor
|
|
inv_offset |
Tensor
|
|
px_scale |
Tensor
|
|
Source code in src/deeperfly/pose2d/windows.py
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 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 | |
build
classmethod
¶
build(
boxes: Sequence[Sequence[float]],
frame_hw: Sequence[Sequence[int]],
input_hw: Sequence[int],
*,
device,
sources: Sequence[int] | None = None,
views: Sequence[bool] | None = None,
) -> WindowGeometry
Precompute the warp and its inverse for one set of per-view windows.
boxes are (x0, y0, x1, y1) slice bounds in each frame's own pixels.
sources says which frame given to fill each window reads (None: one each,
in order), and views whether each is mirrored (None: none is).
Source code in src/deeperfly/pose2d/windows.py
fill ¶
Write each window's (T, H, W) or (H, W) source frame into canvas.
frames is indexed by source_of: host uint8 arrays, or a stacked on-device
tensor (the online path's buffer, indexed without a copy). Integer input is
scaled by 1/255, floats are taken as normalized; a shared source is uploaded,
and pre-shrunk, once. Returns canvas.
Source code in src/deeperfly/pose2d/windows.py
new_canvas ¶
A zero (V, T, H, W) canvas for n_frames moments, on the grid's device.
It must stay zero outside each view's shrunk frame, which is what makes the common canvas identical to per-view buffers; reusing one only overwrites the written region.
Source code in src/deeperfly/pose2d/windows.py
sample ¶
(V, T, H, W) canvas -> the (T, V, 1, h, w) network input, in one kernel.
T rides in grid_sample's channel axis, so one grid of V rows covers a
whole block of moments.
Source code in src/deeperfly/pose2d/windows.py
to_video_px ¶
(..., V, N, 2) input-normalized points -> video pixels, all views at once.
N is whatever the caller stacks per view: keypoints, or keypoints times
candidates.
Source code in src/deeperfly/pose2d/windows.py
warp ¶
The sources' (T, H, W) uint8 frames -> the (T, V, 1, h, w) input.
Source code in src/deeperfly/pose2d/windows.py
WindowPlanner ¶
One block's coarse pass -> the windows its fine pass runs through.
A planner declares which output of the pose net's coarse pass it reads, the joints
or the center peaks, and how many animals it plans; one whose coarse pass is
another net overrides coarse.
Attributes:
| Name | Type | Description |
|---|---|---|
animals |
int
|
How many slots |
n_centers |
int
|
Center peaks per view the coarse pass decodes; 0 asks for the joints instead. |
letterbox |
object
|
Per view, the whole-frame window the coarse pass runs through. |
Source code in src/deeperfly/pose2d/windows.py
coarse ¶
A planner's own coarse pass over one block, or None for the pose net's.
The pose net's pass runs over the letterbox windows and hands plan the
video-pixel joints and conf, or the center peaks at n_centers > 0. A planner
with a net of its own returns (points, conf, centers) itself.
Source code in src/deeperfly/pose2d/windows.py
plan ¶
One block's coarse output -> its windows, its animals and their flags.
points (T, V, K, 2) and conf (T, V, K) are video-pixel joints, None at
n_centers > 0; centers (T, V, C, 3) are video-pixel peaks as
(x, y, score), None at n_centers == 0.
Returns:
| Type | Description |
|---|---|
|
|
|
|
|
Source code in src/deeperfly/pose2d/windows.py
aspect_window ¶
aspect_window(
hw: tuple[int, int], input_hw: tuple[int, int]
) -> tuple[float, float, float, float]
The largest centered window of an hw image at the input's aspect.
Source code in src/deeperfly/pose2d/windows.py
follow_affines ¶
follow_affines(
boxes: Tensor,
shrink_scale: Tensor,
canvas_hw: Sequence[int],
input_hw: Sequence[int],
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]
(V, 4) windows -> affine_grid thetas and the inverse map, on their device.
What WindowGeometry.build computes
host-side, per moment and without mirrors: shrink_scale (V, 2) is each view's
[x, y] pre-shrink (shrunk size over frame size) and canvas_hw the canvas the
shrunk frames sit in. Returns theta (V, 2, 3), input-normalized -> canvas-
normalized, and scale, offset (V, 2), input pixels -> video pixels as
px * scale + offset.
Source code in src/deeperfly/pose2d/windows.py
follow_boxes ¶
follow_boxes(
points: Tensor,
conf: Tensor,
static: Tensor,
*,
have_prev: Tensor,
force_static: Tensor,
aspects: Tensor,
min_w: float,
tightness: float = 1.25,
conf_min: float = 0.3,
keep_frac: float = 0.85,
floor: float = 0.8,
min_points: int = 4,
) -> torch.Tensor
The last pose -> each view's next window, (V, 4), on the pose's device.
points (V, K, 2) and conf (V, K) are the previous moment's, in video
pixels. The box is Locator's (the keep_frac
most confident points at or above conf_min), grown to hold floor times the box
of every finite point about its own center, then drawn at tightness at the view's
aspect and at least min_w wide (ADR-0149). The floor is what stops a tight window
from losing a tip for good: a tip it clips comes back unsure, and the confident box
alone would leave it out again; floor = 0 is the confident box alone.
A view falls back to its static window, the re-acquisition, when it has fewer
than min_points confident points, and every view does before the first pose
(have_prev false) or on demand (force_static). Both flags are 0-d bool tensors,
so nothing here waits on the device.
Source code in src/deeperfly/pose2d/windows.py
mark_others ¶
Draw a white disk in a black ring at every center but own's, in input pixels.
Two animals on top of each other give two nearly identical windows, and nothing
in either says which animal is wanted: both skeletons land on one of them. The
mark is that missing word (ADR-0148). img is (..., H, W) in [0, 1],
centers (A, 2), NaN where an animal is not seen; jitter (pixels, training)
stands for the error of the center a window is placed on. Inference draws the
same mark at the other windows' centers, so the two stay one function.
Source code in src/deeperfly/pose2d/windows.py
padded_window ¶
padded_window(
hw: tuple[int, int], input_hw: tuple[int, int]
) -> tuple[float, float, float, float]
The smallest centered box at the input's aspect holding an hw frame.
The letterbox: the whole frame, the rest black, which an inference given no crop feeds the network instead of a squashed frame.
Source code in src/deeperfly/pose2d/windows.py
window_affine ¶
Frame pixels -> input pixels for one window: the crop, mirrored where it is.
The one definition of what the detector sees, in training and inference. box is
(x0, y0, x1, y1) in the frame's pixels; a mirrored view's crop comes out
reflected (ADR-0053), which is a flip of the input, so the frame's width never
enters.
Source code in src/deeperfly/pose2d/windows.py
One block of frames of every video through the detector, static or planned.
Videos yields, per block of frames, every source's luma in lockstep, so a moment's views arrive together and a frame carrying three views is decoded once. block_pass is one detector pass over a block, through fixed windows or a planner's; run_models collapses an ensemble and averages flip TTA at the coordinate level (the field grid is not mirror-symmetric). Everything comes back in video pixels.
BlockOut
dataclass
¶
One batch of frames through the detector, in video pixels, on the host.
Every per-animal array carries the animal axis behind the frame; N is 1 at one
animal, squeezed off when the recording's arrays are assembled.
Attributes:
| Name | Type | Description |
|---|---|---|
points |
Tensor
|
|
conf |
Tensor
|
|
candidates |
Tensor | None
|
|
candidate_scores |
Tensor | None
|
|
windows |
ndarray | None
|
|
flags |
ndarray
|
|
centers |
ndarray | None
|
|
center_candidates |
ndarray | None
|
|
Source code in src/deeperfly/pose2d/blocks.py
Videos ¶
Open readers over every video of the recording; frames come out per video.
Views that share a video read the same block, each through its own mirror, so a
frame carrying three views is decoded and uploaded once. frames(indices) is random
access and stream_blocks the forward pass.
Source code in src/deeperfly/pose2d/blocks.py
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 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 | |
__init__ ¶
Open one reader per video views read; close releases them.
layout says which video carries each view and which views are mirrored:
everything a detector needs of the rig, and nothing of its calibration.
Source code in src/deeperfly/pose2d/blocks.py
close ¶
frames ¶
Per source, the (T, H, W) uint8 luma of the given frame indices.
geometry ¶
The warp for boxes: one per view, or (N, V) with the views inside.
Source code in src/deeperfly/pose2d/blocks.py
stream_blocks ¶
stream_blocks(
*,
block_size: int,
thread_count: int = 2,
start: int = 0,
stop: int | None = None,
)
Every source's (T, H, W) luma per block, in lockstep: one decode each.
Source code in src/deeperfly/pose2d/blocks.py
window ¶
Video pixels -> input pixels for one window of one view, mirror included.
block_pass ¶
block_pass(
models,
blocks: Sequence[ndarray],
videos: Videos,
geom: WindowGeometry,
*,
planner: WindowPlanner | None,
device,
flip_perm,
combine: str,
candidates: int,
apart: bool = False,
) -> BlockOut
Per-source (T, H, W) luma blocks -> one detector pass, static or planned.
Static (no planner), geom holds the run's fixed N * V windows and the block
goes through them in one batched warp. With a planner, its coarse pass gives one
box per (frame, animal, view) and the fine pass warps per cell; a slot the
planner lost is skipped and comes back NaN at confidence 0, which on single-fly
footage run at two animals saves half the work. Every point comes back in video
pixels on the pixel-center grid, whatever grid the detector was trained on
(ADR-0078). apart says the block's frames are stretches of their own (ADR-0082):
the planner is reset before each, so nothing carries from one to the next.
Source code in src/deeperfly/pose2d/blocks.py
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 | |
run_model ¶
(T, V, 1, h, w) -> points, conf, pools and centers of one member.
(T, V, K, 2) input-normalized points and (T, V, K) conf; the pools are this
member's candidate peaks, one set per pass, empty when candidates is 0.
centers asks for that many center-channel peaks, ((T, V, C, 2), (T, V, C)),
read off the unmirrored pass only, or None.
Source code in src/deeperfly/pose2d/blocks.py
run_models ¶
run_models(
models,
images: Tensor,
flip_perm=None,
combine: str = "mean",
candidates: int = 0,
centers: int = 0,
)
run_model over one module or an ensemble, collapsed by combine.
"mean" averages the members' coordinates and confidences; "median" takes the
per-coordinate median, which one badly placed member cannot move. The candidates
are every member's and pass's peaks pooled around the collapsed point (slot 0), and
the centers are member 0's: an ensemble locates the animals with one member.
Source code in src/deeperfly/pose2d/blocks.py
static_video_px ¶
A static pass's readout -> video pixels, on the readout's device.
pts (T * N, V, K, 2) input-normalized, conf and cand as run_models returns
them, through geom's fixed N * V windows. Returns (points, conf, candidates,
candidate_scores), the last two None without candidates: what block_pass lays
out, before the host copy.
Source code in src/deeperfly/pose2d/blocks.py
A run's static window per view, discovered with the detector.
The network sees a window around the animal, never a stretched frame (ADR-0046). A run has no labels to draw it from, so discover_crops finds it with the detector on sampled frames in three passes: the whole frame letterboxed, a 2x window around what that found, then the final window at 1.5x the animal's box. discover_static_crops is the one entry, which a run calls on the frames it was asked for and keeps what it found in its results (ADR-0082). Above one animal there is a window per animal, and a per-frame run measures one window size per view instead (discover_sizes).
crop_boxes ¶
A crops mapping as the (N, V, 4) windows a static run predicts through.
A view's entry is a window per animal, or one every animal shares (a per-frame run's letterbox). A run config refuses a shared window for a project of more than one animal: one window over two tethered flies would let the detector pose either.
Source code in src/deeperfly/pose2d/crops.py
discover_crops ¶
discover_crops(
models,
videos: Videos,
*,
device,
n_frames: int = 24,
tightness: float = 1.5,
crop_aspect=None,
flip_perm=None,
batch_frames: int = 8,
frames=None,
log: Callable[[str], None] = print,
) -> dict[str, tuple[int, int, int, int]]
Find each view's window around the animal with the detector itself.
Three passes over n_frames evenly spaced frames of frames (every frame by
default); a view whose animal cannot be located keeps the letterbox. tightness is
1.5, not the evaluation's 1.25: the window is one per run and the animal
moves, and a joint leaving the window is not localized at all (15 of 7,323
held-out cells, 323 px mean error, 0.66 px on the overall mean). The model trained
on tightness up to 2.2.
Source code in src/deeperfly/pose2d/crops.py
discover_sizes ¶
discover_sizes(
models,
videos: Videos,
tracker: MeasuredPlanner,
*,
device,
combine: str = "mean",
batch_frames: int = 8,
n_frames: int = 24,
tightness: float = 1.5,
frames=None,
log: Callable[[str], None] = print,
) -> np.ndarray
Measure the animals on sampled frames; one window size per view.
discover_crops for animals that walk: every animal of every sample goes into one
_reduce_box per view, and only the size comes out, stored on the tracker too. A
view whose animals were never found keeps its provisional window. The samples are
drawn from frames, every frame by default.
Source code in src/deeperfly/pose2d/crops.py
discover_static_crops ¶
discover_static_crops(
models,
videos: Videos,
*,
device,
options: PredictOptions | None = None,
frames=None,
rig: Rig | None = None,
log: Callable[[str], None] = print,
) -> np.ndarray
A static run's crop: one rectangle per animal and view, (N, V, 4).
Found with the detector on frames sampled from frames, the ones the run was asked
for (every frame by default), and rounded to whole pixels; a run discovers its own
and keeps it in its results (ADR-0082). Above one animal each view gets a rectangle
per animal, measured through a Tracker on rig, the one reader of a calibration
here.
Raises:
| Type | Description |
|---|---|
ValueError
|
If an animal turns out to walk (see |
Source code in src/deeperfly/pose2d/crops.py
Run a multiview detector over a project's recording, whole or by chosen frames.
predict_recording streams a frame set through the model, one synchronized group per moment, through fixed windows or windows planned per frame, one stretch of contiguous frames at a time, in video pixels; predict_frames reads chosen frames by random access.
FramePredictions
dataclass
¶
Detections at chosen frames of the recording, in video pixels.
What predict_frames returns: the
detector's arrays over the frames asked for instead of every frame.
Attributes:
| Name | Type | Description |
|---|---|---|
view_names |
tuple[str, ...]
|
The views, in order (the |
frames |
tuple[int, ...]
|
The frame indices predicted, in the order given. |
points |
ndarray
|
|
conf |
ndarray
|
|
windows |
ndarray | None
|
|
Source code in src/deeperfly/pose2d/predict.py
StretchPredictions
dataclass
¶
One stretch of a pass: the detections over contiguous frames, in video pixels.
What predict_recording yields, one
per stretch of the frames asked for (ADR-0082). Every per-animal array leads with
N, one animal included, and has the stretch's S frames where a results file
has T. crops is the rectangle each animal was read through, per view and
frame, as slice bounds on the raw frame (ADR-0076): the planner's, or the static
crop at every frame.
Attributes:
| Name | Type | Description |
|---|---|---|
view_names |
tuple[str, ...]
|
The views, in order (the |
keypoint_names |
tuple[str, ...]
|
The detector's points, in order (the |
start |
int
|
The stretch's first frame. |
stop |
int
|
One past its last frame. |
points |
ndarray
|
|
conf |
ndarray
|
|
crops |
ndarray
|
|
candidates |
ndarray | None
|
|
candidate_scores |
ndarray | None
|
|
centers |
ndarray | None
|
|
track_flags |
ndarray | None
|
|
center_candidates |
ndarray | None
|
|
Source code in src/deeperfly/pose2d/predict.py
flip_permutation ¶
The skeleton's left/right permutation as a tensor, or None without TTA.
Source code in src/deeperfly/pose2d/predict.py
predict_frames ¶
predict_frames(
models,
recording: Recording,
frames: Sequence[int],
*,
views: Sequence[str],
layout: Layout | None = None,
device,
options: PredictOptions | None = None,
crops: dict[str, tuple[int, int, int, int]]
| None = None,
locator=None,
) -> FramePredictions
Predict chosen frames of recording by random access, static or per frame.
The evaluation counterpart of predict_recording over a list of frames. Static
windows are crops, or each view's letterbox when None; per frame, the planner
runs over the frames in the order given, so consecutive frames keep its fallback.
A locator places windows but cannot size them, so it costs the recording's
measurement pass first. views are the V axis, read through layout; nothing
of a calibration is needed. options.animals, tracking and candidates are not
read.
Source code in src/deeperfly/pose2d/predict.py
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 | |
predict_recording ¶
predict_recording(
models,
recording: Recording,
*,
views: Sequence[str],
layout: Layout | None = None,
frames: Sequence[int] | ndarray | None = None,
crops: ndarray | None = None,
rig: Rig | None = None,
device,
options: PredictOptions | None = None,
locator=None,
log: Callable[[str], None] = print,
on_block: Callable[..., None] | None = None,
) -> Iterator[StretchPredictions]
frames of recording through models, one stretch at a time, in video pixels.
frames (every frame by default) is unioned and sorted, and each stretch of it, a
maximal contiguous range, is one linear decode (ADR-0069) that the planner starts
afresh: a planner's previous box and the tracks never cross a gap (ADR-0082). One
StretchPredictions comes back per
stretch, as it is computed. One-frame stretches in a row, as a stepped range makes,
carry nothing between them and share detector passes of options.batch_frames.
options.window = "static" predicts through the fixed crops, (N, V, 4) one
rectangle per animal and view, or, without them, the ones discovered on the frames
asked for. "per_frame" ignores crops: a coarse pass over each view's letterbox
locates the animals, a planner draws one box per (frame, animal, view), and the
frames are predicted again through those boxes, at about twice the cost of static.
Above one animal the animals are found by the detector's center channel or by a
locator trained on several (ADR-0127); per frame a Tracker follows them, while
static windows hold tethered ones. locator replaces the coarse pass of a
per-frame run (ADR-0064). The tracker's and the locator's boxes have one size per
view, measured on frames spread over the whole recording, so that they do not
depend on the frames asked for (ADR-0089). The keypoint names are the detector's,
or options.point_names for an artifact that recorded none.
on_block, when given, sees each detector pass as it completes, before its stretch
is assembled: on_block(frames, points, conf, boxes, images) with the pass's
absolute frame indices (T,), points (T, N, V, K, 2) and conf (T, N, V, K) in
video pixels, the windows they ran through (T, N, V, 4), and per video the
(T, H, W) uint8 luma decoded. The arrays are the predictor's own: read them, and
copy what is kept. What it raises ends the iteration, the videos closed.
views are the V axis, read through layout. Only several animals need a
calibration, to triangulate their centers: rig is read by the tracker and by the
discovery of several static crops, and nowhere else.
Raises:
| Type | Description |
|---|---|
ValueError
|
Up front, for options that contradict each other, a crop of the wrong shape or a frame outside the recording; while iterating, for an animal that walks out of a static crop. |
Source code in src/deeperfly/pose2d/predict.py
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 | |
Predict one moment at a time, for a rig that is producing frames right now.
MomentPredictor is the online counterpart of
predict_recording, over the same geometry
and decode (ADR-0069). It freezes every shape at construction, takes each moment from
pinned host buffers a camera SDK writes into and reads the points out of a pinned
output buffer: no .cpu() per moment and nothing allocated after the first call.
"latency" keeps one moment in flight and "throughput" two. With
Follow each moment's windows come from the previous
moment's pose instead of staying where crops put them (ADR-0149).
Follow
dataclass
¶
Windows that follow the animal: each moment's from the previous moment's pose.
On a tethered rig the static windows must hold the legs' full reach for the whole recording, so they are cut loose; a window drawn around the last pose can be cut tight, which gives the network more pixels on the animal (ADR-0149). The box rule is follow_boxes's.
Attributes:
| Name | Type | Description |
|---|---|---|
tightness |
float
|
How much wider than the last pose a window is. |
conf_min |
float
|
Confidence a keypoint needs to count toward a box. |
keep_frac |
float
|
Share of the confident keypoints, the most confident, a box is drawn around. |
floor |
float
|
The box holds at least this share of the box of every keypoint. |
min_points |
int
|
A view with fewer confident keypoints re-acquires: its next window is its static one. |
reacquire_every |
int
|
Every this many moments, every view re-acquires; 0 never. |
static_tightness |
float
|
What the static |
Source code in src/deeperfly/pose2d/stream.py
MomentPredictor ¶
One moment of V synchronized frames in, (V, K, 2) video-pixel points out.
Attributes:
| Name | Type | Description |
|---|---|---|
frames |
tuple[Tensor, ...]
|
One pinned |
windows |
The |
Source code in src/deeperfly/pose2d/stream.py
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 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 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 | |
frames
property
¶
The pinned per-view buffers the next submit takes its moment from.
__init__ ¶
__init__(
model,
*,
crops,
frame_hw,
views=None,
precision: str = "auto",
device=None,
mode: str = "latency",
compile_mode: str = "off",
graph: bool = False,
follow: Follow | None = None,
) -> None
Freeze the shapes, allocate the buffers and warm the forward up.
model is a loaded joint-views detector. crops is a window per view: a
mapping from view name (in views order, or its own) or a sequence of boxes.
frame_hw is one (height, width) or one per view. compile_mode is "off"
or a torch.compile mode, applied with dynamic=False, and graph captures
the step as a CUDA graph; a capture that fails leaves the eager step and
records why in graph_error. follow moves the windows with the animal; then
crops are the windows it starts from and re-acquires with.
Source code in src/deeperfly/pose2d/stream.py
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 | |
collect ¶
Wait for the oldest moment: (V, K, 2) video pixels and (V, K) conf.
Source code in src/deeperfly/pose2d/stream.py
predict ¶
One moment, submitted and collected, for a caller with no pipeline.
Source code in src/deeperfly/pose2d/stream.py
reacquire ¶
reset ¶
Forget the last pose: the next moment runs through the static windows.
submit ¶
Stage frames and launch the moment, with no sync and nothing read back.