Releases: neuroinformatics-unit/movement
v0.16.0
To update movement to the latest version, see the update guide.
What's Changed
⚡️ Highlight 1: draw, save, and load Regions of Interest in the GUI
- Widget for drawing Regions of Interest (ROIs) as napari Shapes by @niksirbi in #617
- Add conversion between
napariShapes andmovementRoIs by @niksirbi in #927
You can now define Regions of Interest (RoIs) interactively in the movement napari GUI, export them to a GeoJSON file, and load them back into Python for analysis — all without writing any coordinates by hand.
Demo_EPM-rois_2026-04-01_3x.mp4
The Define regions of interest menu lets you draw shapes directly on the video frames, name them, and save them via the Save layer button. The saved .geojson file can then be loaded with movement.roi.load_rois():
from movement.roi import load_rois
rois = load_rois("my_regions.geojson")
[roi.name for roi in rois] # ['arena', 'nest', 'corridor']
# use in analysis — e.g. check if the animal is inside a region
is_in_nest = rois[1].contains_point(ds.position)You can also load a .geojson file back into napari via the Load layer button to review or edit your regions.
See the Define regions of interest section of the GUI guide and the updated boundary_angles example for a full walkthrough.
⚡️ Highlight 2: automatic detection of source software in load_dataset
- Add source_software auto-detection for dataset loading by @M0hammed-Reda in #920
Thanks to a first contribution from @M0hammed-Reda, load_dataset() can now infer source_software automatically from the file format. When you know which software produced your file, we still recommend passing it explicitly, but automatic inference can be a convenient fallback.
from movement.io import load_dataset
# recommended: explicit is clearer and faster
ds = load_dataset("path/to/file.h5", source_software="DeepLabCut", fps=30)
# convenient fallback: automatic inference from file format
ds = load_dataset("path/to/file.h5", fps=30)You can also call infer_source_software() directly to check what movement would infer for a given file.
🚀 Performance improvements
@sfmig has substantially sped up loading of VIA-tracks bounding box files. Loading a 34 MB file now takes ~0.5 s (down from ~30 s), and a 100 MB file takes ~1.7 s (down from ~1-2 min). Memory use at peak is now comparable to the final in-memory size of the dataset.
⚠️ Breaking changes
- Make
transforms.scalerequire an explicitfactorby @Tushar7012 in #839
transforms.scale() no longer has a default value for the factor parameter. Previously, omitting factor silently multiplied the data by 1.0 (equivalent to no scaling). If you were omitting factor, add it explicitly:
# Before (silently did nothing)
ds_scaled = scale(ds)
# After
ds_scaled = scale(ds, factor=0.01) # e.g. convert pixels to centimetres🛠️ Refactoring
- Convert CLI from argparse to typer by @roaldarbol in #962
@roaldarbol has been a long-time collaborator and a constant source of ideas for movement — and this is his first, but not last, PR! He migrated the CLI from argparse to Typer, giving the movement command automatic shell completion support and a cleaner foundation for future CLI additions.
📚 Documentation
- Update and add missing docstrings by @lochhh in #885
- Added link to TheBehaviourForum virtual workshop talk by @niksirbi in #951
- Add acknowledgements to examples by @niksirbi in #952
- Replace fixed contributor table with responsive grid by @lochhh in #954
- Update pandas URL in intersphinx config by @lochhh in #895
🧹 Housekeeping and dependencies
- Updated supported Python versions to 3.12 - 3.14 by @niksirbi in #810
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #950
New Contributors
- @M0hammed-Reda made their first contribution in #920
- @roaldarbol made their first contribution in #962
Full Changelog: v0.15.0...v0.16.0
v0.15.0
To update movement to the latest version, see the update guide.
What's Changed
⚡️ Highlight 1: new example for scaling pose tracks to real-world coordinates
- Add example for scaling pose tracks to real-world coordinates by @HollyMorley in #827
This new gallery example by @HollyMorley walks through a complete workflow for converting pixel-based pose data to real-world units using a a known reference distance. It demonstrates pre-processing (confidence filtering, interpolation, smoothing), measuring a reference distance in napari, applying movement.transforms.scale(), and computing real-world body measurements and inter-limb distances.
Holly is working on a few more examples for our gallery, so keep an eye out for those in the coming releases!
⚡️ Highlight 2: Save and load RoIs via GeoJSON
Regions of interest (RoIs) can now be saved to and loaded from GeoJSON files using movement.roi.save_rois() and movement.roi.load_rois(). Each RoI is stored as a GeoJSON Feature containing its geometry and properties (name and RoI type), within a FeatureCollection. This makes it easy to persist ROI definitions across sessions and share them with collaborators.
from movement.roi import LineOfInterest, PolygonOfInterest, save_rois, load_rois
# Create 2 RoIs: a polygon and a line
square = PolygonOfInterest([(0, 0), (1, 0), (1, 1), (0, 1)], name="square")
diagonal = LineOfInterest([(0, 0), (1, 1)], name="diagonal")
# Save them to a GeoJSON file
save_rois([square, diagonal], "rois.geojson")
# Load RoIs from a GeoJSON file
rois = load_rois("rois.geojson")
[roi.name for roi in rois] # returns ["square", "diagonal"]⚡️ Highlight 3: derive bounding boxes from pose tracks
Thanks to @Edu92337, you can now compute bounding boxes from sets of keypoints (poses). The new movement.transforms.poses_to_bboxes() function finds the minimum and maximum coordinates across all keypoints for each individual at each time point. The resulting bounding boxes are represented by their centroid (center point position) and shape (width and height).
Suppose poses_ds is a movement poses dataset with a "position" variable containing keypoint coordinates.
from movement.transforms import poses_to_bboxes
# Compute bounding boxes with zero padding
bbox_centroid, bbox_shape = poses_to_bboxes(poses_ds["position"])
# Compute bounding boxes with 10 pixels of padding
bbox_centroid, bbox_shape = poses_to_bboxes(poses_ds["position"], padding=10)🐛 Bug fixes
- Fix MultiIndex error in compute_forward_displacement (#794) by @Tushar7012 in #799
📚 Documentation
- Updated conda installation instructions with pyqt6 by @niksirbi in #825
- Add "See Also" to
load_multiview_datasetdocstring by @niksirbi in #824 - Remove docstring types and use annotations for docs by @ParthChatupale in #727
- Draft roadmap for 2026 by @niksirbi in #809
- fix: correct loader param name, path assignment, and spelling in docs by @dhruv1955 in #857
🧹 Housekeeping
- Remove OSSS announcement banner by @adamltyson in #828
- Make hide_pooch_hash_logs internal by renaming to _hide_pooch_hash_logs by @Shreecharana24 in #842
- Use idiomatic xarray coordinate comparison in
test_load_posesby @Kayd-06 in #876 - [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #861
- Contributors-Readme-Action: Update contributors list by @github-actions[bot] in #856
- Bump actions/download-artifact from 7 to 8 by @dependabot[bot] in #859
- Contributors-Readme-Action: Update contributors list by @github-actions[bot] in #880
New Contributors
- @HollyMorley made their first contribution in #827
- @Shreecharana24 made their first contribution in #842
- @ParthChatupale made their first contribution in #727
- @dhruv1955 made their first contribution in #857
- @Kayd-06 made their first contribution in #876
Full Changelog: v0.14.0...v0.15.0
v0.14.0
To update movement to the latest version, see the update guide.
What's Changed
⚡️ Highlight: unified data loading interface
Thanks to @lochhh's tireless efforts, loading poses and bounding boxes is now handled by a single entry point, movement.io.load_dataset.
The new load_dataset function works for all our supported third-party formats. For example:
from movement.io import load_dataset
# DeepLabCut -> poses dataset
ds = load_dataset("path/to/file.h5", source_software="DeepLabCut", fps=30)
# SLEAP -> poses dataset
ds = load_dataset("path/to/file.slp", source_software="SLEAP")
# NWB -> poses dataset
ds = load_dataset("path/to/file.nwb", source_software="NWB")
# VGG Image Annotator tracks -> bounding boxes dataset
ds = load_dataset("path/to/file.csv", source_software="VIA-tracks")Similarly, movement.io.load_multiview_dataset replaces the old movement.io.load_poses.from_multiview_files, with added support for bounding boxes:
from movement.io import load_multiview_dataset
# Load LightningPose pose predictions from two cameras.
ds = load_multiview_dataset(
{"cam1": "path/to/cam1.csv", "cam2": "path/to/cam2.csv"},
source_software="LightningPose",
fps=30,
)Software-specific loaders (e.g. load_poses.from_dlc_file, load_bboxes.from_via_tracks_file) remain available for users who want full control over the loading process.
Warning
The following functions are deprecated and will be removed in a future release:
load_poses.from_file→ useload_datasetinsteadload_bboxes.from_file→ useload_datasetinsteadload_poses.from_multiview_files→ useload_multiview_datasetinstead
Migrating from deprecated functions:
# Before
from movement.io import load_poses, load_bboxes
ds = load_poses.from_file("file.h5", source_software="DeepLabCut", fps=30)
ds = load_bboxes.from_file("file.csv", source_software="VIA-tracks", fps=30)
ds = load_poses.from_multiview_files(
{"cam1": "cam1.csv", "cam2": "cam2.csv"},
source_software="LightningPose", fps=30,
)
# After
from movement.io import load_dataset, load_multiview_dataset
ds = load_dataset("file.h5", source_software="DeepLabCut", fps=30)
ds = load_dataset("file.csv", source_software="VIA-tracks", fps=30)
ds = load_multiview_dataset(
{"cam1": "cam1.csv", "cam2": "cam2.csv"},
source_software="LightningPose", fps=30,
)🐛 Bug fixes
- Adapt
savgol_filterfor compatibility with Scipy >= 1.17 by @niksirbi in #761 - Fix coordinate assignment for
elem2in_cdistby @HARSHDIPSAHA in #776 - Hide _factorized properties from Points layer tooltips by @niksirbi in #781
🤝 Improving the contributor experience
- Docs: restructure contributing guide and add community cards by @ishan372or in #764
- Add preliminary benchmarks by @sfmig in #772
- Use
tox-uvin test action by @niksirbi in #766 - Move docs dependencies to pyproject.toml by @AlgoFoe in #774
📚 Documentation
- Fix docstring Raises formatting in LineOfInterest.normal by @AlgoFoe in #771
- Added link to FOSDEM 2026 talk by @niksirbi in #797
- Fix broken 404 page by @AlgoFoe in #785
- Fix links to SLEAP analysis h5 docs by @niksirbi in #807
- Add code snippets for verifying sample data loading by @Edu92337 in #759
🧹 Housekeeping
- Restrict sphinx version to < 9 by @niksirbi in #768
- Unpin sphinx and pin ablog>=0.11.13 by @niksirbi in #811
- Fix deprecated license syntax by @sfmig in #549
- Ignore Docutils URL in linkcheck by @lochhh in #793
- Ignore ISO URL in linkcheck by @lochhh in #815
- Authenticate with GitHub token during linkcheck by @niksirbi in #800
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #795
- Contributors-Readme-Action: Update contributors list by @github-actions[bot] in #787
- Bump conda-incubator/setup-miniconda from 3.2.0 to 3.3.0 by @dependabot[bot] in #788
New Contributors
- @AlgoFoe made their first contribution in #771
- @HARSHDIPSAHA made their first contribution in #776
- @ishan372or made their first contribution in #764
- @Edu92337 made their first contribution in #759
Full Changelog: v0.13.0...v0.14.0
v0.13.0
What's Changed
⚡️ New feature: loading netCDF files in the GUI
Thanks to @Sparshr04, netCDF files saved with movement can now be loaded into our GUI. See the relevant documentation for more information.
- Enable loading netCDF files in the napari widget by @Sparshr04 in #689
🛠️ Refactoring
The changes in this section are primarily relevant to movement developers and contributors.
@lochhh has carried out a substantial refactor of our dataset validators. These have been renamed (ValidPosesDataset -> ValidPosesInputs, ValidBboxesDataset -> ValidBboxesInputs) and significant code duplication has been removed. This work is part of a broader effort to simplify and unify the interface for loading motion-tracking data into movement, and will make it easier to add support for additional formats going forward.
She has also simplified and cleaned up the parsing and validation logic for DeepLabCut CSV files.
📚 Documentation
The Community section of the website has been expanded and refined, including a new page collecting shared resources. Feel free to use and share these when communicating about movement, whether to promote it, teach it, or acknowledge it in your own work.
Moreover, the ability to execute our examples via Binder had been broken for some time. This functionality should now be restored with this release.
- Update binder config for multi-version docs by @niksirbi in #709
- Revamp Binder configuration to work with multi-version docs by @niksirbi in #718
- Update the Community landing page by @niksirbi in #717
- Add community resources page by @niksirbi in #724
- Fix
save_bboxesimport typos in docstring examples by @Tushar7012 in #736
🧹 Housekeeping
This release also includes a number of maintenance and infrastructure updates aimed at improving dependency management, CI robustness, and the overall development workflow.
- Contributors-Readme-Action: Update contributors list by @github-actions[bot] in #711
- Bump actions/checkout from 5 to 6 by @dependabot[bot] in #712
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #713
- Remove Numpy upper pin by @niksirbi in #714
- Bump actions/cache from 4 to 5 by @dependabot[bot] in #738
- Bump actions/download-artifact from 6 to 7 by @dependabot[bot] in #739
- Contributors-Readme-Action: Update contributors list by @github-actions[bot] in #737
- Remove duplicated check-manifest step by @lochhh in #744
- Fix xarray FutureWarnings for merge() Parameters by @vedantvakharia in #740
- Update mypy configuration and fix errors by @lochhh in #721
- Make sphinx linkcheck more robust by @niksirbi in #746
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #742
- Link to
stableversion ofnaparidocs, instead ofdevby @bhanushaliparthhitesh in #756 - Upper pin SciPy < 1.17.0 by @niksirbi in #754
- Change default value assignment logs from WARNING to INFO by @Tushar7012 in #755
New Contributors
- @Sparshr04 made their first contribution in #689
- @Tushar7012 made their first contribution in #736
- @vedantvakharia made their first contribution in #740
- @bhanushaliparthhitesh made their first contribution in #756
Full Changelog: v0.12.0...v0.13.0
v0.12.0
What's Changed
⚡️ Work begins on coordinate system transforms
@animeshsasan has kicked off our efforts in this area.
He has added a new function for computing homography-based transforms between sets of points on a plane. This is the first step towards supporting multi-session alignment in a common coordinate system, with further work planned for upcoming releases (see issue #565).
- Add function to estimate perspective transform between two planes by @animeshsasan in #696
🧹 Housekeeping
- Add workflow to check conda installation weekly by @niksirbi in #701
- Fix errors in conda install check workflow by @niksirbi in #703
- Remove setup-miniconda action from test workflow by @niksirbi in #705
- Add OSSS 2026 banner by @adamltyson in #707
Full Changelog: v0.11.1...v0.12.0
v0.11.1
This release patches some minor issues with our website and welcomes one new contributor to the project.
The actual package functionality is not affected.
What's Changed
- Move displacement blog image to its proper place by @niksirbi in #697
- suppressed hash value of SHA from pooch by @LakshmiSowmya04 in #695
- More specific project description in pyproject.toml by @niksirbi in #698
- Fix issues with multi-version docs by @niksirbi in #700
New Contributors
- @LakshmiSowmya04 made their first contribution in #695
Full Changelog: v0.11.0...v0.11.1
v0.11.0
Summary
movement v0.11.0 redefines displacement vectors, simplifies installation via pip and uv, and introduces multi-version documentation. It also brings support for 3D DeepLabCut files and welcomes many new contributors 🎉.
What’s changed
↗️ Refined displacement vectors
@carlocastoldi has overhauled how movement computes displacement vectors, making the definitions more explicit, flexible, and intuitive for users. You can read all about these changes in our latest blog post, written by Carlo.
- improve resulting displacement vectors by @carlocastoldi in #657
- Add blog post about displacement's API changes by @carlocastoldi in #688
⚡️ Simplified installation via pip and uv
We’ve streamlined the dependency setup so that movement can now be installed directly with pip or uv—no extra steps required.
Early adopters can try:
uv pip install movementSee our updated installation guide for full details.
📚 Multi-version documentation
Thanks to @animeshsasan, the documentation now includes a version switcher! This means that, going forward, you will be able to browse docs for multiple versions of movement, making it much easier to follow along with future updates and interface changes.
- Changes for multi-version docs by @animeshsasan in #668
📂 Improved handling of DeepLabCut files
Through a collaboration between @Akseli-Ilmanen and @lochhh, movement can now load and save DeepLabCut files with 3D coordinates (x, y, z).
In addition, @CeliaLrt clarified the documentation to make explicit that movement currently requires all keypoints to be shared across individuals.
- Support loading and saving 3D DeepLabCut poses by @lochhh in #686
- Add documentation to specify Movement require identical keypoints (#150) by @CeliaLrt in #658
🧹 Housekeeping
- Update links to SLEAP docs by @niksirbi in #681
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #682
- Update links to animovement by @niksirbi in #684
- Pin netcdf<1.7.3 to fix failing integration tests by @niksirbi in #685
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #692
- Contributors-Readme-Action: Update contributors list by @github-actions[bot] in #690
- Bump actions/download-artifact from 5 to 6 by @dependabot[bot] in #691
🧑💻 New Contributors
- @carlocastoldi made their first contribution in #657
- @CeliaLrt made their first contribution in #658
- @animeshsasan made their first contribution in #66
- @Akseli-Ilmanen made their first contribution by co-authoring #686
Full Changelog: v0.10.0...v0.11.0
v0.10.0
What's Changed
✨ Highlight ✨ movement meets FreeMoCap
A new example demonstrating how to read 3D data from FreeMoCap recordings as movement poses datasets. Thanks to @maxstaras for collecting the data and writing this example! 🚀
- Adding FreeMoCap example by @maxstaras in #633
New feature: export a bounding boxes dataset as a VIA-tracks .csv file
- Export bboxes dataset as VIA tracks .csv file by @sfmig in #580
- Update example to show usage of
to_via_tracks_filefrom PR 580 by @sfmig in #680
Documentation
- Add missing contributors by @niksirbi in #665
- Mention typical timing of community calls on website and README by @niksirbi in #672
Housekeeping
- Pin napari-video >= 0.2.13 by @niksirbi in #662
- Contributors-Readme-Action: Update contributors list by @github-actions[bot] in #671
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #673
- Bump actions/download-artifact from 4 to 5 by @dependabot[bot] in #674
New Contributors
- @maxstaras made their first contribution in #633
Full Changelog: v0.9.0...v0.10.0
v0.9.0
What's Changed
New feature: compute kinetic energy
- feat: compute_kinetic_energy for per-individual KE decomposition (#228) by @vtushar06 in #623
The compute_kinetic_energy function is the newest addition to the movement.kinematics module. It can be used to derive the total kinetic energy per individual, or decompose it into "translational" (centre-of-mass motion) and "internal" components (motion of keypoints relative to individual's centre-of-mass). Thanks to @vtushar06 for driving this forward.
Improvements to documentation
Spearheaded by @lochhh.
- Replace external links to movement docs with explicit targets by @lochhh in #642
- Aggregate API docs for flat modules by @lochhh in #648
- Add gallery of examples in API documentation by @lochhh in #644
Housekeeping
- Contributors-Readme-Action: Update contributors list by @github-actions[bot] in #650
- Bump akhilmhdh/contributors-readme-action from 2.3.10 to 2.3.11 by @dependabot[bot] in #651
- Add downloads badge by @adamltyson in #652
- [pre-commit.ci] pre-commit autoupdate by @pre-commit-ci[bot] in #653
Full Changelog: v0.8.2...v0.9.0