Skip to content

Add I/O support for the ndx-pose NWB extension: take 2 #360

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 112 commits into from
May 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
112 commits
Select commit Hold shift + click to select a range
4b59ff0
Create nwb_export.py
edeno Apr 18, 2024
ab60c04
NWB requires one file per individual
edeno Apr 18, 2024
f8a375a
Add script
edeno Apr 19, 2024
1500081
Remove import error handling
edeno Apr 19, 2024
8d1d5f3
Add nwb optional dependencies
edeno Apr 19, 2024
c107737
Fix linting based on pre-commit hooks
edeno Apr 19, 2024
c0e156c
Add example docstring
edeno Apr 19, 2024
32adc93
Rename to fit module naming pattern
edeno Apr 19, 2024
814e867
Add import from nwb
edeno Apr 19, 2024
10e95f4
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 22, 2024
0db918e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 8, 2024
71a09eb
Apply suggestions from code review
edeno Jun 8, 2024
85244bd
Update make pynwb and ndx-pose core dependencies
edeno Jun 8, 2024
48e8bca
Cleanup of docstrings and variable names from code review
edeno Jun 8, 2024
a6a8811
Rename function for clarity
edeno Jun 8, 2024
5a91a34
Update with example converting back to movement
edeno Jun 8, 2024
c328bc6
Add file validation and handling for single path
edeno Jun 8, 2024
6a4f963
Add preliminary tests
edeno Jun 8, 2024
953cc8d
Convert to numpy array
edeno Jun 9, 2024
3d3d038
Handle lack of confidence
edeno Jun 9, 2024
ae439a4
Display xarray
edeno Jun 9, 2024
0cca671
Refactor tests
edeno Jun 9, 2024
684f33a
Create nwb_export.py
edeno Apr 18, 2024
ac4f06c
NWB requires one file per individual
edeno Apr 18, 2024
465bd70
Remove import error handling
edeno Apr 19, 2024
6291d14
Add nwb optional dependencies
edeno Apr 19, 2024
9f807b9
Fix linting based on pre-commit hooks
edeno Apr 19, 2024
486924f
Rename to fit module naming pattern
edeno Apr 19, 2024
a91c2d4
Add import from nwb
edeno Apr 19, 2024
09cd0e4
Update make pynwb and ndx-pose core dependencies
edeno Jun 8, 2024
1359e26
Add file validation and handling for single path
edeno Jun 8, 2024
94a5f73
Convert to numpy array
edeno Jun 9, 2024
ea514fd
fix logging module import
niksirbi Nov 29, 2024
5b55970
constrained pynwb>=0.2.1
niksirbi Nov 29, 2024
9f679ce
fixed existing unit tests
niksirbi Nov 29, 2024
bf68547
add key_name argument to convert_nwb_to_movement
niksirbi Nov 29, 2024
5760296
tests should only create temp file
niksirbi Dec 11, 2024
9947e9f
use Generator instead of legacy np.random.random
niksirbi Dec 11, 2024
362a6a9
reorder dims and use from_numpy for creating movement ds
niksirbi Dec 12, 2024
4ee68cf
define default nwb kwargs as constants
niksirbi Dec 12, 2024
081ba18
renamed and reformatted `add_movement_dataset_to_nwb` to `ds_to_nwb`
niksirbi Dec 12, 2024
dcae070
Expanded module-level docstring
niksirbi Dec 12, 2024
9dd13b7
use individual instead of subject
niksirbi Dec 12, 2024
f3f261a
refactored functions for loading ds from nwb
niksirbi Dec 12, 2024
9728d8f
make mypy happy with numpy typing
niksirbi Dec 18, 2024
059944e
rename nwb example
niksirbi Dec 18, 2024
fd749ae
renamed private func for creating pose estimation and skeletons objects
niksirbi Dec 18, 2024
457f7de
incorporate NWB loading into load_poses module
niksirbi Dec 18, 2024
2d98bd8
incorporate NWB saving function into save_poses module
niksirbi Dec 18, 2024
d094f6f
simplified private nwb functions
niksirbi Dec 18, 2024
979027c
provide examples in docstrings instead of sphinx gallery example
niksirbi Dec 18, 2024
31d79ea
fix docstring syntax error
niksirbi Dec 18, 2024
e83c859
use pose estimation series rate if possible
niksirbi Mar 31, 2025
441be86
use dot notation to access pose estimation attributes
niksirbi Mar 31, 2025
ad86058
remove underscore from _nwb.py file name
niksirbi Mar 31, 2025
fcf2a68
move imports at the top of docstring example
niksirbi Mar 31, 2025
f1eba42
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 31, 2025
153d996
Fix deprecated logger calls
lochhh Apr 23, 2025
75b6d62
Fix line too long
lochhh Apr 23, 2025
aad6641
Add load_nwb test case
lochhh Apr 23, 2025
4ac8030
Move file fixtures to tests/fixtures/files.py
lochhh Apr 25, 2025
440fbcb
Add NWB file validator
lochhh Apr 25, 2025
85243f4
Extend from_nwb_file tests
lochhh Apr 25, 2025
e843944
Add to_nwb_file tests
lochhh Apr 25, 2025
e04cf79
Rename NWBFile fixture
lochhh May 1, 2025
d6dec2a
Remove repeated default nwb kwargs
lochhh May 1, 2025
e90873a
Simplify `_ds_to_pose_and_skeleton_objects` test
lochhh May 2, 2025
9097679
Add pynwb to intersphinx mapping
lochhh May 7, 2025
fd1a121
Draft to_nwb_file refactor
lochhh May 8, 2025
3fdaaac
Allow single key dict for single-ind datasets
lochhh May 9, 2025
ab72903
Create Subject in NWBFile
lochhh May 9, 2025
5cdc4a4
Refactor resolve_kwargs
lochhh May 9, 2025
bdc47f5
Reduce resolve_kwargs complexity
lochhh May 9, 2025
d397678
Include nwb module in API docs
lochhh May 12, 2025
eccb6fa
Resolve pose_estimation_series_kwargs for single keypoint
lochhh May 12, 2025
72b7f2b
Refactor _ds_to_pose_and_skeleton_objects pt1
lochhh May 12, 2025
8af4f76
Allow deprioritising input ids
lochhh May 13, 2025
c0161ad
Add test for _ds_to_pose_and_skeletons
lochhh May 13, 2025
8bef5ef
Update docstrings
lochhh May 13, 2025
0ac6caa
Refactor NWBFileSaveConfig tests
lochhh May 13, 2025
78cb0e1
Allow 0-d individuals
lochhh May 13, 2025
8ef4b93
Link Subject in Skeleton
lochhh May 13, 2025
ab253e0
Update docstrings + rename resolve methods
lochhh May 13, 2025
1cbec09
Resolve pose_estimation_kwargs
lochhh May 14, 2025
c0a90ce
Resolve skeleton_kwargs
lochhh May 15, 2025
7853af5
Remove unused functions
lochhh May 15, 2025
a425b5a
Refactor _write_behavior_processing_module
lochhh May 15, 2025
20c8f8e
Convert frames to seconds when saving
lochhh May 16, 2025
186f17b
Use numpy.random.Generator
lochhh May 16, 2025
bc7280b
Refactor to_nwb_file tests
lochhh May 16, 2025
12636a3
Remove unused constant
lochhh May 16, 2025
2cee1d9
Add to_nwb_file example in docstrings
lochhh May 16, 2025
aa40bb1
Add load NWB in IO guide
lochhh May 19, 2025
fe83497
Add save NWB in IO guide
lochhh May 19, 2025
a53e888
Use rng fixture in NWBFile fixtures
lochhh May 20, 2025
9bd137d
Apply docstring suggestions from code review
lochhh May 27, 2025
463100f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 27, 2025
ecab492
Fix reference to NWBFileSaveConfig in docstrings
lochhh May 27, 2025
a42c05f
Mention NWBFile config option in I/O guide
lochhh May 27, 2025
aeb0324
Clarify from_file fps arg
lochhh May 27, 2025
2a575aa
Replace `identifier` with `experimenter` in to_nwb_file config example
lochhh May 27, 2025
7a243ed
Only set `source_file` attribute when loading from NWB file path
lochhh May 27, 2025
510c50e
Log warning for missing session_start_time in NWBFileSaveConfig
lochhh May 27, 2025
9498839
Rename `is_multi_individual` to `from_multi_individual`
lochhh May 27, 2025
32611a6
Remove "open" when describing NWBFile objects
lochhh May 27, 2025
3518362
Use full NWBFile module refs in docstrings
lochhh May 27, 2025
fdc6501
Return a single NWBFile when saving single-individual datasets
lochhh May 27, 2025
c4a8fbe
Remove `identifier` from nwbfile_kwargs defaults
lochhh May 27, 2025
8e2d766
Gather NWB fixtures
lochhh May 28, 2025
9c64a4e
Allow specifying key for processing module
lochhh May 29, 2025
36fcf52
Allow customising processing module via kwargs
lochhh May 29, 2025
1bf604c
Update nwb test config and comments for clarity
lochhh May 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@
("css/custom.css", {"priority": 100}),
]
html_js_files = [
"js/contributors.js", # javascript for contributors table
"js/contributors.js", # javascript for contributors table
]
html_favicon = "_static/light-logo-niu.png"

