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'
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_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.