Skip to content

Commit f7f3b48

Browse files
niksirbilochhh
andauthored
Drop the .move accessor (#322)
* replace accessor with MovementDataset dataclass * moved pre-save validations inside save_poses module * deleted accessor code and associated tests * define dataset structure in modular classes * updated stale docstring for _validate_dataset() * remove mentions of the accessor from the getting started guide * dropped accessor use in examples * ignore linkcheck for opensource licenses * Revert "ignore linkcheck for opensource licenses" This reverts commit c8f3498. * use ds.sizes instead of ds.dims to suppress warning * Add references * remove movement_dataset.py module --------- Co-authored-by: lochhh <[email protected]>
1 parent 6c32608 commit f7f3b48

File tree

18 files changed

+168
-690
lines changed

18 files changed

+168
-690
lines changed

docs/source/getting_started/movement_dataset.md

Lines changed: 24 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ For example, you can:
195195
[data aggregation and broadcasting](xarray:user-guide/computation.html), and
196196
- use `xarray`'s built-in [plotting methods](xarray:user-guide/plotting.html).
197197

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

201201
```python
@@ -223,55 +223,45 @@ position = ds.position.sel(
223223
) # the output is a data array
224224
```
225225

226-
### Accessing movement-specific functionality
226+
### Modifying movement datasets
227227

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

232-
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:
231+
Let's imagine we want to compute the instantaneous velocity of all tracked
232+
points and store the results within the same dataset, for convenience.
233233

234234
```python
235-
velocity = ds.move.compute_velocity()
236-
acceleration = ds.move.compute_acceleration()
237-
```
235+
from movement.analysis.kinematics import compute_velocity
238236

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

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

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

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

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

258-
# we can now access these using dot notation on the dataset
259-
ds.velocity
260-
ds.acceleration
256+
```python
257+
ds.update({"position": position_filtered, "velocity": velocity_filtered})
261258
```
262259

263-
Custom **attributes** can also be added to the dataset:
260+
Custom **attributes** can be added to the dataset with:
264261

265262
```python
266263
ds.attrs["my_custom_attribute"] = "my_custom_value"
267264

268265
# we can now access this value using dot notation on the dataset
269266
ds.my_custom_attribute
270267
```
271-
272-
We can also update existing **data variables** in-place, using the `update()` method. For example, if we wanted to update the `position`
273-
and `velocity` arrays in our dataset, we could do:
274-
275-
```python
276-
ds.update({"position": position_filtered, "velocity": velocity_filtered})
277-
```

examples/compute_kinematics.py

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -117,27 +117,22 @@
117117
# %%
118118
# Compute displacement
119119
# ---------------------
120+
# The :mod:`movement.analysis.kinematics` module provides functions to compute
121+
# various kinematic quantities,
122+
# such as displacement, velocity, and acceleration.
120123
# We can start off by computing the distance travelled by the mice along
121-
# their trajectories.
122-
# For this, we can use the ``compute_displacement`` method of the
123-
# ``move`` accessor.
124-
displacement = ds.move.compute_displacement()
124+
# their trajectories:
125125

126-
# %%
127-
# This method will return a data array equivalent to the ``position`` one,
128-
# but holding displacement data along the ``space`` axis, rather than
129-
# position data.
130-
131-
# %%
132-
# Notice that we could also compute the displacement (and all the other
133-
# kinematic variables) using the :mod:`movement.analysis.kinematics` module:
134-
135-
# %%
136126
import movement.analysis.kinematics as kin
137127

138-
displacement_kin = kin.compute_displacement(position)
128+
displacement = kin.compute_displacement(position)
139129

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

276271
# %%
277-
# The ``velocity`` method will return a data array equivalent to the
278-
# ``position`` one, but holding velocity data along the ``space`` axis, rather
279-
# than position data. Notice how ``xarray`` nicely deals with the different
280-
# individuals and spatial dimensions for us! ✨
272+
# The :func:`movement.analysis.kinematics.compute_velocity`
273+
# function will return a data array equivalent to
274+
# the ``position`` one, but holding velocity data along the ``space`` axis,
275+
# rather than position data. Notice how ``xarray`` nicely deals with the
276+
# different individuals and spatial dimensions for us! ✨
281277

282278
# %%
283279
# We can plot the components of the velocity vector against time
@@ -350,8 +346,9 @@
350346
# %%
351347
# Compute acceleration
352348
# ---------------------
353-
# We can compute the acceleration of the data with an equivalent method:
354-
accel = ds.move.compute_acceleration()
349+
# Let's now compute the acceleration for all individuals in our data
350+
# array:
351+
accel = kin.compute_acceleration(position)
355352

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

377374
# %%
378-
# The can also represent the magnitude (norm) of the acceleration vector
379-
# for each individual:
375+
# We can also compute and visualise the magnitude (norm) of the
376+
# acceleration vector for each individual:
380377
fig, axes = plt.subplots(3, 1, sharex=True, sharey=True)
381378
for mouse_name, ax in zip(accel.individuals.values, axes, strict=False):
382379
# compute magnitude of the acceleration vector for one mouse

examples/filter_and_interpolate.py

Lines changed: 33 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
# Imports
1010
# -------
1111
from movement import sample_data
12+
from movement.analysis.kinematics import compute_velocity
13+
from movement.filtering import filter_by_confidence, interpolate_over_time
1214

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

88-
ds.update({"position": ds.move.filter_by_confidence()})
89-
90-
# %%
91-
# .. note::
92-
# The ``move`` accessor :meth:`filter_by_confidence()\
93-
# <movement.move_accessor.MovementDataset.filtering_wrapper>`
94-
# method is a convenience method that applies
95-
# :func:`movement.filtering.filter_by_confidence`,
96-
# which takes ``position`` and ``confidence`` as arguments.
97-
# The equivalent function call using the
98-
# :mod:`movement.filtering` module would be:
99-
#
100-
# .. code-block:: python
101-
#
102-
# from movement.filtering import filter_by_confidence
103-
#
104-
# ds.update({"position": filter_by_confidence(position, confidence)})
90+
ds.update({"position": filter_by_confidence(ds.position, ds.confidence)})
10591

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

134-
ds.update({"position": ds.move.interpolate_over_time(max_gap=40)})
135-
136-
# %%
137-
# .. note::
138-
# The ``move`` accessor :meth:`interpolate_over_time()\
139-
# <movement.move_accessor.MovementDataset.filtering_wrapper>`
140-
# is also a convenience method that applies
141-
# :func:`movement.filtering.interpolate_over_time`
142-
# to the ``position`` data variable.
143-
# The equivalent function call using the
144-
# :mod:`movement.filtering` module would be:
145-
#
146-
# .. code-block:: python
147-
#
148-
# from movement.filtering import interpolate_over_time
149-
#
150-
# ds.update({"position": interpolate_over_time(
151-
# position_filtered, max_gap=40
152-
# )})
118+
ds.update({"position": interpolate_over_time(ds.position, max_gap=40)})
153119

154120
# %%
155121
# We see that all NaN values have disappeared, meaning that all gaps were
@@ -176,27 +142,25 @@
176142
# %%
177143
# Filtering multiple data variables
178144
# ---------------------------------
179-
# All :mod:`movement.filtering` functions are available via the
180-
# ``move`` accessor. These ``move`` accessor methods operate on the
181-
# ``position`` data variable in the dataset ``ds`` by default.
182-
# There is also an additional argument ``data_vars`` that allows us to
183-
# specify which data variables in ``ds`` to filter.
184-
# When multiple data variable names are specified in ``data_vars``,
185-
# the method will return a dictionary with the data variable names as keys
186-
# and the filtered DataArrays as values, otherwise it will return a single
187-
# DataArray that is the filtered data.
188-
# This is useful when we want to apply the same filtering operation to
145+
# We can also apply the same filtering operation to
189146
# multiple data variables in ``ds`` at the same time.
190147
#
191148
# For instance, to filter both ``position`` and ``velocity`` data variables
192-
# in ``ds``, based on the confidence scores, we can specify
193-
# ``data_vars=["position", "velocity"]`` in the method call.
194-
# As the filtered data variables are returned as a dictionary, we can once
195-
# again use :meth:`xarray.Dataset.update` to update ``ds`` in-place
149+
# in ``ds``, based on the confidence scores, we can specify a dictionary
150+
# with the data variable names as keys and the corresponding filtered
151+
# DataArrays as values. Then we can once again use
152+
# :meth:`xarray.Dataset.update` to update ``ds`` in-place
196153
# with the filtered data variables.
197154

198-
ds["velocity"] = ds.move.compute_velocity()
199-
filtered_data_dict = ds.move.filter_by_confidence(
200-
data_vars=["position", "velocity"]
201-
)
202-
ds.update(filtered_data_dict)
155+
# Add velocity data variable to the dataset
156+
ds["velocity"] = compute_velocity(ds.position)
157+
158+
# Create a dictionary mapping data variable names to filtered DataArrays
159+
# We disable report printing for brevity
160+
update_dict = {
161+
var: filter_by_confidence(ds[var], ds.confidence, print_report=False)
162+
for var in ["position", "velocity"]
163+
}
164+
165+
# Use the dictionary to update the dataset in-place
166+
ds.update(update_dict)

0 commit comments

Comments
 (0)