Skip to content

Tracking

py3r.behaviour.tracking.tracking.Tracking

Tracking(data: DataFrame, meta: Dict[str, Any], handle: str, tags: dict[str, str] = None)

Represent frame-by-frame tracked keypoints with convenience loaders and tools.

A Tracking holds a pandas DataFrame of columns like p1.x, p1.y, p1.z, p1.likelihood with index named frame. Most users create objects via factory methods and then call instance methods to process or analyze trajectories.

Quick start with realistic CSVs stored in the package data:

  • Load from DLC CSV
  • Load from DLC multi-animal CSV
  • Load from YOLO3R CSV
  • Inspect points, distances
  • Filter, interpolate, smooth
  • Rescale by known distance, trim, check time
  • Save and slice (loc / iloc)
  • Minimal plotting

Examples: Minimal DLC example:

>>> from py3r.behaviour.util.docdata import data_path
>>> with data_path('py3r.behaviour.tracking._data', 'dlc_single.csv') as p:
...     t = Tracking.from_dlc(str(p), handle='ex', fps=30)
>>> len(t.data), t.meta['fps'], t.handle
(5, 30.0, 'ex')
>>> t.data[['p1.x','p1.y','p1.z','p1.likelihood']].head(2).reset_index().values.tolist()
[[0.0, 0.0, 0.0, 0.0, 1.0], [1.0, 1.0, 2.0, 3.0, 0.75]]

Load from DLC multi-animal (DLCMA):

>>> with data_path('py3r.behaviour.tracking._data', 'dlcma_multi.csv') as p_ma:
...     tma = Tracking.from_dlcma(str(p_ma), handle='ma', fps=30)
>>> tma.meta['fps'], tma.handle
(30.0, 'ma')

Load from YOLO3R (3D columns present):

>>> with data_path('py3r.behaviour.tracking._data', 'yolo3r.csv') as p_y:
...     ty = Tracking.from_yolo3r(str(p_y), handle='y3r', fps=30)
>>> 'p1.z' in ty.data.columns and 'p1.likelihood' in ty.data.columns
True
>>> ty.data[['p1.x','p1.y','p1.z','p1.likelihood']].head(2).reset_index().values.tolist()
[[0.0, 0.0, 0.0, 0.0, 1.0], [1.0, 1.0, 2.0, 3.0, 0.9]]

Inspect points and distances:

>>> names = t.get_point_names()
>>> sorted(names)[:3]
['p1', 'p2', 'p3']
>>> d = t.distance_between('p1', 'p2')
>>> len(d) == len(t.data)
True

Filter low-likelihood positions and interpolate:

>>> with data_path('py3r.behaviour.tracking._data', 'dlc_single.csv') as p:
...     t2 = Tracking.from_dlc(str(p), handle='ex2', fps=30)
>>> _ = t2.filter_likelihood(0.2)
>>> import numpy as np
>>> bool(np.isnan(t2.data['p1.x']).any())
True
>>> _ = t2.interpolate(method='nearest', limit=1)
>>> t2.data.columns.str.endswith('.likelihood').any() and t2.meta['interpolation']['method'] == 'nearest'
True

Smooth all points with default window=3 rolling mean, and optional exception for point 'p1':

>>> _ = t.smooth_all(3, 'mean',[(['p1'],'median',4)])
>>> 'smoothing' in t.meta
True

Rescale by known distance between two points (uniform across dims):

>>> _ = t.rescale_by_known_distance('p1', 'p2', distance_in_metres=2.0)
>>> t.meta['distance_units']
'm'

Trim frames and verify time window:

>>> with data_path('py3r.behaviour.tracking._data', 'dlc_single.csv') as p:
...     t3 = Tracking.from_dlc(str(p), handle='ex3', fps=30)
>>> _ = t3.trim(startframe=2, endframe=4)
>>> bool(t3.data.index[0] == 2 and t3.data.index[-1] == 4)
True
>>> bool(t3.time_as_expected(mintime=0.0, maxtime=10.0))
True

Save to a directory (parquet backend) and load back:

>>> import os, tempfile
>>> with tempfile.TemporaryDirectory() as d:
...     _ = t.save(d, data_format='csv',overwrite=True)
...     t_loaded = Tracking.load(d)
>>> isinstance(t_loaded, Tracking) and len(t_loaded.data) == len(t.data)
True

Slice with loc/iloc and keep handle:

