# How to Visualize Tracking Trajectories with BoxMOT Utilities

> Visualize tracking trajectories with BoxMOT utilities by inheriting VisualizationMixin and calling plot_results. Learn how to render track histories as colored circles easily.

- Repository: [Mike/boxmot](https://github.com/mikel-brostrom/boxmot)
- Tags: how-to-guide
- Published: 2026-03-07

---

**To visualize tracking trajectories in BoxMOT, inherit from `VisualizationMixin` and call `plot_results()` with `show_trajectories=True`, which renders each track's `history_observations` as colored circles via `plot_trackers_trajectories()` in [`boxmot/utils/visualization.py`](https://github.com/mikel-brostrom/boxmot/blob/main/boxmot/utils/visualization.py).**

BoxMOT provides a dedicated visualization stack that eliminates the need for manual drawing code when building multi-object trackers. Located in [`boxmot/utils/visualization.py`](https://github.com/mikel-brostrom/boxmot/blob/main/boxmot/utils/visualization.py), the architecture uses a mixin pattern to inject trajectory plotting capabilities into any tracker that maintains observation history. This guide explains the internal mechanics and provides runnable implementations for both mock and production trackers.

## Understanding the Visualization Architecture

The BoxMOT visualization system centers on the **`VisualizationMixin`** class, which exposes high-level rendering methods to trackers without requiring them to implement drawing logic directly.

### The VisualizationMixin Interface

Any tracker inheriting from `VisualizationMixin` gains access to three primary methods:

- **`plot_box_on_img`** – Renders individual bounding boxes (solid or dashed) with track ID, confidence score, and class label
- **`plot_trackers_trajectories`** – Draws the complete motion history of a track as progressively thickening colored circles, using a deterministic hash (`id_to_color`) to assign consistent colors per track ID
- **`plot_results`** – Orchestrates the entire visualization pipeline by iterating over active, lost, and removed tracks, applying appropriate styling, and returning the annotated image

Trackers only need to expose specific attributes (`active_tracks`, optional `lost_stracks`/`removed_stracks`, `is_obb`, `target_id`, `removed_display_frames`) for the mixin to function correctly.

### Helper Classes for State Management

The mixin delegates state-specific rendering to two concrete helper classes:

1. **`ExplicitStateVisualization`** – Used when trackers explicitly expose `lost_stracks` and `removed_stracks` collections
2. **`InferredStateVisualization`** – Used when a tracker provides only `active_tracks`, with the visualizer inferring state transitions on-the-fly

## Core Methods for Trajectory Visualization

### plot_trackers_trajectories

The **`plot_trackers_trajectories`** method (lines 15-21 in [`boxmot/utils/visualization.py`](https://github.com/mikel-brostrom/boxmot/blob/main/boxmot/utils/visualization.py)) handles the actual trajectory drawing. When enabled, it receives a track's `history_observations` list—a sequence of previous bounding box coordinates—and renders each point as a circle. Older observations receive thicker circles to create a visual trail effect, while the color remains consistent per track ID through deterministic hashing.

### plot_results Orchestration

The **`plot_results`** method (lines 228-258) serves as the entry point for frame rendering. It:

1. Categorizes tracks into active, lost, and removed states
2. Determines line style (solid for active/removed, dashed for lost)
3. Conditionally invokes `plot_trackers_trajectories` when `show_trajectories=True`
4. Manages the display lifecycle of removed tracks via `removed_display_frames`

The high-level tracker engine calls this method automatically after each frame update (specifically at line 147 in [`boxmot/engine/tracker.py`](https://github.com/mikel-brostrom/boxmot/blob/main/boxmot/engine/tracker.py)), applying the visualization directly to `result.orig_img`.

## Enabling Trajectory Visualization in Your Tracker

To successfully visualize tracking trajectories, your implementation must satisfy three requirements:

1. **Inherit from `VisualizationMixin`** – Add the mixin to your tracker class definition
2. **Populate `history_observations`** – Append the current bounding box to `track.history_observations` during every tracker update cycle
3. **Configure `plot_results` parameters** – Pass `show_trajectories=True` when calling the visualization method

Optional configurations include setting `is_obb=True` for oriented bounding box support, adjusting `removed_display_frames` to control how long removed tracks remain visible, and enabling `show_lost=True` to display dashed bounding boxes for tentative tracks.

## Code Examples

### Minimal Mock Tracker Example

The following implementation demonstrates the minimal structure required to render trajectories without a full detection pipeline:

```python
import numpy as np
import cv2
from boxmot.utils.visualization import VisualizationMixin

class SimpleTracker(VisualizationMixin):
    def __init__(self):
        self.is_obb = False
        self.target_id = None
        self.removed_display_frames = 10
        self._plot_frame_idx = 0
        self._removed_first_seen = {}
        self._removed_expired = set()
        self.active_tracks = []

class Track:
    def __init__(self, tid, history):
        self.id = tid
        self.history_observations = history
        self.conf = 0.9
        self.cls = 0
        self.time_since_update = 0
        self.is_activated = True
        self.hits = 10
        self.xyxy = history[-1]

# Create dummy data

frame = np.zeros((200, 200, 3), dtype=np.uint8)
track = Track(1, [
    [20, 20, 60, 60],
    [30, 30, 70, 70],
    [40, 40, 80, 80],
])

tracker = SimpleTracker()
tracker.active_tracks = [track]

# Render with trajectories enabled

out = tracker.plot_results(
    img=frame,
    show_trajectories=True,
    show_lost=False,
    thickness=2,
    fontscale=0.5,
)

cv2.imwrite("trajectory_demo.png", out)

```

### Real-World ByteTrack Implementation

For production use with the BoxMOT engine, trajectory visualization integrates seamlessly with existing trackers like ByteTrack:

```python
from boxmot.engine.tracker import Tracker
from boxmot.detection import detect
import cv2

# Initialize pipeline

tracker = Tracker(
    detector="yolox",
    tracker="bytetrack",
    device="cpu",
)

# Process frame

frame = cv2.imread("sample.jpg")
detections = detect(frame)
result = tracker.process(frame, detections)

# Visualize results

vis = tracker.plot_results(
    img=result.orig_img,
    show_trajectories=True,
    show_lost=True,
    thickness=2,
    fontscale=0.5,
)

cv2.imwrite("bytetrack_trajectory.png", vis)

```

In this implementation, the `Tracker` class automatically populates `history_observations` for each track, allowing `plot_trackers_trajectories` to render the motion history without additional user code.

## Summary

- **Inherit from `VisualizationMixin`** located in [`boxmot/utils/visualization.py`](https://github.com/mikel-brostrom/boxmot/blob/main/boxmot/utils/visualization.py) to add trajectory drawing capabilities to any tracker
- **Enable trajectories** by passing `show_trajectories=True` to `plot_results()`, which invokes `plot_trackers_trajectories()` internally
- **Maintain history** by appending bounding boxes to each track's `history_observations` list during the update cycle
- **Configure display options** via `show_lost`, `removed_display_frames`, and `is_obb` parameters to control visual presentation
- **Integration point** occurs automatically at line 147 of [`boxmot/engine/tracker.py`](https://github.com/mikel-brostrom/boxmot/blob/main/boxmot/engine/tracker.py) when using the high-level `Tracker` API

## Frequently Asked Questions

### How does BoxMOT assign consistent colors to track trajectories?

BoxMOT uses a deterministic hash function `id_to_color` that maps each unique track ID to a specific RGB value. This ensures that trajectory circles and bounding boxes for a given track maintain the same color throughout the video sequence, even if the track temporarily disappears or changes state.

### What data structure stores the trajectory points for visualization?

Each track object must maintain a **`history_observations`** attribute containing a list of bounding box coordinates (typically in `[x1, y1, x2, y2]` format). The `plot_trackers_trajectories` method iterates over this list to render progressively thickening circles, with older observations receiving larger radii to create a motion trail effect.

### Can I visualize trajectories for trackers that don't expose lost or removed tracks?

Yes. BoxMOT provides **`InferredStateVisualization`** for trackers that only expose `active_tracks`. The visualizer infers state transitions automatically, allowing you to use `plot_results()` with `show_trajectories=True` even without explicit `lost_stracks` or `removed_stracks` collections.

### Where does the actual rendering call happen in the BoxMOT pipeline?

When using the high-level `Tracker` API from [`boxmot/engine/tracker.py`](https://github.com/mikel-brostrom/boxmot/blob/main/boxmot/engine/tracker.py), the visualization occurs automatically at **line 147**, where the engine calls `tracker.plot_results()` on the original image and assigns the result back to `result.orig_img`. For custom implementations, you must explicitly call this method after each tracking update.