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')
coarse_grain ¶
coarse_grain(
window: int,
method: Literal[
"mean", "median", "min", "max"
] = "mean",
non_numeric: Literal[
"drop", "nan", "first", "mode", "error"
] = "drop",
) -> Self
Coarse-grain tracking data over fixed, non-overlapping windows.
Numeric columns are aggregated with method within each window of
window rows. The result is reindexed from 0 and fps is divided
by window to reflect the new effective frame rate. A
"coarse_grain" entry is appended to meta["transforms"].
Non-numeric columns (e.g. string annotations) are handled according to
non_numeric; the default "drop" removes them from the output.
Parameters¶
window : int Number of consecutive rows to collapse into one. method : {"mean", "median", "min", "max"}, default "mean" Aggregation applied to numeric columns within each window. non_numeric : {"drop", "nan", "first", "mode", "error"}, default "drop" How to handle non-numeric columns.
Returns¶
Tracking
New Tracking (or subclass) object with len(data) // window
rows and fps reduced by a factor of window.
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)
5
>>> t.meta['fps']
30.0
Coarse-graining by 2 halves the row count and fps (incomplete windows are kept):
>>> t2 = t.coarse_grain(2)
>>> len(t2.data)
3
>>> t2.meta['fps']
15.0
>>> t2.handle
'ex'
The 5-row input produces 3 windows: two complete (rows 0–1, rows 2–3) and one partial (row 4 alone). Incomplete trailing windows are retained rather than dropped, so no data is lost.
The first window's mean p1.x is (0.0 + 1.0) / 2 = 0.5:
>>> float(round(t2.data['p1.x'].iloc[0], 6))
0.5
The transform is recorded in meta:
>>> t2.meta['transforms'][-1]
{'type': 'coarse_grain', 'window': 2, 'method': 'mean'}
Using method='max' takes the per-window maximum instead:
>>> t_max = t.coarse_grain(2, method='max')
>>> float(round(t_max.data['p1.x'].iloc[0], 6))
1.0
to_features ¶
to_features() -> Features
Create a Features object from this Tracking.
This is a convenience wrapper around Features(self).
Returns¶
Features A new features object linked to this tracking object.
Examples¶
```pycon >>> 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='demo', fps=30) >>> f = t.to_features() >>> from py3r.behaviour.features.features import Features >>> isinstance(f, Features) True >>> f.handle 'demo'
```
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¶
trackings : list[Tracking] List of Tracking objects to concatenate, in temporal order. handle : str, optional Handle for the concatenated object. If None, uses first object's handle. reindex : {"rezero", "follow_previous", "keep_original"}, default "follow_previous" 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.
Returns¶
Tracking A new Tracking object containing all frames from input objects.
Raises¶
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
>>> bool(np.isnan(t.data['p1.x'].values[-1]))
True
>>> float(t.data['p1.likelihood'].values[0])
1.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)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
str
|
name of the point for which data should be exteracted |
required |
|
optional(tuple(str))
|
dimensons which should exclusively be returned |
None
|
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
)
Set the data of a point from an external DataFrame.
Parameters¶
df : pd.DataFrame
DataFrame containing the point data to write. Column names must
reflect the dimension names (e.g. 'x', 'y').
point : str
Name of the point to overwrite.
target_df : pd.DataFrame | None, default=None
An external copy of self.data to write into. If None, writes
in-place 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.
animation_stream ¶
animation_stream(
*,
points: list[str],
lines: list[tuple[str, str]] | None = None,
features: list[str | None]
| dict[str | None, str | None]
| None = None,
dims: tuple[str, ...] = ("x", "y"),
view: dict | None = None,
canvas_size: tuple[int, int] = (800, 800),
bg_color: tuple[int, int, int] = (0, 0, 0),
style: dict | None = None,
pixel_coords: bool = False,
undo_meta_scaling: bool = False,
) -> AnimationStream
Build an OpenCV-backed frame stream for animated point/line overlays.
This method precomputes the selected point coordinates (and optional 3D projection) once, then returns a stream object that can:
- fetch individual rendered frames via
get_frame(i) - iterate sequentially via
read()/next() - play live via
stream.play(...) - save video via
stream.save(...)
Parameters¶
points : list[str]
Point names to render as circles.
lines : list[tuple[str, str]] | None
Line segments connecting point pairs. Endpoints can include points
not listed in points.
features : list[str | None] | dict[str | None, str | None] | None
Per-frame scalar columns to render as text overlays. If a list is
provided, each column is shown as name: value. If a dict is
provided, keys are display labels and values are source column names.
None or "" entries insert a blank spacer line.
dims : tuple[str, ...], default=("x", "y")
Coordinate dimensions. Use 2D (("x","y")) or 3D
(("x","y","z") with view).
view : dict | None
3D camera options used only when dims has length 3. Supported
keys include azim, elev, proj ("ortho" or
"persp"), camera_distance, focal_length, and pad.
canvas_size : tuple[int, int], default=(800, 800)
Canvas size as (width, height).
bg_color : tuple[int, int, int], default=(0, 0, 0)
Background color in BGR.
style : dict | None
Style overrides for points/lines/boundaries.
pixel_coords : bool, default=False
If True, interpret coordinates as absolute pixel locations.
If False, auto-fit projected coordinates to the canvas.
undo_meta_scaling : bool, default=False
If True, invert aspectratio_correction and
meta["rescale_factor"] before rendering.
Returns¶
AnimationStream
Stream object with get_frame(), read(), play(), and
save().
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)
>>> style = {
... "points": {
... "default": {"color": (0, 255, 255), "radius": 3}, # default
... "p1": {"color": (0, 255, 0), "radius": 5}, # static override
... "p2": { # dynamic override
... "radius": {"from": "p1.likelihood", "map": {1.0: 6, "default": 2}}
... },
... }
... }
>>> stream = t.animation_stream(
... points=["p1", "p2"],
... lines=[("p1", "p2")],
... pixel_coords=True,
... canvas_size=(96, 72),
... style=style,
... )
>>> stream.frame_count
5
>>> frame0 = stream.get_frame(0)
>>> frame0.shape
(72, 96, 3)
points_to_numpy ¶
points_to_numpy(
points: list[str],
dims: tuple[str, ...] = ("x", "y"),
*,
undo_meta_scaling: bool = False,
) -> tuple[list[str], np.ndarray]
Resolve selected point coordinates to a NumPy array.
Parameters¶
points : list[str]
Point names to extract.
dims : tuple[str, ...], default=("x", "y")
Coordinate dimensions to extract (2D or 3D).
undo_meta_scaling : bool, default=False
If True, invert aspectratio_correction and rescale_factor
before extraction.
Returns¶
tuple[list[str], np.ndarray]
(point_names, array) where array has shape
(n_frames, n_points, len(dims)).
Examples¶
>>> import pandas as pd
>>> from py3r.behaviour.tracking.tracking import Tracking
>>> df = pd.DataFrame(
... {
... "nose.x": [1.0, 2.0],
... "nose.y": [3.0, 4.0],
... "tail.x": [5.0, 6.0],
... "tail.y": [7.0, 8.0],
... }
... )
>>> t = Tracking(df, meta={"fps": 30.0}, handle="demo")
>>> names, arr = t.points_to_numpy(["nose", "tail"], dims=("x", "y"))
>>> names
['nose', 'tail']
>>> arr.shape
(2, 2, 2)