>>> with data_path('py3r.behaviour.tracking._data', 'dlc_single.csv') as p:
...     t4 = Tracking.from_dlc(str(p), handle='ex4', fps=30)
>>> t4s = t4.loc[0:3]
>>> isinstance(t4s, Tracking) and t4s.handle == 'ex4'
True
>>> t4s2 = t4.iloc[0:2]
>>> isinstance(t4s2, Tracking) and len(t4s2.data) == 2
True

Minimal plotting (no display):

>>> _ = t.plot(show=False)

Tagging and user metadata:

>>> t.add_tag('session', 'S1')
>>> t.tags['session']
'S1'
>>> t.add_usermeta({'group': 'G1'}, overwrite=True)
>>> t.meta['usermeta']['group']
'G1'

data instance-attribute

data: DataFrame = data

meta instance-attribute

meta: dict = meta

handle instance-attribute

handle: str = handle

tags instance-attribute

tags: dict[str, str] = tags if tags is not None else {}

loc property

loc

Return a new Tracking object with self.data sliced by np.loc

Examples:

>>> from py3r.behaviour.util.docdata import data_path
>>> with data_path('py3r.behaviour.tracking._data', 'dlc_single.csv') as p:
...     t = Tracking.from_dlc(str(p), handle='ex', fps=30)
>>> t.data.shape
(5, 12)
>>> t.loc[0:2,'p1.x'].data.shape
(3,)
>>> t.loc[0:2].handle
'ex'

iloc property

iloc

Return a new Tracking object with self.data sliced by np.iloc

Examples:

>>> from py3r.behaviour.util.docdata import data_path
>>> with data_path('py3r.behaviour.tracking._data', 'dlc_single.csv') as p:
...     t = Tracking.from_dlc(str(p), handle='ex', fps=30)
>>> t.data.shape
(5, 12)
>>> t.iloc[0:2,0].data.shape
(2,)
>>> t.iloc[0:2,0].handle
'ex'

from_dlc classmethod

from_dlc(filepath: str | Path, *, handle: str, fps: float, aspectratio_correction: float = 1.0, tags: dict[str, str] | None = None) -> Self

loads a Tracking object from a (single animal) deeplabcut tracking csv

Examples:

>>> from py3r.behaviour.util.docdata import data_path
>>> from py3r.behaviour.tracking.tracking import Tracking
>>> with data_path('py3r.behaviour.tracking._data', 'dlc_single.csv') as p:
...     t = Tracking.from_dlc(str(p), handle='ex', fps=30)
>>> len(t.data), t.meta['fps'], t.handle
(5, 30.0, 'ex')

from_dlcma classmethod

from_dlcma(filepath: str | Path, *, handle: str, fps: float, aspectratio_correction: float = 1.0, tags: dict[str, str] | None = None) -> Self

loads a Tracking object from a multi-animal deeplabcut tracking csv

Examples:

>>> from py3r.behaviour.util.docdata import data_path
>>> with data_path('py3r.behaviour.tracking._data', 'dlcma_multi.csv') as p:
...     t = Tracking.from_dlcma(str(p), handle='ma', fps=30)
>>> len(t.data), t.meta['fps'], t.handle
(4, 30.0, 'ma')

from_yolo3r classmethod

from_yolo3r(filepath: str | Path, *, handle: str, fps: float, aspectratio_correction: float = 1.0, tags: dict[str, str] | None = None) -> Self

loads a Tracking object from a single- or multi-animal yolo csv in 3R hub format

Examples:

>>> from py3r.behaviour.util.docdata import data_path
>>> with data_path('py3r.behaviour.tracking._data', 'yolo3r.csv') as p:
...     t = Tracking.from_yolo3r(str(p), handle='y3r', fps=30)
>>> 'p1.z' in t.data.columns and 'p1.likelihood' in t.data.columns
True

add_usermeta

add_usermeta(usermeta: dict, overwrite: bool = False) -> None

adds or updates user-defined metadata

Examples:

>>> from py3r.behaviour.util.docdata import data_path
>>> with data_path('py3r.behaviour.tracking._data', 'dlc_single.csv') as p:
...     t = Tracking.from_dlc(str(p), handle='ex', fps=30)
>>> t.add_usermeta({'group': 'G1'}, overwrite=True)
>>> t.meta['usermeta']['group']
'G1'

add_tag

