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)
>>> has_lik = t2.data.columns.str.endswith('.likelihood').any()
>>> interp_ok = t2.meta['interpolation']['method'] == 'nearest'
>>> has_lik and interp_ok
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
copy ¶
copy() -> Self
Creates a copy of an existing tracking object
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)
>>> t_copy = t.copy()
>>> len(t_copy.data), t_copy.meta['fps'], t_copy.handle
(4, 30.0, 'ma')
concat
classmethod
¶
concat(
trackings: list[Self],
*,
handle: str | None = None,
reindex: Literal[
"rezero", "follow_previous", "keep_original"
] = "follow_previous",
) -> Self
Concatenate multiple Tracking objects along the time (frame) axis.
All Tracking objects must have: - Matching fps - Identical column names (same tracked points and dimensions)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
list[Tracking]
|
List of Tracking objects to concatenate, in temporal order. |
required |
|
str
|
Handle for the concatenated object. If None, uses first object's handle. |
None
|
|
('rezero', 'follow_previous', 'keep_original')
|
How to handle frame indices: - "rezero": Reindex all frames starting from 0 (0, 1, 2, ...). - "follow_previous": Each chunk continues from where the previous ended. If chunk 1 ends at frame n, chunk 2 starts at n+1. - "keep_original": Leave indices untouched; duplicates are allowed. |
"rezero"
|
Returns:
| Type | Description |
|---|---|
Tracking
|
A new Tracking object containing all frames from input objects. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If trackings is empty, fps values don't match, or columns differ. |
Examples:
Concatenate two tracking objects:
>>> 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:
... t1 = Tracking.from_dlc(str(p), handle='ex1', fps=30)
... t2 = Tracking.from_dlc(str(p), handle='ex2', fps=30)
>>> combined = Tracking.concat([t1, t2], handle='combined')
>>> len(combined.data) == len(t1.data) + len(t2.data)
True
>>> combined.handle
'combined'
>>> combined.meta['fps']
30.0
Verify column preservation:
>>> list(combined.data.columns) == list(t1.data.columns)
True
Concatenation metadata is recorded:
>>> 'concat' in combined.meta
True
>>> combined.meta['concat']['n_chunks']
2
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(
*, inplace: bool = True
) -> Tracking | None
strip column names to the last two dot-delimited sections
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
check that total tracking duration (seconds) is between mintime and maxtime.
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,
*,
inplace: bool = True,
) -> Tracking | 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, *, inplace: bool = True
) -> Tracking | None
set position values with likelihood below 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
>>> t.data['p1.x'].values
'array([ 0., 1., 2., nan, nan])'
>>> t.data['p1.likelihood'].values
'array([1. , 0.75, 0.5 , 0.25, 0. ])'
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, sorted alphabetically (ascending)
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 = t.get_point_names()
>>> set(['p1','p2','p3']).issubset(names)
True
>>> sorted_names = sorted(names)
>>> sorted_names == names
True
get_point_dimensions ¶
get_point_dimensions(point: str) -> list[str]
Return viable dimension names associated with a point.
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.get_point_dimensions('p1')
['x', 'y', 'z', 'likelihood']
get_point_data ¶
get_point_data(
point: str, dims: Iterable[str] | None = None
) -> pd.DataFrame
For a specific point, returns the DataFrame with all dimensions data. colnames are reformated to drop the pointname (i.e p1.x -> x)
Args: point (str): name of the point for which data should be exteracted dims (optional(tuple(str))): dimensons which should exclusively be returned
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)
>>> df = t.get_point_data('p1')
>>> df['x'].values
array([0, 1, 2, 3, 4])
set_point_data ¶
set_point_data(
df: DataFrame, point: str, target_df: DataFrame = None
)
Sets the data of a point to the values of an external df.
Args: df (pd.DataFrame): the dataframe containing the point data that should be writen into the trackingobject. colnames should reflect the dimension name (i.e 'x', 'y' etc.) point (str): the name of the point to overwrite target_df (pd.DataFrame, Optional): An external copy of the self.data dataframe can be specified. Usefull for operations that are not in place. defaults to None = write into self.data
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)
>>> df = t.get_point_data('p1')
>>> df['x'].values
array([0, 1, 2, 3, 4])
>>> df['x'] += 1
>>> t.set_point_data(df,'p1')
>>> t.data['p1.x'].values
array([1, 2, 3, 4, 5])
>>> external_df = t.data.copy()
>>> external_df['p1.x'].values
array([1, 2, 3, 4, 5])
>>> df['x'] += 1
>>> t.set_point_data(df = df, point = 'p1', target_df = external_df)
>>> external_df['p1.x'].values
array([2, 3, 4, 5, 6])
rescale_by_known_distance ¶
rescale_by_known_distance(
point1: str,
point2: str,
distance_in_metres: float,
dims=("x", "y"),
*,
inplace: bool = True,
) -> Tracking | 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'
smooth_all ¶
smooth_all(
window: int | None = 11,
method: Literal["mean", "median", "savgol"] = "savgol",
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,
method_kwargs: dict | None = None,
**kwargs,
) -> 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,
*,
inplace: bool = True,
**kwargs,
) -> Tracking | 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,
savedir: str | None = None,
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()
savedir: optional directory path to save the plot image. If provided,
figure is saved as '
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.