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)
>>> 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'

data instance-attribute

data: DataFrame = data

handle instance-attribute

handle: str = handle

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'

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'

meta instance-attribute

meta: dict = meta

tags instance-attribute

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

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'

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'

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.

For style dict documentation and worked examples, see the Animation guide.

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:

Name Type Description Default

points

list[str]

Point names to render as circles.

required

lines

list[tuple[str, str]] | None

Line segments connecting point pairs. Endpoints can include points not listed in points.

None

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.

None

dims

tuple[str, ...]

Coordinate dimensions. Use 2D (("x","y")) or 3D (("x","y","z") with view).

('x', 'y')

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.

None

canvas_size

tuple[int, int]

Canvas size as (width, height).

(800, 800)

bg_color

tuple[int, int, int]

Background color in BGR.

(0, 0, 0)

style

dict | None

Style overrides for points/lines/boundaries.

None

pixel_coords

bool

If True, interpret coordinates as absolute pixel locations. If False, auto-fit projected coordinates to the canvas.

False

undo_meta_scaling

bool

If True, invert aspectratio_correction and meta["rescale_factor"] before rendering.

False

Returns:

Type Description
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)

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:

Name Type Description Default

window

int

Number of consecutive rows to collapse into one.

required

method

Literal['mean', 'median', 'min', 'max']

Aggregation applied to numeric columns within each window.

'mean'

non_numeric

Literal['drop', 'nan', 'first', 'mode', 'error']

How to handle non-numeric columns.

'drop'

Returns:

Type Description
Self

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

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

trackings

list[Self]

List of Tracking objects to concatenate, in temporal order.

required

handle

str | None

Handle for the concatenated object. If None, uses first object's handle.

None

reindex

Literal['rezero', 'follow_previous', 'keep_original']

How to handle frame indices. "rezero" reindexes all frames starting from 0. "follow_previous" continues from where the previous chunk ended. "keep_original" leaves indices untouched; duplicates are allowed.

'follow_previous'

Returns:

Type Description
Self

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

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')

define_midpoint

define_midpoint(
    name: str,
    points: list[str] | dict[str, float],
    *,
    inplace: bool = True,
) -> Tracking | None

Define a new point as the (optionally weighted) midpoint of existing points.

Spatial dimensions are inferred from the source points and must be consistent across all of them. Likelihood is taken as the per-frame minimum across all source points.

Parameters:

Name Type Description Default

name

str

Name for the new derived point.

required

points

list[str] | dict[str, float]

Source point names with equal weighting (list), or a mapping of point name to relative weight (dict). Weights are normalised internally, so {"nose": 1, "tail": 3} is equivalent to {"nose": 0.25, "tail": 0.75}.

required

inplace

bool

If True, modifies self.data in place and returns None. If False, returns a new Tracking with the point added.

True

Returns:

Type Description
Tracking | None

None when inplace=True; a new Tracking otherwise.

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)
>>> t.define_midpoint("mid12", ["p1", "p2"])
>>> "mid12" in t.get_point_names()
True
>>> mid_x = float(t.data["mid12.x"].iloc[0])
>>> p1_x = float(t.data["p1.x"].iloc[0])
>>> p2_x = float(t.data["p2.x"].iloc[0])
>>> mid_x == (p1_x + p2_x) / 2
True
>>> "mid12.z" in t.data.columns
True
>>> bool(all(t.data["mid12.likelihood"] <= t.data["p1.likelihood"]))
True
>>> bool(all(t.data["mid12.likelihood"] <= t.data["p2.likelihood"]))
True

Weighted example — p1 carries three times the weight of p2:

>>> 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)
>>> t.define_midpoint("wt_mid", {"p1": 3, "p2": 1})
>>> val = float(t.data["wt_mid.x"].iloc[0])
>>> expected = 0.75 * float(t.data["p1.x"].iloc[0]) + 0.25 * float(t.data["p2.x"].iloc[0])
>>> abs(val - expected) < 1e-10
True

define_offset_point

define_offset_point(
    name: str,
    reference: str,
    offset: tuple[float, ...],
    *,
    inplace: bool = True,
) -> Tracking | None

Define a new point as a fixed spatial offset from an existing point.

The offset is added to every frame's coordinates of the reference point. Likelihood is inherited directly from the reference point.

Parameters:

Name Type Description Default

name

str

Name for the new derived point.

required

reference

str

Name of the existing point to offset from.

required

offset

tuple[float, ...]

Per-dimension displacement, e.g. (dx, dy) for 2D or (dx, dy, dz) for 3D. Length must match the spatial dimensions of reference.

required

inplace

bool

If True, modifies self.data in place and returns None. If False, returns a new Tracking with the point added.

True

Returns:

Type Description
Tracking | None