Expand Down Expand Up @@ -225,6 +225,7 @@
"pandas": ("https://pandas.pydata.org/pandas-docs/stable/", None),
"python": ("https://docs.python.org/3", None),
"loguru": ("https://loguru.readthedocs.io/en/stable/", None),
"pynwb": ("https://pynwb.readthedocs.io/en/stable/", None),
}


Expand Down
134 changes: 85 additions & 49 deletions docs/source/user_guide/input_output.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ To analyse pose tracks, `movement` supports loading data from various frameworks
- [LightingPose](lp:) (LP)
- [Anipose](anipose:) (Anipose)

Additionally, `movement` supports loading data stored in [Neurodata Without Borders (NWB)](https://nwb-overview.readthedocs.io/en/latest/) format (using the [``ndx-pose``](https://github.com/rly/ndx-pose) extension).

To analyse bounding box tracks, `movement` currently supports the [VGG Image Annotator](via:) (VIA) format for [tracks annotation](via:docs/face_track_annotation.html).

:::{note}
Expand All @@ -36,44 +38,39 @@ To read a pose tracks file into a [movement poses dataset](target-poses-and-bbox

::::{tab-set}

:::{tab-item} SLEAP

To load [SLEAP analysis files](sleap:tutorials/analysis) in .h5 format (recommended):
:::{tab-item} DeepLabCut
To load DeepLabCut files in .h5 format:
```python
ds = load_poses.from_sleap_file("/path/to/file.analysis.h5", fps=30)
ds = load_poses.from_dlc_file("/path/to/file.h5", fps=30)

# or equivalently
ds = load_poses.from_file(
"/path/to/file.analysis.h5", source_software="SLEAP", fps=30
"/path/to/file.h5", source_software="DeepLabCut", fps=30
)
```
To load [SLEAP analysis files](sleap:tutorials/analysis) in .slp format (experimental, see notes in {func}`movement.io.load_poses.from_sleap_file`):

To load DeepLabCut files in .csv format:
```python
ds = load_poses.from_sleap_file("/path/to/file.predictions.slp", fps=30)
ds = load_poses.from_dlc_file("/path/to/file.csv", fps=30)
```
:::

:::{tab-item} DeepLabCut

To load DeepLabCut files in .h5 format:
:::{tab-item} SLEAP
To load [SLEAP analysis files](sleap:tutorials/analysis) in .h5 format (recommended):
```python
ds = load_poses.from_dlc_file("/path/to/file.h5", fps=30)
ds = load_poses.from_sleap_file("/path/to/file.analysis.h5", fps=30)

# or equivalently
ds = load_poses.from_file(
"/path/to/file.h5", source_software="DeepLabCut", fps=30
"/path/to/file.analysis.h5", source_software="SLEAP", fps=30
)
```

To load DeepLabCut files in .csv format:
To load [SLEAP analysis files](sleap:tutorials/analysis) in .slp format (experimental, see notes in {func}`movement.io.load_poses.from_sleap_file`):
```python
ds = load_poses.from_dlc_file("/path/to/file.csv", fps=30)
ds = load_poses.from_sleap_file("/path/to/file.predictions.slp", fps=30)
```
:::

:::{tab-item} LightningPose

To load LightningPose files in .csv format:
```python
ds = load_poses.from_lp_file("/path/to/file.analysis.csv", fps=30)
Expand All @@ -86,26 +83,53 @@ ds = load_poses.from_file(
:::

:::{tab-item} Anipose

To load Anipose files in .csv format:
```python
ds = load_poses.from_anipose_file(
"/path/to/file.analysis.csv", fps=30, individual_name="individual_0"
) # We can optionally specify the individual name, by default it is "individual_0"
) # Optionally specify the individual name; defaults to "individual_0"

# or equivalently
ds = load_poses.from_file(
"/path/to/file.analysis.csv", source_software="Anipose", fps=30, individual_name="individual_0"
"/path/to/file.analysis.csv",
source_software="Anipose",
fps=30,
individual_name="individual_0",
)
```
:::

:::{tab-item} NWB
To load NWB files in .nwb format:
```python
ds = load_poses.from_nwb_file(
"path/to/file.nwb",
processing_module_key="behavior",
pose_estimation_key="PoseEstimation",
) # Optionally specify the name of the ProcessingModule and PoseEstimation objects.
# Defaults are "behavior" and "PoseEstimation", respectively.

# or equivalently
ds = load_poses.from_file(
"path/to/file.nwb",
source_software="NWB",
processing_module_key="behavior",
pose_estimation_key="PoseEstimation",
)
```
The above functions also accept an {class}`NWBFile<pynwb.file.NWBFile>` object as input:
```python
with pynwb.NWBHDF5IO("path/to/file.nwb", mode="r") as io:
nwb_file = io.read()
ds = load_poses.from_nwb_file(
nwb_file, pose_estimation_key="PoseEstimation"
)
```
:::

:::{tab-item} From NumPy

In the example below, we create random position data for two individuals, ``Alice`` and ``Bob``,
with three keypoints each: ``snout``, ``centre``, and ``tail_base``. These keypoints are tracked in 2D space for 100 frames, at 30 fps. The confidence scores are set to 1 for all points.

```python
import numpy as np

Expand Down Expand Up @@ -142,7 +166,6 @@ We currently support loading bounding box tracks in the VGG Image Annotator (VIA

::::{tab-set}
:::{tab-item} VGG Image Annotator

To load a VIA tracks .csv file:
```python
ds = load_bboxes.from_via_tracks_file("path/to/file.csv", fps=30)
Expand All @@ -154,17 +177,12 @@ ds = load_bboxes.from_file(
fps=30,
)
```

Note that the x,y coordinates in the input VIA tracks .csv file represent the the top-left corner of each bounding box. Instead the corresponding ``movement`` dataset `ds` will hold in its `position` array the centroid of each bounding box.


:::

:::{tab-item} From NumPy

In the example below, we create random position data for two bounding boxes, ``id_0`` and ``id_1``,
both with the same width (40 pixels) and height (30 pixels). These are tracked in 2D space for 100 frames, which will be numbered in the resulting dataset from 0 to 99. The confidence score for all bounding boxes is set to 0.5.

```python
import numpy as np

Expand All @@ -182,16 +200,16 @@ ds = load_bboxes.from_numpy(

The resulting data structure `ds` will include the centroid trajectories for each tracked bounding box, the boxes' widths and heights, and their associated confidence values if provided.



For more information on the bounding boxes data structure, see the [movement dataset](target-poses-and-bboxes-dataset) page.


(target-saving-pose-tracks)=
## Saving pose tracks
[movement poses datasets](target-poses-and-bboxes-dataset) can be saved in a variety of
formats, including DeepLabCut-style files (.h5 or .csv) and
[SLEAP-style analysis files](sleap:tutorials/analysis) (.h5).
formats:
- DeepLabCut-style files (.h5 or .csv)
- [SLEAP-style analysis files](sleap:tutorials/analysis) (.h5)
- NWB files (.nwb)

To export pose tracks from `movement`, first import the {mod}`movement.io.save_poses` module:

Expand All @@ -203,13 +221,22 @@ Then, depending on the desired format, use one of the following functions:

:::::{tab-set}

::::{tab-item} SLEAP
::::{tab-item} DeepLabCut
To save as a DeepLabCut file, in .h5 or .csv format:
```python
save_poses.to_dlc_file(ds, "/path/to/file.h5") # preferred format
save_poses.to_dlc_file(ds, "/path/to/file.csv")
```
The {func}`movement.io.save_poses.to_dlc_file` function also accepts
a `split_individuals` boolean argument. If set to `True`, the function will
save the data as separate single-animal DeepLabCut-style files.
::::

::::{tab-item} SLEAP
To save as a SLEAP analysis file in .h5 format:
```python
save_poses.to_sleap_analysis_file(ds, "/path/to/file.h5")
```

:::{note}
When saving to SLEAP-style files, only `track_names`, `node_names`, `tracks`, `track_occupancy`,
and `point_scores` are saved. `labels_path` will only be saved if the source
Expand All @@ -222,22 +249,7 @@ each attribute and data variable represents, see the
:::
::::

::::{tab-item} DeepLabCut

To save as a DeepLabCut file, in .h5 or .csv format:
```python
save_poses.to_dlc_file(ds, "/path/to/file.h5") # preferred format
save_poses.to_dlc_file(ds, "/path/to/file.csv")
```

The {func}`movement.io.save_poses.to_dlc_file` function also accepts
a `split_individuals` boolean argument. If set to `True`, the function will
save the data as separate single-animal DeepLabCut-style files.

::::

::::{tab-item} LightningPose

To save as a LightningPose file in .csv format:
```python
save_poses.to_lp_file(ds, "/path/to/file.csv")
Expand All @@ -249,8 +261,32 @@ DeepLabCut .csv format, the above command is equivalent to:
save_poses.to_dlc_file(ds, "/path/to/file.csv", split_individuals=True)
```
:::
::::

::::{tab-item} NWB
To convert a `movement` poses dataset to {class}`NWBFile<pynwb.file.NWBFile>` objects:
```python
nwb_files = save_poses.to_nwb_file(ds)
```
The {func}`movement.io.save_poses.to_nwb_file` function also accepts
a {class}`movement.io.nwb.NWBFileSaveConfig` object as its ``config`` argument
for customising metadata such as session or subject information in the resulting `NWBFile`s
(see {func}`the API reference<movement.io.save_poses.to_nwb_file>` for examples).

These `NWBFile`s can then be saved to disk as .nwb files using {class}`pynwb.NWBHDF5IO`:
```python
from pynwb import NWBHDF5IO

for file in nwb_files:
with NWBHDF5IO(f"{file.identifier}.nwb", "w") as io:
io.write(file)
```
:::{note}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps in this note (or above it within the tab) we should mention the config that can now be passed to the save_poses.to_nwb_file() function. No need to go into details (the docstring does that sufficiently), it's enough to mention that such an option exists.

Copy link
Collaborator

@lochhh lochhh May 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a line describing this

To allow adding additional data to NWB files before saving, {func}`to_nwb_file<movement.io.save_poses.to_nwb_file>` does not write to disk directly.
Instead, it returns a list of {class}`NWBFile<pynwb.file.NWBFile>` objects---one per individual in the dataset---since NWB files are designed to represent data from a single individual.
:::
::::

:::::


Expand Down
Loading
Loading