lightning_pose.data
lightning_pose.data.augmentations Module
Functions to build augmentation pipelines from imgaug transform names or preset strings.
imgaug_transform() builds an iaa.Sequential pipeline from a params dict (transform
name -> {p, args, kwargs}); see its docstring for the dict format.
expand_imgaug_str_to_dict() expands a shorthand preset string ("dlc", "dlc-mv",
etc.) into that same params-dict format.
Adding a new preset: add its string to _allowed_imgaug_strs and a branch in
expand_imgaug_str_to_dict() that builds params_dict the same way the existing
"dlc" branches do – one params_dict[<TransformName>] = {"p": ..., "kwargs": {...}}
entry per imgaug transform, using if not params.endswith("mv") to skip transforms that
don’t make sense for multiview data (e.g. per-view crop/rotation).
lightning_pose.data.cameras Module
Camera geometry utilities for multi-view 2D-to-3D projection and triangulation.
lightning_pose.data.video.dali Module
Data pipelines based on efficient video reading by nvidia dali package.
Import warning
nvidia-dali-cuda110 is not installed on CPU-only machines (macOS, GPU-less Linux), so
importing this module at the top level of any other module will raise ImportError on those
platforms and break import lightning_pose entirely. Always import from this module lazily,
inside the function or method body that uses it:
def my_function(...):
from lightning_pose.data.video.dali import PrepareDALI # lazy: avoids cpu-only ImportError
...
Architecture overview
PrepareDALI is the entry point. Its __init__ validates inputs and pre-computes
pipeline arguments for all four combinations of stage ("train", "predict") and
model type ("base", "context"). Calling the instance (__call__) builds the DALI
pipe for the requested combination and returns a ready-to-iterate LitDaliWrapper.
LitDaliWrapper extends DALIGenericIterator and converts raw DALI output into typed
UnlabeledBatchDict or MultiviewUnlabeledBatchDict instances on every __next__.
Two prediction modes
- Standard mode (no
bbox_df): DALI resizes frames to
resize_dimson the GPU. Thebboxfield of each returned batch covers the full frame (x=0, y=0, h=H, w=W).- Bbox-crop mode (
bbox_dfsupplied toPrepareDALI): DALI delivers full-resolution frames (
resize_dims=Nonein the predict pipe) so thatLitDaliWrapper._apply_bbox_cropcan crop each frame to its per-frame bounding box and resize to the originalresize_dimsusingtorch.nn.functional.interpolate. Thebboxfield of each batch contains the actual crop coordinates, so downstream code can remap predictions back to the original coordinate space.
lightning_pose.data.video.factory Module
Reader-selection dispatch: turns a backend name (or None) into a built dataloader.
Single entry point: build_video_reader(). Resolves which backend to use –
either the caller’s explicit choice, validated against this machine/video, or an
auto-select fallback chain (pynvvc -> dali -> opencv, richest/fastest first, most
portable last) – then constructs and returns that backend’s ready-to-iterate loader.
Adding a new video reader
Every backend module (dali.py, pynvvc.py, opencv.py, …) implements the
same contract. To add one:
Two-phase prepare class:
Prepare<Name>.__init__(model_type, filenames, resize_dims, dali_config, bbox_df=None, ...)validates inputs and precomputes windowing parameters (raise early, before any GPU/decoder allocation);__call__()builds and returns aLit<Name>Wrapper. (Thedali_configparameter name is a pre-existing wart –PreparePynvvcalready reusescfg.dali’s windowing section rather than inventing a per-backend config block, since the windowing semantics are decoder-agnostic; new backends should follow the same precedent rather than fixing it in isolation.)Iterator wrapper class:
Lit<Name>Wrapperis iterable, defines__len__, and yieldsUnlabeledBatchDict/MultiviewUnlabeledBatchDict– FCHW float frames, ImageNet-normalized, plustransformsandbbox. MatchLitDaliWrapper/LitPynvvcWrapper’s shape exactly so downstream code (PredictionHandler,trainer.predict) doesn’t need to know which backend produced a batch.Availability probe (optional):
is_<name>_available(video_path, ...) -> bool, only needed if the backend can be installed but still unusable for a given machine/video (wrong hardware/driver generation, as with pynvvc). Skip it for a backend that’s simply present or absent as a plain package dependency.Import discipline: if the backend’s package is platform-gated or proprietary (not a guaranteed cross-platform install), every import of it must be lazy – inside the function or method body that uses it, never at module top level (see the
# lazy: avoids ImportError on cpu-only installsconvention indali.py/pynvvc.py). A top-level import of such a package anywhere in the package or tests will breakimport lightning_poseon machines that don’t have it. If the package is an unconditional, cross-platform dependency already declared inpyproject.toml(likeopencv-python-headless), top-level imports are fine.Wire it in:
add the name to
_Readerinlightning_pose/utils/inference_types.py(the single source for the CLI’s--readerchoices and API type hints);add a branch – and, if step 3 applies, a fallback rung – in
build_video_reader()(this file);update the
--readerhelp text inlightning_pose/cli/commands/predict.py;add this module to the package map in
lightning_pose.data.video’s docstring.
Tests: add
tests/data/video/test_<name>.pymirroring a sibling reader’s test module one-for-one, plus a case intests/data/video/test_factory.pycovering its fallback/validation branch; mark any test that needs real hardware with@pytest.mark.gpuso the CPU CI workflow (pytest -m "not gpu") still exercises everything else.
lightning_pose.data.video.opencv Module
Data pipeline for video prediction based on OpenCV (cv2.VideoCapture).
Import discipline
Unlike dali.py/pynvvc.py, cv2 (opencv-python-headless) is an unconditional,
cross-platform dependency already declared in pyproject.toml and already imported at module
top level elsewhere in this package (e.g. lightning_pose.data.cameras,
lightning_pose.utils.predictions). So, unlike those two backends, this module imports cv2
eagerly; no lazy-import discipline is needed here (see lightning_pose.data.video’s package
docstring for when lazy imports are required).
Architecture overview
Mirrors lightning_pose.data.video.pynvvc’s two-phase construction: PrepareOpenCV.__init__
validates inputs and precomputes windowing parameters; calling the instance (__call__) builds
and returns a ready-to-iterate LitOpenCVWrapper.
Predict-only, like pynvvc: this backend never runs random_shuffle or the imgaug
augmentation pipeline, since litpose predict already runs with both off. Training continues
to go through DALI exclusively (lightning_pose.data.video.dali).
Sequential decode, not seek-based
Context (MHCRNN) models read overlapping windows: consecutive sequence_length-frame windows
advance by step = sequence_length - 4, so the last 4 frames of window i are the same
physical frames as the first 4 of window i+1. cv2.VideoCapture’s CAP_PROP_POS_FRAMES
seeking is not reliably frame-exact on containers with B-frames or a variable/estimated frame
rate – a seek-per-window implementation risks silently misaligning that overlap: no crash, no
shape change, just a temporally-shifted context stack feeding a plausible-but-wrong prediction.
To avoid that, LitOpenCVWrapper never seeks: it opens each view’s cv2.VideoCapture once
and reads strictly forward via cap.read(), caching the trailing 4 frames of each window in
self._tail to prepend to the next one. Every physical frame is decoded exactly once (aside
from the cached tail).
lightning_pose.data.video.pynvvc Module
Data pipeline for video prediction based on PyNvVideoCodec (direct NVDEC access).
Import warning
Same discipline as lightning_pose.data.video.dali: pynvvideocodec is an unconditional
but platform-gated dependency (Linux x86_64 only), so importing this module at the top
level of any other module could raise ImportError/ModuleNotFoundError on other
platforms. Always import from this module lazily, inside the function or method body
that uses it:
def my_function(...):
from lightning_pose.data.video.pynvvc import PreparePynvvc # lazy
...
The PyNvVideoCodec package itself is additionally deferred to inside
LitPynvvcWrapper.__init__ and is_pynvvc_available specifically, rather than
this module’s top level, since import PyNvVideoCodec succeeds regardless of GPU
generation or driver age (Turing/Ampere/Ada/Hopper/Blackwell only, driver >= 530.41.03
on Linux) – the failure only surfaces when a decoder is actually constructed.
Architecture overview
Mirrors lightning_pose.data.video.dali’s two-phase construction: PreparePynvvc.__init__
validates inputs and precomputes windowing parameters; calling the instance
(__call__) builds and returns a ready-to-iterate LitPynvvcWrapper.
Predict-only, unlike PrepareDALI: this backend never runs random_shuffle or the
imgaug augmentation pipeline, since litpose predict already runs with both off.
Training continues to go through DALI exclusively (lightning_pose.data.video.dali).
CUDA stream synchronization
PyNvVideoCodec.SimpleDecoder writes decoded frames asynchronously on its own
internal CUDA stream and hands them back as DLPack buffers. If a consumer (this
module’s own resize/normalize step, or eventually the model’s forward pass) reads
those buffers on a different stream before the decode is actually finished, the read
happens on stale/partial data – silently. No crash, no NaN, just quietly wrong
pixels feeding a plausible-looking keypoint prediction. LitPynvvcWrapper.__next__
guards against this with an explicit torch.cuda.current_stream().synchronize()
after building each batch’s frame tensors and before returning them. This is the
“safe but not necessarily optimal” fix flagged in issue #476 – passing an explicit
stream handle into the decoder constructor (if supported) would avoid the sync cost
entirely, but that needs checking against the installed PyNvVideoCodec version and is
tracked as a follow-up, not done here.
lightning_pose.data.datamodules Module
Data modules split a dataset into train, val, and test modules.
BaseDataModule wraps a labeled dataset and splits it via _setup.
UnlabeledDataModule (extends BaseDataModule) additionally builds a DALI video
dataloader for semi-supervised training.
Split logic (BaseDataModule._setup): the imgaug pipeline always contains at least one
element (a final resize transform appended by BaseTrackingDataset.__init__), so
len(imgaug_transform) == 1 means “resize only, no augmentations.” When that’s true and
imgaug_hflip is False, all three splits share one underlying dataset object (cheap path).
Otherwise three deep-copied datasets are created, with the val/test copies’ pipelines reset to
resize-only and imgaug_hflip reset to False. Any new augmentation applied outside the
imgaug pipeline (like imgaug_hflip) must be added to this condition and explicitly
stripped from val/test in the else branch – see datasets.py’s module docstring for the
full “adding a keypoint-affecting augmentation” recipe.
lightning_pose.data.datasets Module
Dataset objects store images, labels, and functions for manipulation.
Three classes, in a hierarchy:
BaseTrackingDataset– base class. Loads images and (x, y) keypoints, applies the imgaug pipeline, and handlesimgaug_hflip. Returns aBaseLabeledExampleDict.HeatmapDataset(extendsBaseTrackingDataset) – addscompute_heatmapto convert keypoints to(K, H, W)Gaussian heatmap targets, and synthesizesself.visibilityfrom NaN positions when the CSV has novisiblecolumn. Returns aHeatmapLabeledExampleDict.MultiviewHeatmapDataset– does not inherit fromBaseTrackingDataset. Holds adict[str, HeatmapDataset]atself.dataset(keyed by view name);__getitem__delegates to each child and stacks results. Sharesimgaug_transform/imgaug_hflipattributes with child datasets so the data module can update them in one pass.
__getitem__ branching: both BaseTrackingDataset and HeatmapDataset branch on
self.do_context at the top of __getitem__ – the non-context branch loads a single PIL
image, the context branch loads a sequence of frames – and all augmentation logic (imgaug
pipeline + hflip) is duplicated in both branches, so a change to one must be mirrored in the
other.
Adding a keypoint-affecting augmentation: add any per-sample random state at the top of
each __getitem__ branch (not outside them, since the two paths are structurally
independent); apply it after the imgaug pipeline so keypoints are already in resized
coordinate space; for context mode, draw the random decision once and reuse it for every frame
in the sequence. See CLAUDE.md’s “Adding an augmentation that affects keypoints” section for
the full step-by-step, including the imgaug_hflip-reset requirements in
datamodules.py’s _setup and in api/model.py’s _build_datamodule_pred.
lightning_pose.data.datatypes Module
Classes to streamline data typechecking.
Classes
Return type when calling __getitem__() on BaseTrackingDataset. |
|
Return type when calling __getitem__() on HeatmapTrackingDataset. |
|
Return type when calling __getitem__() on MultiviewDataset. |
|
Return type when calling __getitem__() on MultiviewHeatmapDataset. |
|
Batch type for base labeled data. |
|
Batch type for heatmap labeled data. |
|
Batch type for multiview labeled data. |
|
Batch type for multiview heatmap labeled data. |
|
Batch type for unlabeled data. |
|
Batch type for multiview unlabeled data. |
|
Batch type for base labeled+unlabeled data. |
|
Batch type for heatmap labeled+unlabeled data. |
|
Return type when calling train/val/test_dataloader() on semi-supervised models. |
lightning_pose.data.extractor Module
Helper class to extract labeled data from a data module.
lightning_pose.data.factory Module
Factory functions to build data pipeline components from a Hydra config.
Three public functions, typically called in order:
get_imgaug_transform()— builds an imgaug augmentation pipeline fromcfg.training.imgaug.get_dataset()— wraps the labeled CSV data in the appropriate dataset class (regression, single-view heatmap, or multiview heatmap).get_data_module()— wraps a dataset in a data module that handles train/val/test splitting; selectsUnlabeledDataModulefor semi-supervised training (adds DALI video loader) orBaseDataModulefor supervised-only training.
Adding a new model type (data-side changes only — see models/factory.py for the
model-side steps):
If the new type can reuse an existing dataset class (e.g. it is a heatmap variant), extend the appropriate
elifbranch inget_dataset()to match the newcfg.model.model_typestring. If it needs a new dataset class, define that class indatasets.py, import it here, and add a newelifbranch.If the new type needs a different
BaseDataModulesubclass, add a branch inget_data_module(); otherwise no change is needed there.
lightning_pose.data.utils Module
Dataset/data module utilities.