movement Python Toolbox

repository·main·Indexed 43 days ago

https://github.com/neuroinformatics-unit/movement

A Python toolbox for analyzing animal body movements across space and time. It provides modular tools for cleaning, visualizing, and quantifying motion tracks, including support for Regions of Interest (RoIs), kinematics analysis, and multi-view pose tracking data loading.

Tokens
23.2K
Snippets
71
Records
103
Agent score
56%

What's inside movement

  1. Overview of movement

    main

    movement is a Python toolbox designed for analyzing animal body movements across space and time. It provides a consistent, modular interface for processing motion tracks generated by deep learning tracking tools like [DeepLabCut] and [SLEAP].

    Key capabilities include:

    • Data Cleaning: Standardized methods to process raw tracks.
    • Visualization: Tools to view animal movements.
    • Motion Quantification: Methods to extract scientific metrics from motion data.

    The library aims to support all popular animal tracking frameworks and file formats, bridging the gap between raw tracking output and scientific analysis.

  2. Overview of Input/Output in movement

    main

    The movement library is designed to be input-agnostic, facilitating data flows between various third-party motion tracking formats and its own native data structure (which is based on xarray).

    There are two primary ways to handle data:

    1. Supported third-party formats: Use these as "Import" and "Export/Save As" functions to work with data from popular motion tracking tools or established data specifications.
    2. Native netCDF saving and loading: This is the recommended method for preserving your analysis state. By leveraging xarray's built-in netCDF support, you can save and load datasets while preserving all variables and metadata, allowing workflows to resume exactly where they left off.
  3. Understand the structure of movement datasets

    main

    In movement, pose tracks and bounding box tracks are represented as xarray.Dataset objects. A movement dataset is a container for multiple xarray.DataArray objects (data variables) that share common dimensions and coordinates.

    There are two primary types of datasets:

    1. Poses datasets: Designed for keypoint tracking (e.g., body parts).
    2. Bounding boxes (bboxes) datasets: Designed for tracking object enclosures.

    You can inspect a dataset's structure by printing the object or, in a Jupyter notebook, by typing the variable name (e.g., ds) in a cell to see an interactive representation.

    from movement import sample_data
    
    # Inspect a poses dataset
    ds = sample_data.fetch_dataset("SLEAP_three-mice_Aeon_proofread.analysis.h5")
    print(ds)
    
    # Inspect a bounding boxes dataset
    ds = sample_data.fetch_dataset("VIA_multiple-crabs_5-frames_labels.csv")
    print(ds)
  4. Create and use Regions of Interest (RoIs)

    main

    The movement library supports spatial annotations via Regions of Interest (RoIs). You can define 1D segments or 2D polygonal regions using the following classes:

    • movement.roi.LineOfInterest: For one-dimensional regions (segments or piecewise-linear structures).
    • movement.roi.PolygonOfInterest: For two-dimensional polygonal regions.

    These classes are built on top of shapely. Once an RoI is defined, you can perform spatial queries against your movement data.

    from movement.roi import LineOfInterest, PolygonOfInterest
    
    # Example: Creating a 2D polygonal RoI
    roi = PolygonOfInterest([(0, 0), (10, 0), (10, 10), (0, 10)])
  5. Prepare netCDF files for the GUI

    main

    The GUI can only open netCDF files that satisfy specific movement dataset requirements.

    Requirements:

    • The ds_type attribute must be set to either poses or bboxes.
    • The data variables, dimensions, and coordinates must match the structure required for that ds_type. For example, a poses dataset must include both position and confidence variables.

    To ensure compatibility when processing data, it is recommended to create a copy of your original dataset before applying filters and then saving it using .to_netcdf().

    from movement.io import load_dataset
    from movement.filtering import rolling_filter
    
    # Load existing data
    ds_orig = load_dataset(
        "path/to/my_data.h5", source_software="DeepLabCut", fps=30
    )
    
    # Create a copy to avoid modifying the original dataset
    ds_new = ds_orig.copy()
    
    # Apply processing (e.g., rolling median filter)
    # Ensure the variable name (e.g., 'position') is preserved
    ds_new["position"] = rolling_filter(
        ds_orig["position"], window=5, statistic="median"
    )
    
    # Save as a GUI-compatible netCDF file
    ds_new.to_netcdf("my_data_processed.nc")
  6. Update the API reference

    main

    The API reference is auto-generated using docs/make_api.py and Sphinx extensions (autodoc and autosummary).

    • Excluding modules: To prevent a module from being documented, add it to the EXCLUDE_MODULES list in docs/make_api.py.
    • Documenting package modules: If a module exposes its public API via an __init__.py (a package module), add it to the PACKAGE_MODULES list in docs/make_api.py to ensure the script generates the appropriate .rst files in docs/source/api/.
  7. Manipulate movement datasets using xarray

    main

    Because a movement dataset is an xarray.Dataset, you can use the full xarray API for data manipulation.

    Key capabilities include:

    • Accessing data: Use dot notation to access data variables and attributes (e.g., ds.position, ds.fps).
    • Indexing and Selection: Use .sel() to select data by coordinate labels (e.g., ds.sel(individual=['ind1'])) or .isel() to select by integer index (e.g., ds.isel(time=slice(0, 50))).
    • Aggregation: Use dimension names for broadcasting and aggregation.
    • Plotting: Use built-in xarray plotting methods.
    • I/O: Save/load datasets using netCDF (recommended format) or export to Pandas DataFrames and NumPy arrays.

    Note: Selecting from a data variable (e.g., ds.position.sel(...)) returns an xarray.DataArray, whereas selecting from the dataset returns an xarray.Dataset.

    # Select by time slice
    ds_sel = ds.sel(time=slice(0, 100))
    
    # Select specific individuals or keypoints
    ds_sel = ds.sel(individual=["individual1", "individual2"])
    ds_sel = ds.sel(keypoint="snout")
    
    # Combine selections
    ds_sel = ds.sel(
        time=slice(0, 100),
        individual=["individual1", "individual2"],
        keypoint="snout",
    )
    
    # Selecting from a specific variable returns a DataArray
    position = ds.position.sel(
        individual="individual1", keypoint="snout"
    )
  8. Understand the Qt Model/View architecture in regions_widget

    main

    The movement.napari.regions_widget uses the Qt Model/View pattern to separate data from display. This involves three components:

    1. RegionsTableModel (subclass of QAbstractTableModel): Wraps a napari shapes layer. It exposes region names (layer.properties["name"]) and shape types to Qt, listening to napari layer events and emitting Qt signals.
    2. RegionsTableView (subclass of QTableView): Renders the model data and handles user interactions like row selection and inline editing. It keeps table selection in sync with napari shape selection.
    3. RegionsWidget: The orchestrator that connects the model and view, manages layer selection, and handles layer lifecycle events.

    Data Flow: napari shapes layer <-> RegionsTableModel <-> RegionsTableView <-> User

    Note on Bidirectional Sync: To prevent circular updates (e.g., selecting a shape selects a row, which re-triggers shape selection), use guard flags like _syncing_row_selection and _syncing_layer_selection to ignore events while a sync is in progress.

  9. Understand the scope and purpose of movement

    main

    movement is a Python-based suite of tools designed to facilitate the study of animal behaviour by analysing body movements across space and time.

    Core Functionality

    The library focuses on handling the position and/or orientation of one or more individuals over time. It is designed to operate downstream of animal tracking frameworks (like DeepLabCut or SLEAP) and provides functionalities for:

    • Data cleaning
    • Visualisation
    • Motion quantification
    • Feature generation (useful for action recognition tasks)

    Supported Data Representations

    movement provides a consistent interface for representing various types of motion tracks in both 2D (x, y) and 3D (x, y, z) formats:

    • Pose tracks: Trajectories of a set of keypoints (e.g., body parts, limbs, or appendages).
    • Bounding box tracks: Trajectories of bounding box centroids.
    • Motion tracks: A general term for trajectories representing an individual's movement.

    It can handle data for single or multiple individuals.

  10. Where to place napari layer callbacks

    main

    When developing the napari plugin, decide where to place callbacks based on their intended lifetime and purpose:

    1. In movement.napari.layer_wiring: Use this if the callback mutates layer or viewer state (e.g., layer data, properties, symbols, editable, or frame slider range). These callbacks must outlive the widget because the state they modify is part of the user's work.

      • Viewer lifetime: Use connect_viewer_callbacks(viewer) to react to viewer-level events (e.g., layer insertion/removal).
      • Layer lifetime: Connect directly to layer events (e.g., points_layer.events.data.connect(...)) so the callback lives and dies with the layer.
    2. As a widget method: Use this if the callback only refreshes the widget's own UI (e.g., updating a dropdown, table, or button). Once the widget is closed, there is no need for the callback to persist.

    # Example of layer lifetime connection in loader_widgets
    points_layer.events.data.connect(on_points_data_changed)
  11. Getting started with movement

    main

    To begin using the movement package, follow these steps in order:

    1. Install the package: Use conda or pip to set up your environment.
    2. Understand Movement Datasets: Before processing data, read the documentation on movement datasets, as they are the central data structure used throughout the package.
    3. Load Data: Either load your own tracking data or use the provided sample data to experiment with the API.
    4. Explore via GUI: Use the napari plugin to interactively view and explore your movement data.
  12. Configure formatting and pre-commit hooks

    main

    The project uses pre-commit to enforce a consistent formatting style. The hooks include ruff (linting/formatting), mypy (static typing), check-manifest (package integrity), and codespell (spell checking).

    To install the hooks, run pre-commit install. To run them manually:

    • For staged files: pre-commit run
    • For all files: pre-commit run -a

    If a hook automatically fixes issues, you must stage the changes and run the hooks again:

    git add .
    pre-commit run

    To ignore a specific ruff rule for a single line, use an inline comment like # noqa: <RULE_CODE> (e.g., # noqa: E501).

    pre-commit install
    pre-commit run
    pre-commit run -a