add_tag(tagname: str, tagvalue: str, overwrite: bool = False) -> None

adds or updates a tag

Examples:

>>> from py3r.behaviour.util.docdata import data_path
>>> with data_path('py3r.behaviour.tracking._data', 'dlc_single.csv') as p:
...     t = Tracking.from_dlc(str(p), handle='ex', fps=30)
>>> t.add_tag('session', 'S1', overwrite=True)
>>> t.tags['session']
'S1'

save

save(dirpath: str, *, data_format: str = 'parquet', overwrite: bool = False) -> None

Save this Tracking into a self-describing directory for exact round-trip.

Examples:

>>> import tempfile, os
>>> from py3r.behaviour.util.docdata import data_path
>>> with data_path('py3r.behaviour.tracking._data', 'dlc_single.csv') as p:
...     t = Tracking.from_dlc(str(p), handle='ex', fps=30)
>>> with tempfile.TemporaryDirectory() as d:
...     t.save(d, data_format='csv', overwrite=True)
...     os.path.exists(os.path.join(d, 'manifest.json'))
True

load classmethod

load(dirpath: str) -> Self

Load a Tracking (or subclass) previously saved with save().

Examples:

>>> import tempfile
>>> from py3r.behaviour.util.docdata import data_path
>>> with data_path('py3r.behaviour.tracking._data', 'dlc_single.csv') as p:
...     t = Tracking.from_dlc(str(p), handle='ex', fps=30)
>>> with tempfile.TemporaryDirectory() as d:
...     t.save(d, data_format='csv', overwrite=True)
...     t2 = Tracking.load(d)
>>> isinstance(t2, Tracking) and len(t2.data) == len(t.data)
True

strip_column_names

strip_column_names() -> None

strips out all column name string apart from last two sections delimited by dots

Examples:

>>> from py3r.behaviour.util.docdata import data_path
>>> with data_path('py3r.behaviour.tracking._data', 'dlc_single.csv') as p:
...     t = Tracking.from_dlc(str(p), handle='ex', fps=30)
>>> before = list(t.data.columns)[:3]
>>> t.strip_column_names()
>>> after = list(t.data.columns)[:3]
>>> all(len(c.split('.')) == 2 for c in after)
True

time_as_expected

time_as_expected(mintime: float, maxtime: float) -> bool

checks that the total length of the tracking data is between mintime seconds and maxtime seconds

Examples:

>>> from py3r.behaviour.util.docdata import data_path
>>> with data_path('py3r.behaviour.tracking._data', 'dlc_single.csv') as p:
...     t = Tracking.from_dlc(str(p), handle='ex', fps=30)
>>> bool(t.time_as_expected(0.0, 1.0)) # between 0 and 1 second
True
>>> bool(t.time_as_expected(0.0, 0.1)) # less than 0.1 seconds
False

trim

trim(startframe: int | None = None, endframe: int | None = None) -> None

trims the tracking data object between startframe and endframe

Examples:

>>> from py3r.behaviour.util.docdata import data_path
>>> with data_path('py3r.behaviour.tracking._data', 'dlc_single.csv') as p:
...     t = Tracking.from_dlc(str(p), handle='ex', fps=30)
>>> _ = t.trim(1, 3)
>>> int(t.data.index[0]), int(t.data.index[-1])
(1, 3)

filter_likelihood

filter_likelihood(threshold: float) -> None

sets all tracking position values with likelihood less than threshold to np.nan

Examples:

>>> from py3r.behaviour.util.docdata import data_path
>>> import numpy as np
>>> with data_path('py3r.behaviour.tracking._data', 'dlc_single.csv') as p:
...     t = Tracking.from_dlc(str(p), handle='ex', fps=30)
>>> t.filter_likelihood(0.5)
>>> bool(np.isnan(t.data.filter(like='.x')).any().any())
True

distance_between

distance_between(point1: str, point2: str, dims=('x', 'y')) -> pd.Series

framewise distance between two points

Examples:

>>> from py3r.behaviour.util.docdata import data_path
>>> with data_path('py3r.behaviour.tracking._data', 'dlc_single.csv') as p:
...     t = Tracking.from_dlc(str(p), handle='ex', fps=30)
>>> d = t.distance_between('p1', 'p2')
>>> len(d) == len(t.data)
True

get_point_names

get_point_names() -> list

list of tracked point names

Examples:

