# How to Implement a Custom Tracker in BoxMOT by Extending BaseTracker

> Implement a custom tracker in BoxMOT by extending BaseTracker. Seamlessly integrate your custom tracker with the CLI, visualization, and evaluation pipeline.

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

---

**Extend `BaseTracker` from the BoxMOT library to create a custom multi-object tracker that integrates seamlessly with the existing CLI, visualization tools, and evaluation pipeline.**

BoxMOT (by mikel-brostrom/boxmot) standardizes multi-object tracking through an abstract `BaseTracker` class that defines the interface for all tracking algorithms. Whether you are prototyping a novel association method or adapting research code for production, inheriting from this base class ensures compatibility with the library's model zoo, configuration system, and inference engine. This guide walks through the architectural requirements and provides a complete, runnable implementation.

## Understanding the BaseTracker Abstraction

The `BaseTracker` class in [`boxmot/trackers/basetracker.py`](https://github.com/mikel-brostrom/boxmot/blob/main/boxmot/trackers/basetracker.py) defines the contract that every tracker must fulfill. It provides common functionality such as argument logging, input validation, and decorator utilities for setup and per-class processing.

Any concrete tracker—such as **StrongSort** ([`boxmot/trackers/strongsort/strongsort.py`](https://github.com/mikel-brostrom/boxmot/blob/main/boxmot/trackers/strongsort/strongsort.py)) or **ByteTrack** ([`boxmot/trackers/bytetrack/bytetrack.py`](https://github.com/mikel-brostrom/boxmot/blob/main/boxmot/trackers/bytetrack/bytetrack.py))—must implement:

1. **`__init__`** to accept configuration parameters and call the parent constructor with `super().__init__(**init_args, _tracker_name='YourTracker', **kwargs)`.
2. **`update`** decorated with `@BaseTracker.setup_decorator` and optionally `@BaseTracker.per_class_decorator` to process detections and return tracked objects.
3. **`reset`** (optional) to clear internal state when the tracker instance is reused across sequences.

The `update` method must accept detections as a NumPy array and return an `(N, 8)` array where each row contains `[x1, y1, x2, y2, track_id, confidence, class_id, detection_index]`.

## Step-by-Step Implementation Guide

### Create the Tracker Module

Create a new Python module inside the `boxmot/trackers/` directory. For a tracker named `MyTracker`, create [`boxmot/trackers/mytracker/mytracker.py`](https://github.com/mikel-brostrom/boxmot/blob/main/boxmot/trackers/mytracker/mytracker.py). Import the base class and required utilities:

```python
import numpy as np
from boxmot.trackers.basetracker import BaseTracker

```

### Implement the Class Structure

Define a class that subclasses `BaseTracker` using CamelCase naming. Implement `__init__` to capture tracker-specific hyperparameters:

```python
def __init__(self, max_age=30, min_hits=3, **kwargs):
    # Build init_args dict excluding 'self' and 'kwargs' for the base logger

    init_args = {k: v for k, v in locals().items() if k not in ("self", "kwargs")}
    super().__init__(**init_args, _tracker_name="MyTracker", **kwargs)
    
    # Initialize tracker-specific attributes

    self.max_age = max_age
    self.min_hits = min_hits

```

The `super().__init__` call is critical: it registers the tracker name and logs all initialization arguments for reproducibility, as seen in StrongSort's constructor at lines 66-69.

### Implement the Update Method

The `update` method is the core logic. It must be decorated with `@BaseTracker.setup_decorator` to run one-time initialization (e.g., setting the association function) and optionally `@BaseTracker.per_class_decorator` to enable per-class tracking. The signature must match:

```python
@BaseTracker.setup_decorator
@BaseTracker.per_class_decorator
def update(self, dets: np.ndarray, img: np.ndarray, embs: np.ndarray = None) -> np.ndarray:
    # Validate inputs

    self.check_inputs(dets, img, embs)
    
    # Process detections...

```

ByteTrack's implementation at lines 84-86 demonstrates this exact decorator pattern.

### Process Detections and Generate Outputs

Inside `update`, append a detection index column to maintain correspondence between input detections and output tracks:

```python
dets = np.hstack([dets, np.arange(len(dets)).reshape(-1, 1)])

```

For each detection, assign a unique track ID and build the output array. The return value must be a NumPy array of shape `(N, 8)` with columns `[x1, y1, x2, y2, id, conf, cls, det_ind]`. If no tracks exist, return an empty array with shape `(0, 8)`.

### Register in tracker_zoo.py

To make the tracker accessible via the CLI, register it in [`boxmot/trackers/tracker_zoo.py`](https://github.com/mikel-brostrom/boxmot/blob/main/boxmot/trackers/tracker_zoo.py) by adding an entry to the `TRACKER_MAPPING` dictionary:

```python
TRACKER_MAPPING = {
    # ... existing trackers ...

    "mytracker": "boxmot.trackers.mytracker.mytracker.MyTracker",
}

```

This registry pattern allows the factory function in [`tracker_zoo.py`](https://github.com/mikel-brostrom/boxmot/blob/main/tracker_zoo.py) (lines 10-18) to instantiate your tracker dynamically from a string identifier.

### Add Configuration YAML

Create a default configuration file at [`boxmot/configs/trackers/mytracker.yaml`](https://github.com/mikel-brostrom/boxmot/blob/main/boxmot/configs/trackers/mytracker.yaml) following the schema used by existing trackers:

```yaml
max_age:
  type: int
  default: 30
min_hits:
  type: int
  default: 3
det_thresh:
  type: float
  default: 0.3

```

This enables instantiation via the BoxMOT configuration system when users specify `--tracker mytracker` in the CLI.

## Complete Working Example: EchoTracker

Below is a minimal yet complete custom tracker that forwards each detection as a track with a monotonically increasing ID. This demonstrates proper inheritance, decorator usage, argument handling, and output formatting.

```python

# boxmot/trackers/echotracker/echotracker.py

import numpy as np
from boxmot.trackers.basetracker import BaseTracker


class EchoTracker(BaseTracker):
    """
    EchoTracker – a pedagogical example that echoes detections as tracks.
    Each detection receives a unique, incrementing track ID.
    """

    def __init__(self, **kwargs):
        # Gather all arguments passed via CLI or tracker_zoo

        init_args = {k: v for k, v in locals().items() if k not in ("self", "kwargs")}
        # Pass to BaseTracker for logging and initialization

        super().__init__(**init_args, _tracker_name="EchoTracker", **kwargs)
        
        self._next_id = 0

    @BaseTracker.setup_decorator
    @BaseTracker.per_class_decorator
    def update(
        self, dets: np.ndarray, img: np.ndarray, embs: np.ndarray = None
    ) -> np.ndarray:
        """
        Parameters
        ----------
        dets : np.ndarray, shape (N, 6)
            Rows are [x1, y1, x2, y2, conf, cls]
        img : np.ndarray
            Current frame (unused here)
        embs : np.ndarray, optional
            Appearance embeddings (ignored)

        Returns
        -------
        np.ndarray, shape (M, 8)
            Rows are [x1, y1, x2, y2, id, conf, cls, det_ind]
        """
        # Validate input shapes (optional but recommended)

        self.check_inputs(dets, img, embs)
        
        # Append detection index column

        dets = np.hstack([dets, np.arange(len(dets)).reshape(-1, 1)])
        
        tracks = []
        for x1, y1, x2, y2, conf, cls, det_ind in dets:
            tracks.append([x1, y1, x2, y2, self._next_id, conf, cls, det_ind])
            self._next_id += 1
            
        return np.asarray(tracks) if tracks else np.empty((0, 8))

    def reset(self):
        """Clear internal state when tracker is reused."""
        self._next_id = 0

```

**Registration** (in [`boxmot/trackers/tracker_zoo.py`](https://github.com/mikel-brostrom/boxmot/blob/main/boxmot/trackers/tracker_zoo.py)):

```python
TRACKER_MAPPING = {
    # ... other trackers ...

    "echotracker": "boxmot.trackers.echotracker.echotracker.EchoTracker",
}

```

**Configuration** ([`boxmot/configs/trackers/echotracker.yaml`](https://github.com/mikel-brostrom/boxmot/blob/main/boxmot/configs/trackers/echotracker.yaml)):

```yaml
det_thresh:
  type: float
  default: 0.3
max_age:
  type: int
  default: 30
min_hits:
  type: int
  default: 3

```

You can now run the tracker from the command line:

```bash
python -m boxmot.engine.cli track \
    --source path/to/video.mp4 \
    --tracker echotracker \
    --detector yolox

```

## Summary

- **Inherit from `BaseTracker`** in [`boxmot/trackers/basetracker.py`](https://github.com/mikel-brostrom/boxmot/blob/main/boxmot/trackers/basetracker.py) to ensure API compatibility with the BoxMOT ecosystem.
- **Implement `__init__`** to capture parameters and call `super().__init__` with `init_args` and `_tracker_name` for proper logging.
- **Use decorators** `@BaseTracker.setup_decorator` and `@BaseTracker.per_class_decorator` on the `update` method to enable initialization hooks and per-class tracking.
- **Return the correct shape**: an `(N, 8)` NumPy array with columns `[x1, y1, x2, y2, id, conf, cls, det_ind]`.
- **Register in [`tracker_zoo.py`](https://github.com/mikel-brostrom/boxmot/blob/main/tracker_zoo.py)** and **create a YAML config** to expose the tracker via the CLI and configuration system.

## Frequently Asked Questions

### What is the minimum number of methods I must implement to create a valid BoxMOT tracker?

You must implement `__init__` and `update`. The `__init__` method must call `super().__init__(**init_args, _tracker_name='YourName', **kwargs)` to properly initialize the base class. The `update` method must accept `dets`, `img`, and optional `embs` parameters and return a NumPy array of shape `(N, 8)`. Optionally, you can override `reset()` to clear state between video sequences, though the base class provides an empty placeholder.

### How do I access the original detection indices in my tracker's output?

Append a column containing `np.arange(len(dets))` to your detections array before processing. The final output must include this as the eighth column (index 7) in the returned array. This allows downstream components in BoxMOT to correlate tracks with their original detections for visualization or evaluation.

### Can I use precomputed appearance embeddings (embs) in my custom tracker?

Yes. The `update` method receives an optional `embs` parameter containing appearance embeddings for the current detections. Validate the input using `self.check_inputs(dets, img, embs)` to ensure dimensions match, then access the embeddings to implement appearance-based association logic similar to StrongSort.

### Where does BoxMOT look for tracker configurations when using the CLI?

The system loads YAML configuration files from `boxmot/configs/trackers/<tracker_name>.yaml`. The [`tracker_zoo.py`](https://github.com/mikel-brostrom/boxmot/blob/main/tracker_zoo.py) module uses the `TRACKER_MAPPING` dictionary to resolve the tracker name to a class path, then instantiates the class using parameters defined in the corresponding YAML file. Ensure your configuration follows the established schema with `type` and `default` keys for each parameter.