diff --git a/.buildinfo b/.buildinfo index 1e9bfcec..d621ee30 100644 --- a/.buildinfo +++ b/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 73eb998e14fd2132a732d009e358aee8 +config: ce414a2ba73a00c5a7f97cc1fa7502a7 tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.doctrees/api/movement.analysis.kinematics.compute_acceleration.doctree b/.doctrees/api/movement.analysis.kinematics.compute_acceleration.doctree new file mode 100644 index 00000000..8a065190 Binary files /dev/null and b/.doctrees/api/movement.analysis.kinematics.compute_acceleration.doctree differ diff --git a/.doctrees/api/movement.analysis.kinematics.compute_displacement.doctree b/.doctrees/api/movement.analysis.kinematics.compute_displacement.doctree new file mode 100644 index 00000000..1831a57a Binary files /dev/null and b/.doctrees/api/movement.analysis.kinematics.compute_displacement.doctree differ diff --git a/.doctrees/api/movement.analysis.kinematics.compute_velocity.doctree b/.doctrees/api/movement.analysis.kinematics.compute_velocity.doctree new file mode 100644 index 00000000..f0590c7b Binary files /dev/null and b/.doctrees/api/movement.analysis.kinematics.compute_velocity.doctree differ diff --git a/.doctrees/api/movement.filtering.filter_by_confidence.doctree b/.doctrees/api/movement.filtering.filter_by_confidence.doctree new file mode 100644 index 00000000..d506e561 Binary files /dev/null and b/.doctrees/api/movement.filtering.filter_by_confidence.doctree differ diff --git a/.doctrees/api/movement.filtering.interpolate_over_time.doctree b/.doctrees/api/movement.filtering.interpolate_over_time.doctree new file mode 100644 index 00000000..a319feb6 Binary files /dev/null and b/.doctrees/api/movement.filtering.interpolate_over_time.doctree differ diff --git a/.doctrees/api/movement.filtering.report_nan_values.doctree b/.doctrees/api/movement.filtering.report_nan_values.doctree new file mode 100644 index 00000000..df6e674f Binary files /dev/null and b/.doctrees/api/movement.filtering.report_nan_values.doctree differ diff --git a/.doctrees/api/movement.io.load_poses.from_file.doctree b/.doctrees/api/movement.io.load_poses.from_file.doctree new file mode 100644 index 00000000..79bcbb3c Binary files /dev/null and b/.doctrees/api/movement.io.load_poses.from_file.doctree differ diff --git a/.doctrees/api/movement.move_accessor.MoveAccessor.doctree b/.doctrees/api/movement.move_accessor.MoveAccessor.doctree new file mode 100644 index 00000000..2a891146 Binary files /dev/null and b/.doctrees/api/movement.move_accessor.MoveAccessor.doctree differ diff --git a/.doctrees/api/movement.sample_data.fetch_sample_data.doctree b/.doctrees/api/movement.sample_data.fetch_sample_data.doctree new file mode 100644 index 00000000..c5bf22f6 Binary files /dev/null and b/.doctrees/api/movement.sample_data.fetch_sample_data.doctree differ diff --git a/.doctrees/api/movement.datasets.fetch_pose_data_path.doctree b/.doctrees/api/movement.sample_data.fetch_sample_data_path.doctree similarity index 63% rename from .doctrees/api/movement.datasets.fetch_pose_data_path.doctree rename to .doctrees/api/movement.sample_data.fetch_sample_data_path.doctree index 75f15b53..076be91c 100644 Binary files a/.doctrees/api/movement.datasets.fetch_pose_data_path.doctree and b/.doctrees/api/movement.sample_data.fetch_sample_data_path.doctree differ diff --git a/.doctrees/api/movement.datasets.list_pose_data.doctree b/.doctrees/api/movement.sample_data.list_sample_data.doctree similarity index 63% rename from .doctrees/api/movement.datasets.list_pose_data.doctree rename to .doctrees/api/movement.sample_data.list_sample_data.doctree index 3eae2867..43a02bf7 100644 Binary files a/.doctrees/api/movement.datasets.list_pose_data.doctree and b/.doctrees/api/movement.sample_data.list_sample_data.doctree differ diff --git a/.doctrees/api_index.doctree b/.doctrees/api_index.doctree index c613bca8..bd1ff51c 100644 Binary files a/.doctrees/api_index.doctree and b/.doctrees/api_index.doctree differ diff --git a/.doctrees/community/contributing.doctree b/.doctrees/community/contributing.doctree index c3c25085..62371771 100644 Binary files a/.doctrees/community/contributing.doctree and b/.doctrees/community/contributing.doctree differ diff --git a/.doctrees/environment.pickle b/.doctrees/environment.pickle index d07dbcff..cfa0436f 100644 Binary files a/.doctrees/environment.pickle and b/.doctrees/environment.pickle differ diff --git a/.doctrees/examples/filter_and_interpolate.doctree b/.doctrees/examples/filter_and_interpolate.doctree new file mode 100644 index 00000000..e86e09f7 Binary files /dev/null and b/.doctrees/examples/filter_and_interpolate.doctree differ diff --git a/.doctrees/examples/index.doctree b/.doctrees/examples/index.doctree index a7aecaf5..4d13a69f 100644 Binary files a/.doctrees/examples/index.doctree and b/.doctrees/examples/index.doctree differ diff --git a/.doctrees/examples/load_and_explore_poses.doctree b/.doctrees/examples/load_and_explore_poses.doctree index 4098b950..67e4c162 100644 Binary files a/.doctrees/examples/load_and_explore_poses.doctree and b/.doctrees/examples/load_and_explore_poses.doctree differ diff --git a/.doctrees/examples/sg_execution_times.doctree b/.doctrees/examples/sg_execution_times.doctree index 2cd8d1b1..80c05406 100644 Binary files a/.doctrees/examples/sg_execution_times.doctree and b/.doctrees/examples/sg_execution_times.doctree differ diff --git a/.doctrees/getting_started.doctree b/.doctrees/getting_started.doctree index 9c0d2c0b..6379ba90 100644 Binary files a/.doctrees/getting_started.doctree and b/.doctrees/getting_started.doctree differ diff --git a/.doctrees/sg_execution_times.doctree b/.doctrees/sg_execution_times.doctree index 138638f4..2bd18160 100644 Binary files a/.doctrees/sg_execution_times.doctree and b/.doctrees/sg_execution_times.doctree differ diff --git a/_downloads/122338c6db2328ed9eeb5e9961344704/filter_and_interpolate.ipynb b/_downloads/122338c6db2328ed9eeb5e9961344704/filter_and_interpolate.ipynb new file mode 100644 index 00000000..827c035d --- /dev/null +++ b/_downloads/122338c6db2328ed9eeb5e9961344704/filter_and_interpolate.ipynb @@ -0,0 +1,240 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Filtering and interpolation\n\nFilter out points with low confidence scores and interpolate over\nmissing values.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Imports\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from movement import sample_data\nfrom movement.filtering import filter_by_confidence, interpolate_over_time" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load a sample dataset\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "ds = sample_data.fetch_sample_data(\"DLC_single-wasp.predictions.h5\")\nprint(ds)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see that this dataset contains the 2D pose tracks and confidence\nscores for a single wasp, generated with DeepLabCut. There are 2 keypoints:\n\"head\" and \"stinger\".\n\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Visualise the pose tracks\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "pose_tracks = ds.pose_tracks.sel(individuals=\"individual_0\")\npose_tracks.plot.line(\n x=\"time\", row=\"keypoints\", hue=\"space\", aspect=2, size=2.5\n)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see that the pose tracks contain some implausible \"jumps\", such\nas the the big shift in the final second, and the \"spikes\" of the stinger\nnear the 14th second. Perhaps we can get rid of those based on the model's\nreported confidence scores?\n\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Visualise confidence scores\nThe confidence scores are stored in the ``confidence`` data variable.\nSince the predicted poses in this example have been generated by DeepLabCut,\nthe confidence scores should be likelihood values between 0 and 1.\nThat said, confidence scores are not standardised across pose\nestimation frameworks, and their ranges can vary. Therefore,\nit's always a good idea to inspect the actual confidence values in the data.\n\nLet's first look at a histogram of the confidence scores.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "ds.confidence.plot.hist(bins=20)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Based on the above histogram, we can confirm that the confidence scores\nindeed range between 0 and 1, with most values closer to 1. Now let's see how\nthey evolve over time.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "confidence = ds.confidence.sel(individuals=\"individual_0\")\nconfidence.plot.line(x=\"time\", row=\"keypoints\", aspect=2, size=2.5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Encouragingly, some of the drops in confidence scores do seem to correspond\nto the implausible jumps and spikes we had seen in the pose tracks.\nWe can use that to our advantage.\n\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Filter out points with low confidence\nWe can filter out points with confidence scores below a certain threshold.\nHere, we use ``threshold=0.6``. Points in the ``pose tracks`` data variable\nwith confidence scores below this threshold will be converted to NaN.\nThe ``print_report`` argument, which is True by default, reports the number\nof NaN values in the dataset before and after the filtering operation.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "ds_filtered = filter_by_confidence(ds, threshold=0.6, print_report=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see that the filtering operation has introduced NaN values in the\n``pose_tracks`` data variable. Let's visualise the filtered pose tracks.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "pose_tracks_filtered = ds_filtered.pose_tracks.sel(individuals=\"individual_0\")\npose_tracks_filtered.plot.line(\n x=\"time\", row=\"keypoints\", hue=\"space\", aspect=2, size=2.5\n)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here we can see that gaps have appeared in the pose tracks, some of which\nare over the implausible jumps and spikes we had seen earlier. Moreover,\nmost gaps seem to be brief, lasting < 1 second.\n\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Interpolate over missing values\nWe can interpolate over the gaps we've introduced in the pose tracks.\nHere we use the default linear interpolation method and ``max_gap=1``,\nmeaning that we will only interpolate over gaps of 1 second or shorter.\nSetting ``max_gap=None`` would interpolate over all gaps, regardless of\ntheir length, which should be used with caution as it can introduce\nspurious data. The ``print_report`` argument acts as described above.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "ds_interpolated = interpolate_over_time(\n ds_filtered, method=\"linear\", max_gap=1, print_report=True\n)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We see that all NaN values have disappeared, meaning that all gaps were\nindeed shorter than 1 second. Let's visualise the interpolated pose tracks\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "pose_tracks_interpolated = ds_interpolated.pose_tracks.sel(\n individuals=\"individual_0\"\n)\npose_tracks_interpolated.plot.line(\n x=\"time\", row=\"keypoints\", hue=\"space\", aspect=2, size=2.5\n)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Log of processing steps\nSo, far we've processed the pose tracks first by filtering out points with\nlow confidence scores, and then by interpolating over missing values.\nThe order of these operations and the parameters with which they were\nperformed are saved in the ``log`` attribute of the dataset.\nThis is useful for keeping track of the processing steps that have been\napplied to the data.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "for log_entry in ds_interpolated.log:\n print(log_entry)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.8" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/_downloads/307bfbce3b04c474f9b3bd90e970e4c9/load_and_explore_poses.py b/_downloads/307bfbce3b04c474f9b3bd90e970e4c9/load_and_explore_poses.py index de5f5196..4a68ee7e 100644 --- a/_downloads/307bfbce3b04c474f9b3bd90e970e4c9/load_and_explore_poses.py +++ b/_downloads/307bfbce3b04c474f9b3bd90e970e4c9/load_and_explore_poses.py @@ -10,7 +10,7 @@ # ------- from matplotlib import pyplot as plt -from movement import datasets +from movement import sample_data from movement.io import load_poses # %% @@ -18,14 +18,14 @@ # ------------------------ # Print a list of available datasets: -for file_name in datasets.list_pose_data(): +for file_name in sample_data.list_sample_data(): print(file_name) # %% # Fetch the path to an example dataset. # Feel free to replace this with the path to your own dataset. # e.g., ``file_path = "/path/to/my/data.h5"``) -file_path = datasets.fetch_pose_data_path( +file_path = sample_data.fetch_sample_data_path( "SLEAP_three-mice_Aeon_proofread.analysis.h5" ) diff --git a/_downloads/7f0c10d5c8b88801f34de1b2292ae010/load_and_explore_poses.ipynb b/_downloads/7f0c10d5c8b88801f34de1b2292ae010/load_and_explore_poses.ipynb index ccc14ec3..8b0cda2a 100644 --- a/_downloads/7f0c10d5c8b88801f34de1b2292ae010/load_and_explore_poses.ipynb +++ b/_downloads/7f0c10d5c8b88801f34de1b2292ae010/load_and_explore_poses.ipynb @@ -22,7 +22,7 @@ }, "outputs": [], "source": [ - "from matplotlib import pyplot as plt\n\nfrom movement import datasets\nfrom movement.io import load_poses" + "from matplotlib import pyplot as plt\n\nfrom movement import sample_data\nfrom movement.io import load_poses" ] }, { @@ -40,7 +40,7 @@ }, "outputs": [], "source": [ - "for file_name in datasets.list_pose_data():\n print(file_name)" + "for file_name in sample_data.list_sample_data():\n print(file_name)" ] }, { @@ -58,7 +58,7 @@ }, "outputs": [], "source": [ - "file_path = datasets.fetch_pose_data_path(\n \"SLEAP_three-mice_Aeon_proofread.analysis.h5\"\n)" + "file_path = sample_data.fetch_sample_data_path(\n \"SLEAP_three-mice_Aeon_proofread.analysis.h5\"\n)" ] }, { @@ -186,7 +186,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.7" + "version": "3.11.8" } }, "nbformat": 4, diff --git a/_downloads/8616797cd8df925412b19674f364ffea/filter_and_interpolate.py b/_downloads/8616797cd8df925412b19674f364ffea/filter_and_interpolate.py new file mode 100644 index 00000000..004e3a04 --- /dev/null +++ b/_downloads/8616797cd8df925412b19674f364ffea/filter_and_interpolate.py @@ -0,0 +1,130 @@ +""" +Filtering and interpolation +============================ + +Filter out points with low confidence scores and interpolate over +missing values. +""" + +# %% +# Imports +# ------- +from movement import sample_data +from movement.filtering import filter_by_confidence, interpolate_over_time + +# %% +# Load a sample dataset +# --------------------- + +ds = sample_data.fetch_sample_data("DLC_single-wasp.predictions.h5") +print(ds) + +# %% +# We can see that this dataset contains the 2D pose tracks and confidence +# scores for a single wasp, generated with DeepLabCut. There are 2 keypoints: +# "head" and "stinger". + +# %% +# Visualise the pose tracks +# ------------------------- + +pose_tracks = ds.pose_tracks.sel(individuals="individual_0") +pose_tracks.plot.line( + x="time", row="keypoints", hue="space", aspect=2, size=2.5 +) + +# %% +# We can see that the pose tracks contain some implausible "jumps", such +# as the the big shift in the final second, and the "spikes" of the stinger +# near the 14th second. Perhaps we can get rid of those based on the model's +# reported confidence scores? + +# %% +# Visualise confidence scores +# --------------------------- +# The confidence scores are stored in the ``confidence`` data variable. +# Since the predicted poses in this example have been generated by DeepLabCut, +# the confidence scores should be likelihood values between 0 and 1. +# That said, confidence scores are not standardised across pose +# estimation frameworks, and their ranges can vary. Therefore, +# it's always a good idea to inspect the actual confidence values in the data. +# +# Let's first look at a histogram of the confidence scores. +ds.confidence.plot.hist(bins=20) + +# %% +# Based on the above histogram, we can confirm that the confidence scores +# indeed range between 0 and 1, with most values closer to 1. Now let's see how +# they evolve over time. + +confidence = ds.confidence.sel(individuals="individual_0") +confidence.plot.line(x="time", row="keypoints", aspect=2, size=2.5) + +# %% +# Encouragingly, some of the drops in confidence scores do seem to correspond +# to the implausible jumps and spikes we had seen in the pose tracks. +# We can use that to our advantage. + + +# %% +# Filter out points with low confidence +# ------------------------------------- +# We can filter out points with confidence scores below a certain threshold. +# Here, we use ``threshold=0.6``. Points in the ``pose tracks`` data variable +# with confidence scores below this threshold will be converted to NaN. +# The ``print_report`` argument, which is True by default, reports the number +# of NaN values in the dataset before and after the filtering operation. + +ds_filtered = filter_by_confidence(ds, threshold=0.6, print_report=True) + +# %% +# We can see that the filtering operation has introduced NaN values in the +# ``pose_tracks`` data variable. Let's visualise the filtered pose tracks. + +pose_tracks_filtered = ds_filtered.pose_tracks.sel(individuals="individual_0") +pose_tracks_filtered.plot.line( + x="time", row="keypoints", hue="space", aspect=2, size=2.5 +) + +# %% +# Here we can see that gaps have appeared in the pose tracks, some of which +# are over the implausible jumps and spikes we had seen earlier. Moreover, +# most gaps seem to be brief, lasting < 1 second. + +# %% +# Interpolate over missing values +# ------------------------------- +# We can interpolate over the gaps we've introduced in the pose tracks. +# Here we use the default linear interpolation method and ``max_gap=1``, +# meaning that we will only interpolate over gaps of 1 second or shorter. +# Setting ``max_gap=None`` would interpolate over all gaps, regardless of +# their length, which should be used with caution as it can introduce +# spurious data. The ``print_report`` argument acts as described above. + +ds_interpolated = interpolate_over_time( + ds_filtered, method="linear", max_gap=1, print_report=True +) + +# %% +# We see that all NaN values have disappeared, meaning that all gaps were +# indeed shorter than 1 second. Let's visualise the interpolated pose tracks + +pose_tracks_interpolated = ds_interpolated.pose_tracks.sel( + individuals="individual_0" +) +pose_tracks_interpolated.plot.line( + x="time", row="keypoints", hue="space", aspect=2, size=2.5 +) + +# %% +# Log of processing steps +# ----------------------- +# So, far we've processed the pose tracks first by filtering out points with +# low confidence scores, and then by interpolating over missing values. +# The order of these operations and the parameters with which they were +# performed are saved in the ``log`` attribute of the dataset. +# This is useful for keeping track of the processing steps that have been +# applied to the data. + +for log_entry in ds_interpolated.log: + print(log_entry) diff --git a/_downloads/bc82bea3a5dd7bdba60b65220891d9e5/examples_python.zip b/_downloads/bc82bea3a5dd7bdba60b65220891d9e5/examples_python.zip index 0b2f10d6..fbb3b3a8 100644 Binary files a/_downloads/bc82bea3a5dd7bdba60b65220891d9e5/examples_python.zip and b/_downloads/bc82bea3a5dd7bdba60b65220891d9e5/examples_python.zip differ diff --git a/_downloads/fb625db3c50d423b1b7881136ffdeec8/examples_jupyter.zip b/_downloads/fb625db3c50d423b1b7881136ffdeec8/examples_jupyter.zip index 31503bce..eec11755 100644 Binary files a/_downloads/fb625db3c50d423b1b7881136ffdeec8/examples_jupyter.zip and b/_downloads/fb625db3c50d423b1b7881136ffdeec8/examples_jupyter.zip differ diff --git a/_images/sphx_glr_filter_and_interpolate_001.png b/_images/sphx_glr_filter_and_interpolate_001.png new file mode 100644 index 00000000..6f30664a Binary files /dev/null and b/_images/sphx_glr_filter_and_interpolate_001.png differ diff --git a/_images/sphx_glr_filter_and_interpolate_002.png b/_images/sphx_glr_filter_and_interpolate_002.png new file mode 100644 index 00000000..d3c7892b Binary files /dev/null and b/_images/sphx_glr_filter_and_interpolate_002.png differ diff --git a/_images/sphx_glr_filter_and_interpolate_003.png b/_images/sphx_glr_filter_and_interpolate_003.png new file mode 100644 index 00000000..72e84963 Binary files /dev/null and b/_images/sphx_glr_filter_and_interpolate_003.png differ diff --git a/_images/sphx_glr_filter_and_interpolate_004.png b/_images/sphx_glr_filter_and_interpolate_004.png new file mode 100644 index 00000000..c3d720e8 Binary files /dev/null and b/_images/sphx_glr_filter_and_interpolate_004.png differ diff --git a/_images/sphx_glr_filter_and_interpolate_005.png b/_images/sphx_glr_filter_and_interpolate_005.png new file mode 100644 index 00000000..00b80418 Binary files /dev/null and b/_images/sphx_glr_filter_and_interpolate_005.png differ diff --git a/_images/sphx_glr_filter_and_interpolate_thumb.png b/_images/sphx_glr_filter_and_interpolate_thumb.png new file mode 100644 index 00000000..d3bc823c Binary files /dev/null and b/_images/sphx_glr_filter_and_interpolate_thumb.png differ diff --git a/_images/sphx_glr_load_and_explore_poses_001.png b/_images/sphx_glr_load_and_explore_poses_001.png index f1477585..57a279a6 100644 Binary files a/_images/sphx_glr_load_and_explore_poses_001.png and b/_images/sphx_glr_load_and_explore_poses_001.png differ diff --git a/_images/sphx_glr_load_and_explore_poses_002.png b/_images/sphx_glr_load_and_explore_poses_002.png index 659638a0..34dd04d9 100644 Binary files a/_images/sphx_glr_load_and_explore_poses_002.png and b/_images/sphx_glr_load_and_explore_poses_002.png differ diff --git a/_images/sphx_glr_load_and_explore_poses_003.png b/_images/sphx_glr_load_and_explore_poses_003.png index 8e242141..6a9bad7f 100644 Binary files a/_images/sphx_glr_load_and_explore_poses_003.png and b/_images/sphx_glr_load_and_explore_poses_003.png differ diff --git a/_modules/index.html b/_modules/index.html index 34b36d67..832183da 100644 --- a/_modules/index.html +++ b/_modules/index.html @@ -17,15 +17,15 @@ - - - + + + - - - - + + + + @@ -36,11 +36,11 @@ - - - + + + - + @@ -59,10 +59,9 @@ - +
- + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for movement.analysis.kinematics

+import numpy as np
+import xarray as xr
+
+from movement.logging import log_error
+
+
+
[docs]def compute_displacement(data: xr.DataArray) -> xr.DataArray: + """Compute the displacement between consecutive positions + of each keypoint for each individual across time. + + Parameters + ---------- + data : xarray.DataArray + The input data containing ``time`` as a dimension. + + Returns + ------- + xarray.DataArray + An xarray DataArray containing the computed displacement. + """ + _validate_time_dimension(data) + result = data.diff(dim="time") + result = result.reindex(data.coords, fill_value=0) + return result
+ + +
[docs]def compute_velocity(data: xr.DataArray) -> xr.DataArray: + """Compute the velocity between consecutive positions + of each keypoint for each individual across time. + + Parameters + ---------- + data : xarray.DataArray + The input data containing ``time`` as a dimension. + + Returns + ------- + xarray.DataArray + An xarray DataArray containing the computed velocity. + + Notes + ----- + This function computes velocity using numerical differentiation + and assumes equidistant time spacing. + """ + return _compute_approximate_derivative(data, order=1)
+ + +
[docs]def compute_acceleration(data: xr.DataArray) -> xr.DataArray: + """Compute the acceleration between consecutive positions + of each keypoint for each individual across time. + + Parameters + ---------- + data : xarray.DataArray + The input data containing ``time`` as a dimension. + + Returns + ------- + xarray.DataArray + An xarray DataArray containing the computed acceleration. + + Notes + ----- + This function computes acceleration using numerical differentiation + and assumes equidistant time spacing. + """ + return _compute_approximate_derivative(data, order=2)
+ + +def _compute_approximate_derivative( + data: xr.DataArray, order: int +) -> xr.DataArray: + """Compute velocity or acceleration using numerical differentiation, + assuming equidistant time spacing. + + Parameters + ---------- + data : xarray.DataArray + The input data containing ``time`` as a dimension. + order : int + The order of the derivative. 1 for velocity, 2 for + acceleration. Value must be a positive integer. + + Returns + ------- + xarray.DataArray + An xarray DataArray containing the derived variable. + """ + if not isinstance(order, int): + raise log_error( + TypeError, f"Order must be an integer, but got {type(order)}." + ) + if order <= 0: + raise log_error(ValueError, "Order must be a positive integer.") + _validate_time_dimension(data) + result = data + dt = data["time"].values[1] - data["time"].values[0] + for _ in range(order): + result = xr.apply_ufunc( + np.gradient, + result, + dt, + kwargs={"axis": 0}, + ) + result = result.reindex_like(data) + return result + + +def _validate_time_dimension(data: xr.DataArray) -> None: + """Validate the input data contains a ``time`` dimension. + + Parameters + ---------- + data : xarray.DataArray + The input data to validate. + + Raises + ------ + ValueError + If the input data does not contain a ``time`` dimension. + """ + if "time" not in data.dims: + raise log_error( + ValueError, "Input data must contain 'time' as a dimension." + ) +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/movement/filtering.html b/_modules/movement/filtering.html new file mode 100644 index 00000000..4b2403d6 --- /dev/null +++ b/_modules/movement/filtering.html @@ -0,0 +1,649 @@ + + + + + + + + + + movement.filtering — movement + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for movement.filtering

+import logging
+from datetime import datetime
+from functools import wraps
+from typing import Union
+
+import xarray as xr
+
+
+def log_to_attrs(func):
+    """
+    Decorator that logs the operation performed by the wrapped function
+    and appends the log entry to the xarray.Dataset's "log" attribute.
+    For the decorator to work, the wrapped function must accept an
+    xarray.Dataset as its first argument and return an xarray.Dataset.
+    """
+
+    @wraps(func)
+    def wrapper(*args, **kwargs):
+        result = func(*args, **kwargs)
+
+        log_entry = {
+            "operation": func.__name__,
+            "datetime": str(datetime.now()),
+            **{f"arg_{i}": arg for i, arg in enumerate(args[1:], start=1)},
+            **kwargs,
+        }
+
+        # Append the log entry to the result's attributes
+        if result is not None and hasattr(result, "attrs"):
+            if "log" not in result.attrs.keys():
+                result.attrs["log"] = []
+            result.attrs["log"].append(log_entry)
+
+        return result
+
+    return wrapper
+
+
+
[docs]def report_nan_values(ds: xr.Dataset, ds_label: str = "dataset"): + """ + Report the number and percentage of points that are NaN for each individual + and each keypoint in the provided dataset. + + Parameters + ---------- + ds : xarray.Dataset + Dataset containing pose tracks, confidence scores, and metadata. + ds_label : str + Label to identify the dataset in the report. Default is "dataset". + """ + + # Compile the report + nan_report = f"\nMissing points (marked as NaN) in {ds_label}:" + for ind in ds.individuals.values: + nan_report += f"\n\tIndividual: {ind}" + for kp in ds.keypoints.values: + # Get the track for the current individual and keypoint + track_ = ds.pose_tracks.sel(individuals=ind, keypoints=kp) + # A point is considered NaN if any of its space coordinates are NaN + n_nans = track_.isnull().any(["space"]).sum(["time"]).item() + n_points = track_.time.size + percent_nans = round((n_nans / n_points) * 100, 1) + nan_report += f"\n\t\t{kp}: {n_nans}/{n_points} ({percent_nans}%)" + + # Write nan report to logger + logger = logging.getLogger(__name__) + logger.info(nan_report) + # Also print the report to the console + print(nan_report) + return None
+ + +
[docs]@log_to_attrs +def interpolate_over_time( + ds: xr.Dataset, + method: str = "linear", + max_gap: Union[int, None] = None, + print_report: bool = True, +) -> Union[xr.Dataset, None]: + """ + Fill in NaN values by interpolating over the time dimension. + + Parameters + ---------- + ds : xarray.Dataset + Dataset containing pose tracks, confidence scores, and metadata. + method : str + String indicating which method to use for interpolation. + Default is ``linear``. See documentation for + ``xarray.DataArray.interpolate_na`` for complete list of options. + max_gap : + The largest time gap of consecutive NaNs (in seconds) that will be + interpolated over. The default value is ``None`` (no limit). + print_report : bool + Whether to print a report on the number of NaNs in the dataset + before and after interpolation. Default is ``True``. + + Returns + ------- + ds_interpolated : xr.Dataset + The provided dataset (ds), where NaN values have been + interpolated over using the parameters provided. + """ + ds_interpolated = ds.copy() + poses_interpolated = ds.pose_tracks.interpolate_na( + dim="time", method=method, max_gap=max_gap, fill_value="extrapolate" + ) + ds_interpolated.update({"pose_tracks": poses_interpolated}) + if print_report: + report_nan_values(ds, "input dataset") + report_nan_values(ds_interpolated, "interpolated dataset") + return ds_interpolated
+ + +
[docs]@log_to_attrs +def filter_by_confidence( + ds: xr.Dataset, + threshold: float = 0.6, + print_report: bool = True, +) -> Union[xr.Dataset, None]: + """ + Drop all points where the associated confidence value falls below a + user-defined threshold. + + Parameters + ---------- + ds : xarray.Dataset + Dataset containing pose tracks, confidence scores, and metadata. + threshold : float + The confidence threshold below which datapoints are filtered. + A default value of ``0.6`` is used. See notes for more information. + print_report : bool + Whether to print a report on the number of NaNs in the dataset + before and after filtering. Default is ``True``. + + Returns + ------- + ds_thresholded : xarray.Dataset + The provided dataset (ds), where points with a confidence + value below the user-defined threshold have been converted + to NaNs + + Notes + ----- + The point-wise confidence values reported by various pose estimation + frameworks are not standardised, and the range of values can vary. + For example, DeepLabCut reports a likelihood value between 0 and 1, whereas + the point confidence reported by SLEAP can range above 1. + Therefore, the default threshold value will not be appropriate for all + datasets and does not have the same meaning across pose estimation + frameworks. We advise users to inspect the confidence values + in their dataset and adjust the threshold accordingly. + """ + ds_thresholded = ds.copy() + ds_thresholded.update( + {"pose_tracks": ds.pose_tracks.where(ds.confidence >= threshold)} + ) + if print_report: + report_nan_values(ds, "input dataset") + report_nan_values(ds_thresholded, "filtered dataset") + + return ds_thresholded
+
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/movement/io/load_poses.html b/_modules/movement/io/load_poses.html index 0d7b3e93..bf674642 100644 --- a/_modules/movement/io/load_poses.html +++ b/_modules/movement/io/load_poses.html @@ -17,15 +17,15 @@ - - - + + + - - - - + + + + @@ -36,11 +36,11 @@ - - - + + + - + @@ -59,10 +59,9 @@ - +
- + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for movement.move_accessor

+import logging
+from typing import Callable, ClassVar
+
+import xarray as xr
+
+from movement.analysis import kinematics
+from movement.io.validators import ValidPoseTracks
+
+logger = logging.getLogger(__name__)
+
+# Preserve the attributes (metadata) of xarray objects after operations
+xr.set_options(keep_attrs=True)
+
+
+
[docs]@xr.register_dataset_accessor("move") +class MoveAccessor: + """An accessor that extends an xarray Dataset by implementing + `movement`-specific properties and methods. + + The xarray Dataset contains the following expected dimensions: + - ``time``: the number of frames in the video + - ``individuals``: the number of individuals in the video + - ``keypoints``: the number of keypoints in the skeleton + - ``space``: the number of spatial dimensions, either 2 or 3 + + Appropriate coordinate labels are assigned to each dimension: + list of unique names (str) for ``individuals`` and ``keypoints``, + ['x','y',('z')] for ``space``. The coordinates of the ``time`` dimension + are in seconds if ``fps`` is provided, otherwise they are in frame numbers. + + The dataset contains two expected data variables (xarray DataArrays): + - ``pose_tracks``: with shape (``time``, ``individuals``, + ``keypoints``, ``space``) + - ``confidence``: with shape (``time``, ``individuals``, ``keypoints``) + + When accessing a ``.move`` property (e.g. ``displacement``, ``velocity``, + ``acceleration``) for the first time, the property is computed and stored + as a data variable with the same name in the dataset. The ``.move`` + accessor can be omitted in subsequent accesses, i.e. + ``ds.move.displacement`` and ``ds.displacement`` will return the same data + variable. + + The dataset may also contain following attributes as metadata: + - ``fps``: the number of frames per second in the video + - ``time_unit``: the unit of the ``time`` coordinates, frames or + seconds + - ``source_software``: the software from which the pose tracks were + loaded + - ``source_file``: the file from which the pose tracks were + loaded + + Notes + ----- + Using an accessor is the recommended way to extend xarray objects. + See [1]_ for more details. + + Methods/properties that are specific to this class can be accessed via + the ``.move`` accessor, e.g. ``ds.move.validate()``. + + References + ---------- + .. [1] https://docs.xarray.dev/en/stable/internals/extending-xarray.html + """ + + # Names of the expected dimensions in the dataset + dim_names: ClassVar[tuple] = ( + "time", + "individuals", + "keypoints", + "space", + ) + + # Names of the expected data variables in the dataset + var_names: ClassVar[tuple] = ( + "pose_tracks", + "confidence", + ) + +
[docs] def __init__(self, ds: xr.Dataset): + self._obj = ds
+ + def _compute_property( + self, + property: str, + compute_function: Callable[[xr.DataArray], xr.DataArray], + ) -> xr.DataArray: + """Compute a kinematic property and store it in the dataset. + + Parameters + ---------- + property : str + The name of the property to compute. + compute_function : Callable[[xarray.DataArray], xarray.DataArray] + The function to compute the property. + + Returns + ------- + xarray.DataArray + The computed property. + """ + self.validate() + if property not in self._obj: + pose_tracks = self._obj[self.var_names[0]] + self._obj[property] = compute_function(pose_tracks) + return self._obj[property] + + @property + def displacement(self) -> xr.DataArray: + """Return the displacement between consecutive positions + of each keypoint for each individual across time. + """ + return self._compute_property( + "displacement", kinematics.compute_displacement + ) + + @property + def velocity(self) -> xr.DataArray: + """Return the velocity between consecutive positions + of each keypoint for each individual across time. + """ + return self._compute_property("velocity", kinematics.compute_velocity) + + @property + def acceleration(self) -> xr.DataArray: + """Return the acceleration between consecutive positions + of each keypoint for each individual across time. + """ + return self._compute_property( + "acceleration", kinematics.compute_acceleration + ) + + def validate(self) -> None: + """Validate the PoseTracks dataset.""" + fps = self._obj.attrs.get("fps", None) + source_software = self._obj.attrs.get("source_software", None) + try: + missing_dims = set(self.dim_names) - set(self._obj.dims) + missing_vars = set(self.var_names) - set(self._obj.data_vars) + if missing_dims: + raise ValueError( + f"Missing required dimensions: {missing_dims}" + ) + if missing_vars: + raise ValueError( + f"Missing required data variables: {missing_vars}" + ) + ValidPoseTracks( + tracks_array=self._obj[self.var_names[0]].values, + scores_array=self._obj[self.var_names[1]].values, + individual_names=self._obj.coords[self.dim_names[1]].values, + keypoint_names=self._obj.coords[self.dim_names[2]].values, + fps=fps, + source_software=source_software, + ) + except Exception as e: + error_msg = "The dataset does not contain valid pose tracks." + logger.error(error_msg) + raise ValueError(error_msg) from e
+
+ +
+ + + + + +
+ +
+
+
+ +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/_modules/movement/datasets.html b/_modules/movement/sample_data.html similarity index 52% rename from _modules/movement/datasets.html rename to _modules/movement/sample_data.html index 88b304b6..275b9571 100644 --- a/_modules/movement/datasets.html +++ b/_modules/movement/sample_data.html @@ -7,7 +7,7 @@ - movement.datasets — movement + movement.sample_data — movement @@ -17,15 +17,15 @@ - - - + + + - - - - + + + + @@ -36,17 +36,17 @@ - - - + + + - + - - + + @@ -59,10 +59,9 @@ - +
- + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + +
+ + + + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +
+

movement.analysis.kinematics.compute_acceleration#

+
+
+movement.analysis.kinematics.compute_acceleration(data)[source]#
+

Compute the acceleration between consecutive positions +of each keypoint for each individual across time.

+
+
Parameters:
+

data (xarray.DataArray) – The input data containing time as a dimension.

+
+
Returns:
+

An xarray DataArray containing the computed acceleration.

+
+
Return type:
+

xarray.DataArray

+
+
+

Notes

+

This function computes acceleration using numerical differentiation +and assumes equidistant time spacing.

+
+ +
+ + +
+ + + + + + + +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/api/movement.analysis.kinematics.compute_displacement.html b/api/movement.analysis.kinematics.compute_displacement.html new file mode 100644 index 00000000..ec9c05cc --- /dev/null +++ b/api/movement.analysis.kinematics.compute_displacement.html @@ -0,0 +1,605 @@ + + + + + + + + + + + movement.analysis.kinematics.compute_displacement — movement + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + +
+ + + + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +
+

movement.analysis.kinematics.compute_displacement#

+
+
+movement.analysis.kinematics.compute_displacement(data)[source]#
+

Compute the displacement between consecutive positions +of each keypoint for each individual across time.

+
+
Parameters:
+

data (xarray.DataArray) – The input data containing time as a dimension.

+
+
Returns:
+

An xarray DataArray containing the computed displacement.

+
+
Return type:
+

xarray.DataArray

+
+
+
+ +
+ + +
+ + + + + + + +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/api/movement.analysis.kinematics.compute_velocity.html b/api/movement.analysis.kinematics.compute_velocity.html new file mode 100644 index 00000000..c9e81bdb --- /dev/null +++ b/api/movement.analysis.kinematics.compute_velocity.html @@ -0,0 +1,608 @@ + + + + + + + + + + + movement.analysis.kinematics.compute_velocity — movement + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + +
+ + + + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +
+

movement.analysis.kinematics.compute_velocity#

+
+
+movement.analysis.kinematics.compute_velocity(data)[source]#
+

Compute the velocity between consecutive positions +of each keypoint for each individual across time.

+
+
Parameters:
+

data (xarray.DataArray) – The input data containing time as a dimension.

+
+
Returns:
+

An xarray DataArray containing the computed velocity.

+
+
Return type:
+

xarray.DataArray

+
+
+

Notes

+

This function computes velocity using numerical differentiation +and assumes equidistant time spacing.

+
+ +
+ + +
+ + + + + + + +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/api/movement.filtering.filter_by_confidence.html b/api/movement.filtering.filter_by_confidence.html new file mode 100644 index 00000000..a11abffd --- /dev/null +++ b/api/movement.filtering.filter_by_confidence.html @@ -0,0 +1,622 @@ + + + + + + + + + + + movement.filtering.filter_by_confidence — movement + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + +
+ + + + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +
+

movement.filtering.filter_by_confidence#

+
+
+movement.filtering.filter_by_confidence(ds, threshold=0.6, print_report=True)[source]#
+

Drop all points where the associated confidence value falls below a +user-defined threshold.

+
+
Parameters:
+
    +
  • ds (xarray.Dataset) – Dataset containing pose tracks, confidence scores, and metadata.

  • +
  • threshold (float) – The confidence threshold below which datapoints are filtered. +A default value of 0.6 is used. See notes for more information.

  • +
  • print_report (bool) – Whether to print a report on the number of NaNs in the dataset +before and after filtering. Default is True.

  • +
+
+
Returns:
+

ds_thresholded – The provided dataset (ds), where points with a confidence +value below the user-defined threshold have been converted +to NaNs

+
+
Return type:
+

xarray.Dataset

+
+
+

Notes

+

The point-wise confidence values reported by various pose estimation +frameworks are not standardised, and the range of values can vary. +For example, DeepLabCut reports a likelihood value between 0 and 1, whereas +the point confidence reported by SLEAP can range above 1. +Therefore, the default threshold value will not be appropriate for all +datasets and does not have the same meaning across pose estimation +frameworks. We advise users to inspect the confidence values +in their dataset and adjust the threshold accordingly.

+
+ +
+ + +
+ + + + + + + +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/api/movement.filtering.interpolate_over_time.html b/api/movement.filtering.interpolate_over_time.html new file mode 100644 index 00000000..f51ddddd --- /dev/null +++ b/api/movement.filtering.interpolate_over_time.html @@ -0,0 +1,614 @@ + + + + + + + + + + + movement.filtering.interpolate_over_time — movement + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + +
+ + + + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +
+

movement.filtering.interpolate_over_time#

+
+
+movement.filtering.interpolate_over_time(ds, method='linear', max_gap=None, print_report=True)[source]#
+

Fill in NaN values by interpolating over the time dimension.

+
+
Parameters:
+
    +
  • ds (xarray.Dataset) – Dataset containing pose tracks, confidence scores, and metadata.

  • +
  • method (str) – String indicating which method to use for interpolation. +Default is linear. See documentation for +xarray.DataArray.interpolate_na for complete list of options.

  • +
  • max_gap (Optional[int]) – The largest time gap of consecutive NaNs (in seconds) that will be +interpolated over. The default value is None (no limit).

  • +
  • print_report (bool) – Whether to print a report on the number of NaNs in the dataset +before and after interpolation. Default is True.

  • +
+
+
Returns:
+

ds_interpolated – The provided dataset (ds), where NaN values have been +interpolated over using the parameters provided.

+
+
Return type:
+

xr.Dataset

+
+
+
+ +
+ + +
+ + + + + + + +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/api/movement.filtering.report_nan_values.html b/api/movement.filtering.report_nan_values.html new file mode 100644 index 00000000..90378997 --- /dev/null +++ b/api/movement.filtering.report_nan_values.html @@ -0,0 +1,602 @@ + + + + + + + + + + + movement.filtering.report_nan_values — movement + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + +
+ + + + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +
+

movement.filtering.report_nan_values#

+
+
+movement.filtering.report_nan_values(ds, ds_label='dataset')[source]#
+

Report the number and percentage of points that are NaN for each individual +and each keypoint in the provided dataset.

+
+
Parameters:
+
    +
  • ds (xarray.Dataset) – Dataset containing pose tracks, confidence scores, and metadata.

  • +
  • ds_label (str) – Label to identify the dataset in the report. Default is “dataset”.

  • +
+
+
+
+ +
+ + +
+ + + + + + + +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/api/movement.io.load_poses.from_dlc_df.html b/api/movement.io.load_poses.from_dlc_df.html index ad4df909..478069be 100644 --- a/api/movement.io.load_poses.from_dlc_df.html +++ b/api/movement.io.load_poses.from_dlc_df.html @@ -18,15 +18,15 @@ - - - + + + - - - - + + + + @@ -37,11 +37,11 @@ - - - + + + - + @@ -62,10 +62,9 @@ - +
- + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + +
+ + + + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +
+

movement.io.load_poses.from_file#

+
+
+movement.io.load_poses.from_file(file_path, source_software, fps=None)[source]#
+

Load pose tracking data from a DeepLabCut (DLC), LightningPose (LP) or +SLEAP output file into an xarray Dataset.

+
+
Parameters:
+
    +
  • file_path (pathlib.Path or str) – Path to the file containing predicted poses. The file format must +be among those supported by the from_dlc_file(), +from_slp_file() or from_lp_file() functions, +since one of these functions will be called internally, based on +the value of source_software.

  • +
  • source_software ("DeepLabCut", "SLEAP" or "LightningPose") – The source software of the file.

  • +
  • fps (float, optional) – The number of frames per second in the video. If None (default), +the time coordinates will be in frame numbers.

  • +
+
+
Returns:
+

Dataset containing the pose tracks, confidence scores, and metadata.

+
+
Return type:
+

xarray.Dataset

+
+
+ +
+ +
+ + +
+ + + + + + + +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/api/movement.io.load_poses.from_lp_file.html b/api/movement.io.load_poses.from_lp_file.html index e4b482ae..70fc8daf 100644 --- a/api/movement.io.load_poses.from_lp_file.html +++ b/api/movement.io.load_poses.from_lp_file.html @@ -18,15 +18,15 @@ - - - + + + - - - - + + + + @@ -37,11 +37,11 @@ - - - + + + - + @@ -62,10 +62,9 @@ - +
- + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + +
+ + + + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +
+

movement.move_accessor.MoveAccessor#

+
+
+class movement.move_accessor.MoveAccessor(ds)[source]#
+

An accessor that extends an xarray Dataset by implementing +movement-specific properties and methods.

+
+
The xarray Dataset contains the following expected dimensions:
    +
  • time: the number of frames in the video

  • +
  • individuals: the number of individuals in the video

  • +
  • keypoints: the number of keypoints in the skeleton

  • +
  • space: the number of spatial dimensions, either 2 or 3

  • +
+
+
+

Appropriate coordinate labels are assigned to each dimension: +list of unique names (str) for individuals and keypoints, +[‘x’,’y’,(‘z’)] for space. The coordinates of the time dimension +are in seconds if fps is provided, otherwise they are in frame numbers.

+
+
The dataset contains two expected data variables (xarray DataArrays):
    +
  • pose_tracks: with shape (time, individuals, +keypoints, space)

  • +
  • confidence: with shape (time, individuals, keypoints)

  • +
+
+
+

When accessing a .move property (e.g. displacement, velocity, +acceleration) for the first time, the property is computed and stored +as a data variable with the same name in the dataset. The .move +accessor can be omitted in subsequent accesses, i.e. +ds.move.displacement and ds.displacement will return the same data +variable.

+
+
The dataset may also contain following attributes as metadata:
    +
  • fps: the number of frames per second in the video

  • +
  • time_unit: the unit of the time coordinates, frames or +seconds

  • +
  • source_software: the software from which the pose tracks were +loaded

  • +
  • source_file: the file from which the pose tracks were +loaded

  • +
+
+
+

Notes

+

Using an accessor is the recommended way to extend xarray objects. +See [1] for more details.

+

Methods/properties that are specific to this class can be accessed via +the .move accessor, e.g. ds.move.validate().

+

References

+ +
+
+__init__(ds)[source]#
+
+ +

Methods

+ + + + + + + + + +

__init__(ds)

validate()

Validate the PoseTracks dataset.

+

Attributes

+ + + + + + + + + + + + + + + + + + +

acceleration

Return the acceleration between consecutive positions of each keypoint for each individual across time.

dim_names

displacement

Return the displacement between consecutive positions of each keypoint for each individual across time.

var_names

velocity

Return the velocity between consecutive positions of each keypoint for each individual across time.

+
+ +
+ + +
+ + + + + + + +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/api/movement.datasets.fetch_pose_data_path.html b/api/movement.sample_data.fetch_sample_data.html similarity index 79% rename from api/movement.datasets.fetch_pose_data_path.html rename to api/movement.sample_data.fetch_sample_data.html index 2eaa907b..7f5d2d62 100644 --- a/api/movement.datasets.fetch_pose_data_path.html +++ b/api/movement.sample_data.fetch_sample_data.html @@ -8,7 +8,7 @@ - movement.datasets.fetch_pose_data_path — movement + movement.sample_data.fetch_sample_data — movement @@ -18,15 +18,15 @@ - - - + + + - - - - + + + + @@ -37,22 +37,22 @@ - - - + + + - + - - + + - - + + @@ -62,10 +62,9 @@ - +
- + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + +
+ + + + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ +
+

movement.sample_data.fetch_sample_data_path#

+
+
+movement.sample_data.fetch_sample_data_path(filename)[source]#
+

Download sample pose data from the movement data repository and return +its local filepath.

+

The data are downloaded to the user’s local machine the first time they are +used and are stored in a local cache directory. The function returns the +path to the downloaded file, not the contents of the file itself.

+
+
Parameters:
+

filename (str) – Name of the file to fetch.

+
+
Returns:
+

path – Path to the downloaded file.

+
+
Return type:
+

pathlib.Path

+
+
+
+ +
+ + +
+ + + + + + + +
+ + + +
+ + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/api/movement.datasets.list_pose_data.html b/api/movement.sample_data.list_sample_data.html similarity index 80% rename from api/movement.datasets.list_pose_data.html rename to api/movement.sample_data.list_sample_data.html index 300b7313..1a5a7e72 100644 --- a/api/movement.datasets.list_pose_data.html +++ b/api/movement.sample_data.list_sample_data.html @@ -8,7 +8,7 @@ - movement.datasets.list_pose_data — movement + movement.sample_data.list_sample_data — movement @@ -18,15 +18,15 @@ - - - + + + - - - - + + + + @@ -37,21 +37,21 @@ - - - + + + - + - - + + - + @@ -62,10 +62,9 @@ - +
- + + + + + + + + +
+
+
+
+
+ + + + +
+
+ + + +
+ + + + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + + + +
+ +
+ + +
+
+ + + + + +
+ + +
+

Filtering and interpolation#

+

Filter out points with low confidence scores and interpolate over +missing values.

+
+

Imports#

+
from movement import sample_data
+from movement.filtering import filter_by_confidence, interpolate_over_time
+
+
+
+
+

Load a sample dataset#

+
ds = sample_data.fetch_sample_data("DLC_single-wasp.predictions.h5")
+print(ds)
+
+
+
<xarray.Dataset> Size: 61kB
+Dimensions:      (time: 1085, individuals: 1, keypoints: 2, space: 2)
+Coordinates:
+  * time         (time) float64 9kB 0.0 0.025 0.05 0.075 ... 27.05 27.07 27.1
+  * individuals  (individuals) <U12 48B 'individual_0'
+  * keypoints    (keypoints) <U7 56B 'head' 'stinger'
+  * space        (space) <U1 8B 'x' 'y'
+Data variables:
+    pose_tracks  (time, individuals, keypoints, space) float64 35kB 1.086e+03...
+    confidence   (time, individuals, keypoints) float64 17kB 0.05305 ... 0.0
+Attributes:
+    fps:              40.0
+    time_unit:        seconds
+    source_software:  DeepLabCut
+    source_file:      /home/runner/.movement/data/poses/DLC_single-wasp.predi...
+
+
+

We can see that this dataset contains the 2D pose tracks and confidence +scores for a single wasp, generated with DeepLabCut. There are 2 keypoints: +“head” and “stinger”.

+
+
+

Visualise the pose tracks#

+
pose_tracks = ds.pose_tracks.sel(individuals="individual_0")
+pose_tracks.plot.line(
+    x="time", row="keypoints", hue="space", aspect=2, size=2.5
+)
+
+
+keypoints = head, keypoints = stinger
<xarray.plot.facetgrid.FacetGrid object at 0x7f7af4446050>
+
+
+

We can see that the pose tracks contain some implausible “jumps”, such +as the the big shift in the final second, and the “spikes” of the stinger +near the 14th second. Perhaps we can get rid of those based on the model’s +reported confidence scores?

+
+
+

Visualise confidence scores#

+

The confidence scores are stored in the confidence data variable. +Since the predicted poses in this example have been generated by DeepLabCut, +the confidence scores should be likelihood values between 0 and 1. +That said, confidence scores are not standardised across pose +estimation frameworks, and their ranges can vary. Therefore, +it’s always a good idea to inspect the actual confidence values in the data.

+

Let’s first look at a histogram of the confidence scores.

+
ds.confidence.plot.hist(bins=20)
+
+
+individuals = ['individual_0']
(array([  61.,   13.,   16.,   10.,   10.,    8.,   21.,   11.,   14.,
+         11.,   26.,   13.,   28.,   19.,   39.,   44.,   79.,   84.,
+        149., 1514.]), array([0.        , 0.04999823, 0.09999646, 0.14999469, 0.19999292,
+       0.24999115, 0.29998938, 0.34998761, 0.39998584, 0.44998407,
+       0.4999823 , 0.54998053, 0.59997876, 0.64997699, 0.69997522,
+       0.74997345, 0.79997168, 0.84996991, 0.89996814, 0.94996637,
+       0.99996459]), <BarContainer object of 20 artists>)
+
+
+

Based on the above histogram, we can confirm that the confidence scores +indeed range between 0 and 1, with most values closer to 1. Now let’s see how +they evolve over time.

+
confidence = ds.confidence.sel(individuals="individual_0")
+confidence.plot.line(x="time", row="keypoints", aspect=2, size=2.5)
+
+
+keypoints = head, keypoints = stinger
<xarray.plot.facetgrid.FacetGrid object at 0x7f7af67d6450>
+
+
+

Encouragingly, some of the drops in confidence scores do seem to correspond +to the implausible jumps and spikes we had seen in the pose tracks. +We can use that to our advantage.

+
+
+

Filter out points with low confidence#

+

We can filter out points with confidence scores below a certain threshold. +Here, we use threshold=0.6. Points in the pose tracks data variable +with confidence scores below this threshold will be converted to NaN. +The print_report argument, which is True by default, reports the number +of NaN values in the dataset before and after the filtering operation.

+
ds_filtered = filter_by_confidence(ds, threshold=0.6, print_report=True)
+
+
+
Missing points (marked as NaN) in input dataset:
+        Individual: individual_0
+                head: 0/1085 (0.0%)
+                stinger: 0/1085 (0.0%)
+
+Missing points (marked as NaN) in filtered dataset:
+        Individual: individual_0
+                head: 121/1085 (11.2%)
+                stinger: 93/1085 (8.6%)
+
+
+

We can see that the filtering operation has introduced NaN values in the +pose_tracks data variable. Let’s visualise the filtered pose tracks.

+
pose_tracks_filtered = ds_filtered.pose_tracks.sel(individuals="individual_0")
+pose_tracks_filtered.plot.line(
+    x="time", row="keypoints", hue="space", aspect=2, size=2.5
+)
+
+
+keypoints = head, keypoints = stinger
<xarray.plot.facetgrid.FacetGrid object at 0x7f7af66e9990>
+
+
+

Here we can see that gaps have appeared in the pose tracks, some of which +are over the implausible jumps and spikes we had seen earlier. Moreover, +most gaps seem to be brief, lasting < 1 second.

+
+
+

Interpolate over missing values#

+

We can interpolate over the gaps we’ve introduced in the pose tracks. +Here we use the default linear interpolation method and max_gap=1, +meaning that we will only interpolate over gaps of 1 second or shorter. +Setting max_gap=None would interpolate over all gaps, regardless of +their length, which should be used with caution as it can introduce +spurious data. The print_report argument acts as described above.

+
ds_interpolated = interpolate_over_time(
+    ds_filtered, method="linear", max_gap=1, print_report=True
+)
+
+
+
Missing points (marked as NaN) in input dataset:
+        Individual: individual_0
+                head: 121/1085 (11.2%)
+                stinger: 93/1085 (8.6%)
+
+Missing points (marked as NaN) in interpolated dataset:
+        Individual: individual_0
+                head: 0/1085 (0.0%)
+                stinger: 0/1085 (0.0%)
+
+
+

We see that all NaN values have disappeared, meaning that all gaps were +indeed shorter than 1 second. Let’s visualise the interpolated pose tracks

+
pose_tracks_interpolated = ds_interpolated.pose_tracks.sel(
+    individuals="individual_0"
+)
+pose_tracks_interpolated.plot.line(
+    x="time", row="keypoints", hue="space", aspect=2, size=2.5
+)
+
+
+keypoints = head, keypoints = stinger
<xarray.plot.facetgrid.FacetGrid object at 0x7f7aea168e10>
+
+
+
+
+

Log of processing steps#

+

So, far we’ve processed the pose tracks first by filtering out points with +low confidence scores, and then by interpolating over missing values. +The order of these operations and the parameters with which they were +performed are saved in the log attribute of the dataset. +This is useful for keeping track of the processing steps that have been +applied to the data.

+
for log_entry in ds_interpolated.log:
+    print(log_entry)
+
+
+
{'operation': 'filter_by_confidence', 'datetime': '2024-03-12 15:17:14.490904', 'threshold': 0.6, 'print_report': True}
+{'operation': 'interpolate_over_time', 'datetime': '2024-03-12 15:17:16.005184', 'method': 'linear', 'max_gap': 1, 'print_report': True}
+
+
+

Total running time of the script: (0 minutes 3.400 seconds)

+ +

Gallery generated by Sphinx-Gallery

+
+
+ + +
+ + + + + + + +
+ + + + + + +
+
+ +
+ +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/examples/index.html b/examples/index.html index 55780b1d..ae4fe28f 100644 --- a/examples/index.html +++ b/examples/index.html @@ -18,15 +18,15 @@ - - - + + + - - - - + + + + @@ -37,11 +37,11 @@ - - - + + + - + @@ -62,10 +62,9 @@ - +
-