Add MultiCameraPoseEstimation and CalibratedCamera neurodata types - #57
Add MultiCameraPoseEstimation and CalibratedCamera neurodata types#57alessandratrapani wants to merge 21 commits into
MultiCameraPoseEstimation and CalibratedCamera neurodata types#57Conversation
…` neurodata types ## Add `MultiCameraPoseEstimation`, `CameraView`, and `CameraCalibration` neurodata types ### Motivation `PoseEstimation` is designed for single-camera, pixel-space pose data (e.g. DeepLabCut). It does not model multi-camera 3D setups well: there is no place to store calibration parameters, the relationship between cameras and their videos is implicit (order-based string paths), and 3D world-space estimates have no structural separation from 2D pixel estimates. This PR adds three new neurodata types to handle multi-camera 3D pose estimation (DANNCE, Anipose, etc.). --- ### New types #### `CameraCalibration` (`NWBDataInterface`) Stores intrinsic and extrinsic calibration parameters for a set of cameras. Each row of every dataset corresponds to one camera, in the same order as the linked `Device` objects. | Field | Required | Shape | Description | |-------|----------|-------|-------------| | `intrinsic_matrix` | yes | `(n_cameras, 3, 3)` | Camera matrix K | | `rotation_matrix` | no | `(n_cameras, 3, 3)` | Rotation from world to camera frame | | `translation_vector` | no | `(n_cameras, 3)` | Translation from world to camera frame | | `distortion_coefficients` | no | `(n_cameras, N)` | Lens distortion coefficients | | `devices` | no | — | Links to `Device` objects, one per camera (must already be in the NWBFile) | #### `CameraView` (`NWBDataInterface`) Represents a single camera's contribution to the pose estimation pipeline. Groups a camera device, its raw video, and optionally its per-camera 2D keypoint estimates in pixel space. | Field | Required | Description | |-------|----------|-------------| | `device` | yes | Link to a `Device` already in the NWBFile | | `source_video` | no | Link to an `ImageSeries` stored in acquisition | | `pose_estimation_series` | no | 2D `PoseEstimationSeries` children (pixel-space estimates for this camera) | #### `MultiCameraPoseEstimation` (`NWBDataInterface`) Top-level container for 3D multi-camera pose estimation. Holds 3D world-space keypoints, one `CameraView` per camera, and optionally a calibration object and skeleton link. | Field | Required | Description | |-------|----------|-------------| | `pose_estimation_series` | no | 3D `PoseEstimationSeries` children in world-space coordinates | | `camera_views` | no | `CameraView` children, one per camera | | `calibration` | no | `CameraCalibration` child | | `skeleton` | no | Link to a `Skeleton` in a `Skeletons` object | | `description` | no | Description of the pose estimation procedure | | `scorer` | no | Name of the scorer / algorithm | | `source_software` | no | Name of the software tool | | `source_software_version` | no | Version string of the software tool | --- ### Design decisions - **`labeled_video` omitted**: labeled videos (pose overlays) are derived visualization artifacts, not raw data. They do not belong in the primary data storage path and are therefore excluded from `CameraView`. - **`CameraView` as a named container per camera**: each camera has its own named group, making the device ↔ video ↔ 2D-estimates relationship explicit and structural rather than relying on implicit ordering of parallel lists. - **Links to `ImageSeries` in acquisition**: `CameraView.source_video` follows the same pattern introduced in PR rly#56 for `PoseEstimation.source_video` — the `ImageSeries` lives in acquisition and is linked from the pose container. - **Calibration row order matches device link order**: `CameraCalibration` datasets are row-indexed to match the order of the linked `Device` list, making the camera-to-parameters mapping unambiguous. --- ### Files changed | File | Change | |------|--------| | `spec/ndx-pose.extensions.yaml` | Added `CameraCalibration`, `CameraView`, `MultiCameraPoseEstimation` type definitions | | `spec/ndx-pose.namespace.yaml` | Version bumped to `0.2.2` | | `src/pynwb/ndx_pose/pose.py` | Implemented all three classes | | `src/pynwb/ndx_pose/io/pose.py` | Added `MultiCameraPoseEstimationMap` for `source_software_version` IO mapping | | `src/pynwb/ndx_pose/__init__.py` | Exported `CameraCalibration`, `CameraView` | | `src/pynwb/ndx_pose/testing/mock/pose.py` | Added `mock_CameraCalibration`, `mock_CameraView`; rewrote `mock_MultiCameraPoseEstimation` | | `src/pynwb/tests/unit/test_pose.py` | Added `TestCameraCalibrationConstructor`, `TestCameraViewConstructor`; rewrote `TestMultiCameraPoseEstimationConstructor` | | `src/pynwb/tests/integration/hdf5/test_pose.py` | Added `TestCameraCalibrationRoundtrip`, `TestCameraViewRoundtrip`; rewrote `TestMultiCameraPoseEstimationRoundtrip` and `TestMultiCameraPoseEstimationRoundtripPyNWB` |
|
Thanks @alessandratrapani . #56 is merged and there are some conflicts |
|
Hey, I finally took a look at this. We should fix the tests and conflicts but right now this is what I think. There are basically two ideas. I think that In the current proposal the |
|
@rly I implemented the suggestion from @h-mayorquin, and it is now ready for review. |
|
The tests are still failing, can you take a look at that before I take a final look? |
MultiCameraPoseEstimation, CameraView, and CameraCalibration neurodata typesMultiCameraPoseEstimation and CalibratedCamera neurodata types
|
I'm reviewing it and fixing up the tests (pynwb 4.0 breaking change) now |
…orts The MultiCameraPoseEstimation roundtrip test and the multi-camera example construct external ImageSeries that pynwb 4.0 requires num_samples for (format='external' with rate-based timing). Set num_samples on those, define n_frames before the example's video loop so it can be referenced, and remove the unused SourceVideos and mock_CalibratedCamera imports that ruff flagged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Bump the extension version to 0.4.0 for the new neurodata types (pyproject.toml, create_extension_spec.py, docs/conf.py, namespace.yaml). - Move the new-types changelog entries out of the released 0.3.0 section into a new 0.4.0 (upcoming) section, and correct the author handle. - Add Alessandra Trapani to the author lists, and add the previously missing Paul Adkisson to the namespace and spec generator author lists. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Overall looks good. I made some minor updates: bumping the version, adding your name as a contributor @alessandratrapani , fixing the changelog, and fixing some pynwb 4.0 compatibility issues. And I made some comments above. The failing spec validation relates to the @alessandratrapani It looks like you modified and reformatted the YAML directly, which is totally fine. Once it's final, I'll port those changes into |
Co-authored-by: Ryan Ly <310197+rly@users.noreply.github.com>
…ni/ndx-pose into multicamera-pose
|
@rly Thanks for the first round of review! |
|
What is the policy for reading files with multiple cameras from previous versions? |
|
Thanks guys, looks good to me. Three things that I spotted: 1. 2. Backwards compatibility. A 0.2.1 file with a Worth noting why CI missed this: 3. Example doesn't run. |
|
Thanks for the thorough review!
Added the same check
I've made
I also added a deprecated
I added |
h-mayorquin
left a comment
There was a problem hiding this comment.
LGTM.
If the decision is not read multi-device files created with previous versions I think we should note that in the changelog but all my feedback has been addressed. I think what remains rests on @rly to decide. Thanks.
Add
CalibratedCameraandMultiCameraPoseEstimationneurodata typesMotivation
PoseEstimationis designed for single-camera, pixel-space pose data (e.g. DeepLabCut).It does not model multi-camera 3D setups well: there is no place to store calibration
parameters, the relationship between cameras and their videos is implicit (order-based
string paths), and 3D world-space estimates have no structural separation from 2D pixel
estimates.
This PR adds two new neurodata types to handle multi-camera 3D pose estimation
(DANNCE, Anipose, etc.), and extends
PoseEstimationto serve as the per-camerabuilding block for multi-camera setups.
Update: an earlier version of this PR introduced two additional types,
CameraViewand
CameraCalibration. Following review feedback, both have been removed:CameraViewduplicatedPoseEstimationalmost entirely (device link, source video,2D
PoseEstimationSeries, scorer, source software). Rather than ship two overlappingper-camera containers that would drift apart over time,
PoseEstimationitself is nowscoped to a single camera view and reused directly inside
MultiCameraPoseEstimation.CameraCalibrationstored calibration as parallel, row-indexed arrays alongside aseparate list of
Devicelinks, which required duplicating the whole rig (and keepingthe row order in sync) for every subject in a multi-subject session (e.g. sDANNCE).
Calibration now lives on the camera itself via the new
CalibratedCameratype, so thecamera + calibration are added once to the NWBFile and shared by reference.
New types
CalibratedCamera(Device)A
Deviceextended with intrinsic and extrinsic calibration parameters for that singlecamera. Because it is a
Device, it is added once to the NWBFile (e.g. ingeneral/devices) and can be linked to by reference from multiplePoseEstimation/MultiCameraPoseEstimationobjects — e.g. one per subject in a multi-subject recordingsession — so the camera rig and its calibration are never duplicated, and there is no
row-order matching to maintain.
intrinsic_matrix(3, 3)rotation_matrix(3, 3)translation_vector(3,)distortion_coefficients(N,)PoseEstimation(NWBDataInterface) — updatedNow explicitly represents pose estimates from a single camera view, computed with
one tool/algorithm. This is the same type used for standalone single-camera files; when
used as a child of
MultiCameraPoseEstimation, onePoseEstimationis added per camera.deviceDevice(ideally aCalibratedCamera) already in the NWBFilesource_video/labeled_videoImageSeriesobjects stored in acquisitionpose_estimation_seriesPoseEstimationSerieschildren (2D pixel-space, or 3D if used standalone)scorer,source_software,source_software_versionskeletonSkeletonin aSkeletonsobjectdevices(list)deviceinstead; passing more than one device now raises an errorMultiCameraPoseEstimation(NWBDataInterface)Top-level container for 3D multi-camera pose estimation. Holds 3D world-space keypoints
and one
PoseEstimationper camera view.pose_estimation_seriesPoseEstimationSerieschildren in world-space coordinatespose_estimationsPoseEstimationchildren, one per camera viewskeletonSkeletonin aSkeletonsobjectdescriptionscorersource_softwaresource_software_versionDesign decisions
PoseEstimationreused instead of a newCameraViewtype: avoids two structurallyidentical containers drifting apart over time. It also means anything that already
reads/writes
PoseEstimation(DeepLabCut/Anipose-style per-camera 2D outputs) composesdirectly into a multi-camera file without a new type to learn.
CalibratedCamera), not in a separate rig object:cameras are
Devices, so they're added once to the NWBFile and referenced by link.This removes the row-order coupling between a calibration object and its device list,
and avoids duplicating the rig per subject in multi-subject sessions.
PoseEstimation.deviceis now singular:PoseEstimationrepresents one cameraview. The previous
devices(list) argument is deprecated (warns once, still readable)and raises if more than one device is passed — multi-camera setups should add one
PoseEstimationper camera to aMultiCameraPoseEstimationinstead.labeled_videoomitted from the per-camera 2D estimates: labeled videos (poseoverlays) are derived visualization artifacts, not raw data, consistent with the
original design decision.
ImageSeriesin acquisition: unchanged —PoseEstimation.source_videofollows the pattern introduced in PR Add
source_videolink field to PoseEstimation #56, with theImageSeriesliving in acquisitionand linked from the pose container.
Files changed
spec/ndx-pose.extensions.yamlCalibratedCamera; updatedPoseEstimation(singulardevicelink, deprecated multi-video fields); updatedMultiCameraPoseEstimationto containPoseEstimationchildren instead ofCameraView/CameraCalibrationsrc/pynwb/ndx_pose/pose.pyCalibratedCamera; updatedPoseEstimationdevice deprecation logic; rewroteMultiCameraPoseEstimationsrc/pynwb/ndx_pose/io/pose.pyMultiCameraPoseEstimationMapforsource_software_versionIO mappingsrc/pynwb/ndx_pose/__init__.pyCalibratedCamerainstead ofCameraCalibration/CameraViewsrc/pynwb/ndx_pose/testing/mock/pose.pymock_CalibratedCamera; updatedmock_PoseEstimation(singulardevice,add_to_nwbfileflag); rewrotemock_MultiCameraPoseEstimationto build per-cameraPoseEstimationchildrenexamples/write_multicamera_pose_estimates.pysrc/pynwb/tests/unit/test_pose.pyTestCalibratedCameraConstructor; addedPoseEstimationdeprecation tests; rewroteTestMultiCameraPoseEstimationConstructorsrc/pynwb/tests/integration/hdf5/test_pose.pyTestCalibratedCameraRoundtrip; rewroteTestMultiCameraPoseEstimationRoundtripREADME.md,CHANGELOG.mdPrevious proposal
### New typesCameraCalibration(NWBDataInterface)Stores intrinsic and extrinsic calibration parameters for a set of cameras. Each row of
every dataset corresponds to one camera, in the same order as the linked
Deviceobjects.intrinsic_matrix(n_cameras, 3, 3)rotation_matrix(n_cameras, 3, 3)translation_vector(n_cameras, 3)distortion_coefficients(n_cameras, N)devicesDeviceobjects, one per camera (must already be in the NWBFile)CameraView(NWBDataInterface)Represents a single camera's contribution to the pose estimation pipeline. Groups a
camera device, its raw video, and optionally its per-camera 2D keypoint estimates in
pixel space.
deviceDevicealready in the NWBFilesource_videoImageSeriesstored in acquisitionpose_estimation_seriesPoseEstimationSerieschildren (pixel-space estimates for this camera)MultiCameraPoseEstimation(NWBDataInterface)Top-level container for 3D multi-camera pose estimation. Holds 3D world-space keypoints,
one
CameraViewper camera, and optionally a calibration object and skeleton link.pose_estimation_seriesPoseEstimationSerieschildren in world-space coordinatescamera_viewsCameraViewchildren, one per cameracalibrationCameraCalibrationchildskeletonSkeletonin aSkeletonsobjectdescriptionscorersource_softwaresource_software_versionDesign decisions
labeled_videoomitted: labeled videos (pose overlays) are derived visualizationartifacts, not raw data. They do not belong in the primary data storage path and are
therefore excluded from
CameraView.CameraViewas a named container per camera: each camera has its own named group,making the device ↔ video ↔ 2D-estimates relationship explicit and structural rather
than relying on implicit ordering of parallel lists.
ImageSeriesin acquisition:CameraView.source_videofollows the samepattern introduced in PR Add
source_videolink field to PoseEstimation #56 forPoseEstimation.source_video— theImageSerieslives in acquisition and is linked from the pose container.
CameraCalibrationdatasets arerow-indexed to match the order of the linked
Devicelist, making thecamera-to-parameters mapping unambiguous.
Files changed
spec/ndx-pose.extensions.yamlCameraCalibration,CameraView,MultiCameraPoseEstimationtype definitionsspec/ndx-pose.namespace.yaml0.2.2src/pynwb/ndx_pose/pose.pysrc/pynwb/ndx_pose/io/pose.pyMultiCameraPoseEstimationMapforsource_software_versionIO mappingsrc/pynwb/ndx_pose/__init__.pyCameraCalibration,CameraViewsrc/pynwb/ndx_pose/testing/mock/pose.pymock_CameraCalibration,mock_CameraView; rewrotemock_MultiCameraPoseEstimationsrc/pynwb/tests/unit/test_pose.pyTestCameraCalibrationConstructor,TestCameraViewConstructor; rewroteTestMultiCameraPoseEstimationConstructorsrc/pynwb/tests/integration/hdf5/test_pose.pyTestCameraCalibrationRoundtrip,TestCameraViewRoundtrip; rewroteTestMultiCameraPoseEstimationRoundtripandTestMultiCameraPoseEstimationRoundtripPyNWB