Skip to content
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

Drop the .move accessor #322

Merged
merged 12 commits into from
Oct 24, 2024
58 changes: 24 additions & 34 deletions docs/source/getting_started/movement_dataset.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ For example, you can:
[data aggregation and broadcasting](xarray:user-guide/computation.html), and
- use `xarray`'s built-in [plotting methods](xarray:user-guide/plotting.html).

As an example, here's how you can use the `sel` method to select subsets of
As an example, here's how you can use {meth}`xarray.Dataset.sel` to select subsets of
data:

```python
Expand Down Expand Up @@ -223,55 +223,45 @@ position = ds.position.sel(
) # the output is a data array
```

### Accessing movement-specific functionality
### Modifying movement datasets

`movement` extends `xarray`'s functionality with a number of convenience
methods that are specific to `movement` datasets. These `movement`-specific methods are accessed using the
`move` keyword.
Datasets can be modified by adding new **data variables** and **attributes**,
or updating existing ones.

For example, to compute the velocity and acceleration vectors for all individuals and keypoints across time, we provide the `move.compute_velocity` and `move.compute_acceleration` methods:
Let's imagine we want to compute the instantaneous velocity of all tracked
points and store the results within the same dataset, for convenience.

```python
velocity = ds.move.compute_velocity()
acceleration = ds.move.compute_acceleration()
```
from movement.analysis.kinematics import compute_velocity

