Examples
This section provides practical examples of using TracksData for multi-object tracking tasks.
Basic Tracking Example
Here's a complete basic example that demonstrates the core workflow of TracksData. This example is available as an executable Python file at docs/examples/basic.py
.
"""
Basic multi-object tracking example using TracksData.
This example demonstrates the complete workflow for tracking objects across time:
1. Load segmented image data
2. Extract object features (nodes) from each frame
3. Create temporal connections (edges) between objects
4. Compute additional attributes to edges (e.g. IoU)
5. Solve the tracking optimization problem
6. Convert results to napari format
7. Visualize results
Requirements:
- Set CTC_DIR environment variable pointing to Cell Tracking Challenge data
- Example assumes Fluo-N2DL-HeLa dataset structure
Usage:
python basic.py # Run with napari visualization
python basic.py --profile # Run with performance profiling
"""
import os
from pathlib import Path
import click
import napari
import numpy as np
from profilehooks import profile as profile_hook
from tifffile import imread
import tracksdata as td
def basic_tracking_example(show_napari_viewer: bool = True) -> None:
"""
Perform basic multi-object tracking on segmented microscopy data.
This function demonstrates the core TracksData workflow:
- Node extraction from regionprops
- Distance-based edge creation
- IoU attribute computation
- Nearest neighbors tracking solution
Parameters
----------
show_napari_viewer : bool
Whether to display results in napari viewer
"""
# Step 1: Load and prepare data
# Load example data from Cell Tracking Challenge format
data_dir = Path(os.environ["CTC_DIR"]) / "training/Fluo-N2DL-HeLa/01_GT/TRA"
assert data_dir.exists(), f"Data directory {data_dir} does not exist."
# Load all timepoints as a 3D array: (time, height, width)
labels = np.stack(
[imread(p) for p in sorted(data_dir.glob("*.tif"))],
)
# Configure TracksData options (disable progress bars for cleaner output)
td.options.set_options(show_progress=False)
print("Starting tracking workflow...")
# Step 2: Initialize graph and extract nodes
graph = td.graph.InMemoryGraph()
# Extract object features using region properties
# This creates one node per object per timeframe
nodes_operator = td.nodes.RegionPropsNodes()
nodes_operator.add_nodes(graph, labels=labels)
print(f"✓ Extracted {graph.num_nodes} nodes from {labels.shape[0]} timeframes")
# Step 3: Create temporal edges between consecutive frames
# Add distance-based edges between objects in consecutive timeframes
# Only connects objects within distance_threshold and limits to n_neighbors
dist_operator = td.edges.DistanceEdges(
distance_threshold=30.0,
n_neighbors=5,
)
dist_operator.add_edges(graph)
print(f"✓ Created {graph.num_edges} potential temporal connections")
# Step 4: Add IoU (Intersection over Union) attributes to edges
# Compute IoU between connected objects to measure shape similarity
# Higher IoU values indicate better matches for tracking
iou_operator = td.edges.IoUEdgeAttr(output_key="iou")
iou_operator.add_edge_attrs(graph)
print("✓ Computed IoU attributes for edge weights")
# Step 5: Solve tracking optimization problem
# Create edge weights combining distance and IoU information
# Lower distance + higher IoU = better connection (lower cost)
dist_weight = 1 / dist_operator.distance_threshold
# Use nearest neighbors solver for fast, greedy tracking
# Each edge weight is defined as:
# - IoU(e_ij) * exp(-distance(e_ij) / dist_threshold)
# Where e_ij is the edge between nodes i and j.
# Alternative: ILPSolver for globally optimal but slower solutions
solver = td.solvers.NearestNeighborsSolver(
edge_weight=-td.EdgeAttr("iou") * (td.EdgeAttr("distance") * dist_weight).exp(),
max_children=2, # Allow cell divisions (max 2 children per parent)
)
# Alternative ILP solver (uncomment for optimal tracking):
# solver = td.solvers.ILPSolver(
# edge_weight=-td.EdgeAttr("iou") * (td.EdgeAttr("weight") * dist_weight).exp(),
# node_weight=0.0, # Cost for keeping an object
# appearance_weight=1.0, # Cost for object appearing
# disappearance_weight=1.0, # Cost for object disappearing
# division_weight=1.0, # Cost for cell division
# )
solver.solve(graph)
print("✓ Solved tracking assignments")
# Step 6: Convert results for visualization
# Convert tracking graph to napari-compatible format
# Returns: tracked labels, tracks dataframe, and track graph
print("Converting results to napari format...")
tracks_df, track_graph, track_labels = td.functional.to_napari_format(graph, labels.shape, mask_key="mask")
print(f"✓ Generated {len(tracks_df)} track points across {len(set(tracks_df['track_id']))} tracks")
# Step 7: Visualize results (optional)
if show_napari_viewer:
print("Opening napari viewer...")
viewer = napari.Viewer()
# Add original segmented labels
viewer.add_labels(track_labels, name="Tracked Labels")
# Add tracking trajectories with lineage information
viewer.add_tracks(tracks_df, graph=track_graph, name="Tracks")
# Start interactive viewer
napari.run()
@click.command()
@click.option("--profile", is_flag=True, help="Enable performance profiling (disables napari viewer)")
def main(profile: bool) -> None:
"""Run the basic tracking example with optional profiling."""
if profile:
# Run with performance profiling, no visualization
profile_hook(basic_tracking_example, immediate=True, sort="time")(show_napari_viewer=False)
else:
# Normal run with visualization
basic_tracking_example(show_napari_viewer=True)
if __name__ == "__main__":
main()
Key Components Explained
- Graph: The core data structure holding nodes (objects) and edges (connections)
- Nodes Operators: Extract object features from segmented images (RegionPropsNodes, MaskNodes, etc.)
- Edges Operators: Create temporal connections between objects (DistanceEdges, IoUEdges, etc.)
- Solvers: Optimize a minimization problem to find the best tracking assignments (NearestNeighborsSolver, ILPSolver)
- Functional: Utilities for format conversion and visualization
Next Steps
- Check the Getting Started guide for more detailed explanations
- Explore the Concepts page to understand the architecture
- See the API reference for complete documentation of all components