>>> from py3r.behaviour.util.docdata import data_path
>>> with data_path('py3r.behaviour.tracking._data', 'dlc_single.csv') as p:
...     t = Tracking.from_dlc(str(p), handle='ex', fps=30)
>>> names = sorted(t.get_point_names())
>>> set(['p1','p2','p3']).issubset(names)
True

rescale_by_known_distance

rescale_by_known_distance(point1: str, point2: str, distance_in_metres: float, dims=('x', 'y')) -> None

rescale all dims by known distance between two points

Examples:

>>> from py3r.behaviour.util.docdata import data_path
>>> with data_path('py3r.behaviour.tracking._data', 'dlc_single.csv') as p:
...     t = Tracking.from_dlc(str(p), handle='ex', fps=30)
>>> t.rescale_by_known_distance('p1','p2', 2.0)
>>> t.meta['distance_units']
'm'

generate_smoothdict

generate_smoothdict(pointslists: list, windows: list, smoothtypes: list) -> dict

deprecated, use smooth_all instead

smooth

smooth(smoothing_params: dict) -> None

deprecated, use smooth_all instead

smooth_all

smooth_all(window: int | None = 3, method: str = 'mean', overrides: list[tuple[list[str] | tuple[str, ...] | str, str, int | None]] | None = None, dims: tuple[str, ...] = ('x', 'y'), strict: bool = False, inplace: bool = True, smoother=None, smoother_kwargs: dict | None = None) -> 'Tracking | None'

Smooth all tracked points using a default method/window, with optional override groups.

  • window/method: default applied to any point without override
  • overrides: optional list of (points, method, window) tuples, where
    • points: list/tuple of point names (or a single str)
    • method: 'median' or 'mean'
    • window: int (or None to skip smoothing for those points)
  • dims: coordinate dimensions to smooth
  • strict: require an effective window for every point
  • inplace: mutate or return a new object

Examples:

>>> from py3r.behaviour.util.docdata import data_path
>>> with data_path('py3r.behaviour.tracking._data', 'dlc_single.csv') as p:
...     t = Tracking.from_dlc(str(p), handle='ex', fps=30)
>>> t.smooth_all(3, 'mean', overrides=[(['p1'], 'median', 4)])
>>> 'smoothing' in t.meta
True

interpolate

interpolate(method: str = 'linear', limit: int = 1, **kwargs) -> None

interpolates missing data in the tracking data, and sets likelihood to np.nan uses pandas.DataFrame.interpolate() with kwargs

Examples:

>>> from py3r.behaviour.util.docdata import data_path
>>> import numpy as np
>>> with data_path('py3r.behaviour.tracking._data', 'dlc_single.csv') as p:
...     t = Tracking.from_dlc(str(p), handle='ex', fps=30)
>>> t.filter_likelihood(0.5)
>>> t.interpolate(method='linear', limit=1)
>>> 'interpolation' in t.meta
True

plot

plot(trajectories=None, static=None, lines=None, dims=('x', 'y'), ax=None, title=None, show=True, elev=30, azim=45)

Plot trajectories and static points for this Tracking object. Args: trajectories: list of point names or dict {point: color_series} static: list of point names to plot as static (median) lines: list of (point1, point2) pairs to join with a line dims: tuple of dimension names (default ('x','y'); use ('x','y','z') for 3D) ax: matplotlib axis (optional) title: plot title (default: self.handle) show: whether to call plt.show() Returns: fig, ax

Examples:

>>> from py3r.behaviour.util.docdata import data_path
>>> with data_path('py3r.behaviour.tracking._data', 'dlc_single.csv') as p:
...     t = Tracking.from_dlc(str(p), handle='ex', fps=30)
>>> _ = t.plot(show=False)

save_3d_tracking_video_multi_view

save_3d_tracking_video_multi_view(out_path: str, lines: list[tuple[str, str]] = None, point_size=40, line_width=2, point_color='b', line_color='k', dpi=150, writer='pillow', startframe=None, endframe=None, xlim=None, ylim=None, zlim=None, robust_percentile=1, invert_z=True)

Save a 3D animation of tracked points to a video file, with 4 subplots per frame: - azim=0, elev=0, ortho - azim=90, elev=0, ortho - azim=0, elev=90, ortho - azim=45, elev=30, persp Optionally, set axis limits manually or use robust percentiles to ignore outliers. Enforces equal aspect ratio for all axes.