The `movement`-specific functionalities are implemented in the
{class}`movement.move_accessor.MovementDataset` class, which is an [accessor](https://docs.xarray.dev/en/stable/internals/extending-xarray.html) to the
underlying {class}`xarray.Dataset` object. Defining a custom accessor is convenient
to avoid conflicts with `xarray`'s built-in methods.
# compute velocity from position
velocity = compute_velocity(ds.position)
# add it to the dataset as a new data variable
ds["velocity"] = velocity

### Modifying movement datasets
# we could have also done both steps in a single line
ds["velocity"] = compute_velocity(ds.position)

The `velocity` and `acceleration` produced in the above example are {class}`xarray.DataArray` objects, with the same **dimensions** as the
original `position` **data variable**.
# we can now access velocity like any other data variable
ds.velocity
```

In some cases, you may wish to
add these or other new **data variables** to the `movement` dataset for
convenience. This can be done by simply assigning them to the dataset
with an appropriate name:
The output of {func}`movement.analysis.kinematics.compute_velocity` is an {class}`xarray.DataArray` object,
with the same **dimensions** as the original `position` **data variable**,
so adding it to the existing `ds` makes sense and works seamlessly.

```python
ds["velocity"] = velocity
ds["acceleration"] = acceleration
We can also update existing **data variables** in-place, using {meth}`xarray.Dataset.update`. For example, if we wanted to update the `position`
and `velocity` arrays in our dataset, we could do:

# we can now access these using dot notation on the dataset
ds.velocity
ds.acceleration
```python
ds.update({"position": position_filtered, "velocity": velocity_filtered})
```

Custom **attributes** can also be added to the dataset:
Custom **attributes** can be added to the dataset with:

```python
ds.attrs["my_custom_attribute"] = "my_custom_value"

# we can now access this value using dot notation on the dataset
ds.my_custom_attribute
```

We can also update existing **data variables** in-place, using the `update()` method. For example, if we wanted to update the `position`
and `velocity` arrays in our dataset, we could do:

```python
ds.update({"position": position_filtered, "velocity": velocity_filtered})
```
45 changes: 21 additions & 24 deletions examples/compute_kinematics.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,27 +117,22 @@
# %%
# Compute displacement
# ---------------------
# The :mod:`movement.analysis.kinematics` module provides functions to compute
# various kinematic quantities,
# such as displacement, velocity, and acceleration.
# We can start off by computing the distance travelled by the mice along
# their trajectories.
# For this, we can use the ``compute_displacement`` method of the
# ``move`` accessor.
displacement = ds.move.compute_displacement()
# their trajectories:

# %%
# This method will return a data array equivalent to the ``position`` one,
# but holding displacement data along the ``space`` axis, rather than
# position data.

# %%
# Notice that we could also compute the displacement (and all the other
# kinematic variables) using the :mod:`movement.analysis.kinematics` module:

# %%
import movement.analysis.kinematics as kin

displacement_kin = kin.compute_displacement(position)
displacement = kin.compute_displacement(position)

# %%
# The :func:`movement.analysis.kinematics.compute_displacement`
# function will return a data array equivalent to the ``position`` one,
# but holding displacement data along the ``space`` axis, rather than
# position data.
#
# The ``displacement`` data array holds, for a given individual and keypoint
# at timestep ``t``, the vector that goes from its previous position at time
# ``t-1`` to its current position at time ``t``.
Expand Down Expand Up @@ -271,13 +266,14 @@
# ----------------
# We can easily compute the velocity vectors for all individuals in our data
# array:
velocity = ds.move.compute_velocity()
velocity = kin.compute_velocity(position)

# %%
# The ``velocity`` method will return a data array equivalent to the
# ``position`` one, but holding velocity data along the ``space`` axis, rather
# than position data. Notice how ``xarray`` nicely deals with the different
# individuals and spatial dimensions for us! ✨
# The :func:`movement.analysis.kinematics.compute_velocity`
# function will return a data array equivalent to
# the ``position`` one, but holding velocity data along the ``space`` axis,
# rather than position data. Notice how ``xarray`` nicely deals with the
# different individuals and spatial dimensions for us! ✨

# %%
# We can plot the components of the velocity vector against time
Expand Down Expand Up @@ -350,8 +346,9 @@
# %%
# Compute acceleration
# ---------------------
# We can compute the acceleration of the data with an equivalent method:
accel = ds.move.compute_acceleration()
# Let's now compute the acceleration for all individuals in our data
# array:
accel = kin.compute_acceleration(position)

# %%
# and plot of the components of the acceleration vector ``ax``, ``ay`` per
Expand All @@ -375,8 +372,8 @@
fig.tight_layout()

# %%
# The can also represent the magnitude (norm) of the acceleration vector
# for each individual:
# We can also compute and visualise the magnitude (norm) of the
# acceleration vector for each individual:
fig, axes = plt.subplots(3, 1, sharex=True, sharey=True)
for mouse_name, ax in zip(accel.individuals.values, axes, strict=False):
# compute magnitude of the acceleration vector for one mouse
Expand Down
102 changes: 33 additions & 69 deletions examples/filter_and_interpolate.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
# Imports
# -------
from movement import sample_data
from movement.analysis.kinematics import compute_velocity
from movement.filtering import filter_by_confidence, interpolate_over_time

# %%
# Load a sample dataset
Expand Down Expand Up @@ -73,35 +75,19 @@
# %%
# Filter out points with low confidence
# -------------------------------------
# Using the
# :meth:`filter_by_confidence()\
# <movement.move_accessor.MovementDataset.filtering_wrapper>`
# method of the ``move`` accessor,
# we can filter out points with confidence scores below a certain threshold.
# The default ``threshold=0.6`` will be used when ``threshold`` is not
# provided.
# This method will also report the number of NaN values in the dataset before
# and after the filtering operation by default (``print_report=True``).
# Using the :func:`movement.filtering.filter_by_confidence` function from the
# :mod:`movement.filtering` module, we can filter out points with confidence
# scores below a certain threshold. This function takes ``position`` and
# ``confidence`` as required arguments, and accepts an optional ``threshold``
# parameter, which defaults to ``threshold=0.6`` unless specified otherwise.
# The function will also report the number of NaN values in the dataset before
# and after the filtering operation by default, but you can disable this
# by passing ``print_report=False``.
#
# We will use :meth:`xarray.Dataset.update` to update ``ds`` in-place
# with the filtered ``position``.

ds.update({"position": ds.move.filter_by_confidence()})

# %%
# .. note::
# The ``move`` accessor :meth:`filter_by_confidence()\
# <movement.move_accessor.MovementDataset.filtering_wrapper>`
# method is a convenience method that applies
# :func:`movement.filtering.filter_by_confidence`,
# which takes ``position`` and ``confidence`` as arguments.
# The equivalent function call using the
# :mod:`movement.filtering` module would be:
#
# .. code-block:: python
#
# from movement.filtering import filter_by_confidence
#
# ds.update({"position": filter_by_confidence(position, confidence)})
ds.update({"position": filter_by_confidence(ds.position, ds.confidence)})

# %%
# We can see that the filtering operation has introduced NaN values in the
Expand All @@ -120,36 +106,16 @@
# %%
# Interpolate over missing values
# -------------------------------
# Using the
# :meth:`interpolate_over_time()\
# <movement.move_accessor.MovementDataset.filtering_wrapper>`
# method of the ``move`` accessor,
# we can interpolate over the gaps we've introduced in the pose tracks.
# Using the :func:`movement.filtering.interpolate_over_time` function from the
# :mod:`movement.filtering` module, we can interpolate over gaps
# we've introduced in the pose tracks.
# Here we use the default linear interpolation method (``method=linear``)
# and interpolate over gaps of 40 frames or less (``max_gap=40``).
# The default ``max_gap=None`` would interpolate over all gaps, regardless of
# their length, but this should be used with caution as it can introduce
# spurious data. The ``print_report`` argument acts as described above.

ds.update({"position": ds.move.interpolate_over_time(max_gap=40)})

# %%
# .. note::
# The ``move`` accessor :meth:`interpolate_over_time()\
# <movement.move_accessor.MovementDataset.filtering_wrapper>`
# is also a convenience method that applies
# :func:`movement.filtering.interpolate_over_time`
# to the ``position`` data variable.
# The equivalent function call using the
# :mod:`movement.filtering` module would be:
#
# .. code-block:: python
#
# from movement.filtering import interpolate_over_time
#
# ds.update({"position": interpolate_over_time(
# position_filtered, max_gap=40
# )})
ds.update({"position": interpolate_over_time(ds.position, max_gap=40)})

# %%
# We see that all NaN values have disappeared, meaning that all gaps were
Expand All @@ -176,27 +142,25 @@
# %%
# Filtering multiple data variables
# ---------------------------------
# All :mod:`movement.filtering` functions are available via the
# ``move`` accessor. These ``move`` accessor methods operate on the
# ``position`` data variable in the dataset ``ds`` by default.
# There is also an additional argument ``data_vars`` that allows us to
# specify which data variables in ``ds`` to filter.
# When multiple data variable names are specified in ``data_vars``,
# the method will return a dictionary with the data variable names as keys
# and the filtered DataArrays as values, otherwise it will return a single
# DataArray that is the filtered data.
# This is useful when we want to apply the same filtering operation to
# We can also apply the same filtering operation to
# multiple data variables in ``ds`` at the same time.
#
# For instance, to filter both ``position`` and ``velocity`` data variables
# in ``ds``, based on the confidence scores, we can specify
# ``data_vars=["position", "velocity"]`` in the method call.
# As the filtered data variables are returned as a dictionary, we can once
# again use :meth:`xarray.Dataset.update` to update ``ds`` in-place
# in ``ds``, based on the confidence scores, we can specify a dictionary
# with the data variable names as keys and the corresponding filtered
# DataArrays as values. Then we can once again use
# :meth:`xarray.Dataset.update` to update ``ds`` in-place
# with the filtered data variables.

ds["velocity"] = ds.move.compute_velocity()
filtered_data_dict = ds.move.filter_by_confidence(
data_vars=["position", "velocity"]
)
ds.update(filtered_data_dict)
# Add velocity data variable to the dataset
ds["velocity"] = compute_velocity(ds.position)

# Create a dictionary mapping data variable names to filtered DataArrays
# We disable report printing for brevity
update_dict = {
var: filter_by_confidence(ds[var], ds.confidence, print_report=False)
for var in ["position", "velocity"]
}

# Use the dictionary to update the dataset in-place
ds.update(update_dict)
Loading
Loading