None when inplace=True; a new Tracking otherwise.

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)
>>> t.define_offset_point("p1_shifted", "p1", offset=(10.0, 0.0, 0.0))
>>> bool(all(t.data["p1_shifted.x"] == t.data["p1.x"] + 10.0))
True
>>> bool(all(t.data["p1_shifted.y"] == t.data["p1.y"]))
True
>>> bool(all(t.data["p1_shifted.likelihood"] == t.data["p1.likelihood"]))
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

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

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

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

point

str

name of the point for which data should be exteracted

required

dims

Iterable[str] | None

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])

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

interpolate

interpolate(
    method: str = "linear",
    limit: int = 1,
    *,
    inplace: bool = True,
    **kwargs,
) -> Tracking | None

Interpolate missing position data and set likelihood columns to 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

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

plot

plot(
    trajectories: list[str]
    | dict[str, Series]
    | None = None,
    static: list[str] | None = None,
    lines: list[tuple[str, str]] | None = None,
    dims: tuple[str, ...] = ("x", "y"),
    ax: Any | None = None,
    title: str | None = None,
    show: bool = True,
    savedir: str | None = None,
    elev: float = 30,
    azim: float = 45,
) -> tuple[Any, Any]

Plot trajectories and static points for this Tracking object.

Parameters:

Name Type Description Default

trajectories

list[str] | dict[str, Series] | None

Point names to plot as trajectories, or a dict mapping point name to a color pd.Series.

None

static

list[str] | None

Point names to plot as static markers (median position).

None

lines

list[tuple[str, str]] | None

Pairs of point names to connect with a line.

None

dims

tuple[str, ...]

Coordinate dimensions to plot. Use ('x','y','z') for 3D.

('x', 'y')

ax

Any | None

Existing matplotlib axis to draw on.

None

title

str | None

Plot title. Defaults to self.handle.

None

show

bool

Whether to call plt.show().

True

savedir

str | None

If provided, saves the figure as <handle>_plot.png here.

None

elev

float

Elevation angle for 3D plots.

30

azim

float

Azimuth angle for 3D plots.

45

Returns:

Type Description
tuple[Any, Any]

Tuple of (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)

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:

Name Type Description Default

points

list[str]

Point names to extract.

required

dims

tuple[str, ...]

Coordinate dimensions to extract (2D or 3D).

('x', 'y')

undo_meta_scaling

bool

If True, invert aspectratio_correction and rescale_factor before extraction.

False

Returns:

Type Description
tuple[list[str], 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)

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'

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

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.

Renders four subplots per frame (front, side, top, and isometric views). Axis limits can be set manually or derived from robust percentiles. Enforces equal aspect ratio for all axes.

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:

Name Type Description Default

df

DataFrame

DataFrame containing the point data to write. Column names must reflect the dimension names (e.g. 'x', 'y').

required

point

str

Name of the point to overwrite.

required

target_df

DataFrame

An external copy of self.data to write into. If None, writes in-place into self.data.

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])
>>> 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])

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: Any | None = None,
    smoother_kwargs: dict | None = None,
    method_kwargs: dict | None = None,
    **kwargs: Any,
) -> Tracking | None

Smooth all tracked points using a default method/window.

Supports optional per-point override groups.

Parameters:

Name Type Description Default

window

int | None

Default smoothing window applied to any point without an override.

11

method

Literal['mean', 'median', 'savgol']

Default smoothing method applied to any point without an override.

'savgol'

overrides

list[tuple[list[str] | tuple[str, ...] | str, str, int | None]] | None

Optional list of (points, method, window) tuples. points is a list/tuple of point names (or a single str); method is 'median' or 'mean'; window is an int, or None to skip smoothing for those points.

None

dims

tuple[str, ...]

Coordinate dimensions to smooth.

('x', 'y')

strict

bool

If True, require an effective window for every point.

False

inplace

bool

If True, mutate self and return None; if False, return a new object.

True

smoother

Any | None

Custom smoother callable, passed directly to apply_smoothing.

None

smoother_kwargs

dict | None

Keyword arguments forwarded to the custom smoother.

None

method_kwargs

dict | None

Extra kwargs applied to the default method for all points (e.g. {"nan_policy": "omit"} for savgol).

None

**kwargs

Any

Additional method kwargs merged into per-point specs (e.g. polyorder=3). Convenient shorthand for method_kwargs.

{}
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

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

to_features

to_features() -> Features

Create a Features object from this Tracking.

This is a convenience wrapper around Features(self).

Returns:

Type Description
Features

A new features object linked to this tracking object.

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='demo', fps=30)
>>> f = t.to_features()
>>> from py3r.behaviour.features.features import Features
>>> isinstance(f, Features)
True
>>> f.handle
'demo'

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)