Skip to content

Commit

Permalink
dropped accessor use in examples
Browse files Browse the repository at this point in the history
  • Loading branch information
niksirbi committed Oct 16, 2024
1 parent 2e52ca1 commit 94347e3
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 142 deletions.
33 changes: 13 additions & 20 deletions examples/compute_kinematics.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,25 +119,18 @@
# ---------------------
# 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()
# For this, we can use the ``compute_displacement()`` function from
# the :mod:`movement.analysis.kinematics` module:

# %%
# 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)

# %%
# This 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 +264,13 @@
# ----------------
# 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 ``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 @@ -351,7 +344,7 @@
# Compute acceleration
# ---------------------
# We can compute the acceleration of the data with an equivalent method:
accel = ds.move.compute_acceleration()
accel = kin.compute_acceleration(position)

# %%
# and plot of the components of the acceleration vector ``ax``, ``ay`` per
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)
82 changes: 29 additions & 53 deletions examples/smooth.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
from scipy.signal import welch

from movement import sample_data
from movement.filtering import (
interpolate_over_time,
median_filter,
savgol_filter,
)

# %%
# Load a sample dataset
Expand All @@ -33,8 +38,8 @@
# %%
# Define a plotting function
# --------------------------
# Let's define a plotting function to help us visualise the effects smoothing
# both in the time and frequency domains.
# Let's define a plotting function to help us visualise the effects of
# smoothing both in the time and frequency domains.
# The function takes as inputs two datasets containing raw and smooth data
# respectively, and plots the position time series and power spectral density
# (PSD) for a given individual and keypoint. The function also allows you to
Expand Down Expand Up @@ -77,9 +82,8 @@ def plot_raw_and_smooth_timeseries_and_psd(
)

# interpolate data to remove NaNs in the PSD calculation
pos_interp = ds.sel(**selection).move.interpolate_over_time(
print_report=False
)
pos_interp = interpolate_over_time(pos, print_report=False)

# compute and plot the PSD
freq, psd = welch(pos_interp, fs=ds.fps, nperseg=256)
ax[1].semilogy(
Expand Down Expand Up @@ -108,36 +112,17 @@ def plot_raw_and_smooth_timeseries_and_psd(
# %%
# Smoothing with a median filter
# ------------------------------
# Using the
# :meth:`median_filter()\
# <movement.move_accessor.MovementDataset.filtering_wrapper>`
# method of the ``move`` accessor,
# we apply a rolling window median filter over a 0.1-second window
# (4 frames) to the wasp dataset.
# Using the :func:`movement.filtering.median_filter` function on the
# ``position`` data variable, we can apply a rolling window median filter
# over a 0.1-second window (4 frames) to the wasp dataset.
# As the ``window`` parameter is defined in *number of observations*,
# we can simply multiply the desired time window by the frame rate
# of the video. We will also create a copy of the dataset to avoid
# modifying the original data.

window = int(0.1 * ds_wasp.fps)
ds_wasp_smooth = ds_wasp.copy()
ds_wasp_smooth.update({"position": ds_wasp_smooth.move.median_filter(window)})

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

# %%
# We see from the printed report that the dataset has no missing values
Expand Down Expand Up @@ -181,9 +166,7 @@ def plot_raw_and_smooth_timeseries_and_psd(

window = int(0.1 * ds_mouse.fps)
ds_mouse_smooth = ds_mouse.copy()
ds_mouse_smooth.update(
{"position": ds_mouse_smooth.move.median_filter(window)}
)
ds_mouse_smooth.update({"position": median_filter(ds_mouse.position, window)})

# %%
# The report informs us that the raw data contains NaN values, most of which
Expand All @@ -199,7 +182,7 @@ def plot_raw_and_smooth_timeseries_and_psd(
# window are sufficient for the median to be calculated. Let's try this.

ds_mouse_smooth.update(
{"position": ds_mouse.move.median_filter(window, min_periods=2)}
{"position": median_filter(ds_mouse.position, window, min_periods=2)}
)

# %%
Expand All @@ -222,7 +205,7 @@ def plot_raw_and_smooth_timeseries_and_psd(

window = int(2 * ds_mouse.fps)
ds_mouse_smooth.update(
{"position": ds_mouse.move.median_filter(window, min_periods=2)}
{"position": median_filter(ds_mouse.position, window, min_periods=2)}
)

# %%
Expand All @@ -248,13 +231,9 @@ def plot_raw_and_smooth_timeseries_and_psd(
# %%
# Smoothing with a Savitzky-Golay filter
# --------------------------------------
# Here we use the
# :meth:`savgol_filter()\
# <movement.move_accessor.MovementDataset.filtering_wrapper>`
# method of the ``move`` accessor, which is a convenience method that applies
# :func:`movement.filtering.savgol_filter`
# (a wrapper around :func:`scipy.signal.savgol_filter`),
# to the ``position`` data variable.
# Here we apply the :func:`movement.filtering.savgol_filter` function
# (a wrapper around :func:`scipy.signal.savgol_filter`), to the ``position``
# data variable.
# The Savitzky-Golay filter is a polynomial smoothing filter that can be
# applied to time series data on a rolling window basis.
# A polynomial with a degree specified by ``polyorder`` is applied to each
Expand All @@ -268,7 +247,7 @@ def plot_raw_and_smooth_timeseries_and_psd(
# to be used as the ``window`` size.

window = int(0.2 * ds_mouse.fps)
ds_mouse_smooth.update({"position": ds_mouse.move.savgol_filter(window)})
ds_mouse_smooth.update({"position": savgol_filter(ds_mouse.position, window)})

# %%
# We see that the number of NaN values has increased after filtering. This is
Expand All @@ -289,7 +268,7 @@ def plot_raw_and_smooth_timeseries_and_psd(
# Now let's apply the same Savitzky-Golay filter to the wasp dataset.

window = int(0.2 * ds_wasp.fps)
ds_wasp_smooth.update({"position": ds_wasp.move.savgol_filter(window)})
ds_wasp_smooth.update({"position": savgol_filter(ds_wasp.position, window)})

# %%
plot_raw_and_smooth_timeseries_and_psd(
Expand All @@ -315,27 +294,24 @@ def plot_raw_and_smooth_timeseries_and_psd(
# with a larger ``window`` to further smooth the data.
# Between the two filters, we can interpolate over small gaps to avoid the
# excessive proliferation of NaN values. Let's try this on the mouse dataset.
# First, we will apply the median filter.

# First, we will apply the median filter.
window = int(0.1 * ds_mouse.fps)
ds_mouse_smooth.update(
{"position": ds_mouse.move.median_filter(window, min_periods=2)}
{"position": median_filter(ds_mouse.position, window, min_periods=2)}
)

# %%
# Next, let's linearly interpolate over gaps smaller than 1 second (30 frames).

# Next, let's linearly interpolate over gaps smaller
# than 1 second (30 frames).
ds_mouse_smooth.update(
{"position": ds_mouse_smooth.move.interpolate_over_time(max_gap=30)}
{"position": interpolate_over_time(ds_mouse_smooth.position, max_gap=30)}
)

# %%
# Finally, let's apply the Savitzky-Golay filter over a 0.4-second window
# (12 frames).

# Finally, let's apply the Savitzky-Golay filter
# over a 0.4-second window (12 frames).
window = int(0.4 * ds_mouse.fps)
ds_mouse_smooth.update(
{"position": ds_mouse_smooth.move.savgol_filter(window)}
{"position": savgol_filter(ds_mouse_smooth.position, window)}
)

# %%
Expand Down

0 comments on commit 94347e3

Please sign in to comment.