An extremely fast MOT and HOTA metrics library, written in Rust.
Project description
motrics
An extremely fast MOT and HOTA metrics library, written in Rust — CLEAR (MOTA/MOTP), Identity (IDF1), and HOTA, with an ergonomic Python API.
Highlights
- ⚡ Extremely fast — Rust core, ~3–14× faster than TrackEval and py-motmetrics on real MOT17 data.
- 🎯 Numerically validated — exact parity with TrackEval on CLEAR, Identity, and HOTA, checked in CI.
- 🔄 Drop-in migration — swap one import to replace py-motmetrics; evaluate a MOTChallenge benchmark without installing TrackEval.
- 🐍 Ergonomic, typed Python API — PEP 561,
numpythe only required runtime dependency. - 🔢 Flexible box input —
xyxyorxywh, and a zero-copy read path for contiguous NumPy arrays.
Install
Not on PyPI yet — build from source:
uv sync --group dev # create the environment + install dev tools
uv run maturin develop # compile the Rust extension into the venv
uv run python -c "import motrics; print(motrics.version())"
Quickstart
import motrics
# Parse MOTChallenge ground truth and tracker results.
gt = motrics.load_motchallenge("seq/gt/gt.txt")
pred = motrics.load_motchallenge("seq/res.txt", min_confidence=0.5)
# Align onto a shared frame timeline, bundle each side, then evaluate.
gt_ids, gt_boxes, pred_ids, pred_boxes = motrics.align_frames(gt, pred)
result = motrics.evaluate(
motrics.Frames(ids=gt_ids, boxes=gt_boxes),
motrics.Frames(ids=pred_ids, boxes=pred_boxes),
)
print(result.clear.mota, result.identity.idf1, result.hota.hota)
evaluate() builds the gt/pred similarity matrix once and shares it across
CLEAR, Identity, and HOTA, instead of recomputing it per metric. Only need one
metric? compute_clear/compute_identity/compute_hota take the same
gt_ids, gt_boxes, pred_ids, pred_boxes directly, without a Frames wrapper.
Boxes default to the xyxy convention (x1, y1, x2, y2); pass
box_format="xywh" for (x, y, width, height) instead. Each frame's boxes
can also be a (N, 4) float64 NumPy array — a contiguous xyxy array is read
with zero copies, no per-box Python overhead.
Want numbers matching TrackEval's own reported values (pedestrian-only,
distractor-aware)? Use load_motchallenge_gt + preprocess_motchallenge
instead of load_motchallenge + align_frames.
Migrating from py-motmetrics
Swap the import — the rest of your accumulator code is unchanged:
# before
import motmetrics as mm
# after — same code, motrics underneath
import motrics.compat.motmetrics as mm
acc = mm.MOTAccumulator(auto_id=True)
for gt_ids, gt_boxes, pred_ids, pred_boxes in sequence:
dists = mm.distances.iou_matrix(gt_boxes, pred_boxes, max_iou=0.5)
acc.update(gt_ids, pred_ids, dists)
summary = mm.metrics.create().compute(acc, metrics=mm.metrics.SUPPORTED, name="acc")
pip install motrics[compat](pulls in pandas, needed only for this subpackage).- Supported:
mota,motp,idf1,idp,idr,recall,precision,num_false_positives,num_misses,num_switches,num_unique_objects— the same names py-motmetrics uses. - Not yet implemented: per-trajectory metrics (mostly-tracked, fragmentations,
transfer/ascend/migrate). Requesting them raises
NotImplementedErrornaming exactly what's missing, rather than a silently wrong number. - See
python/motrics/compat/motmetrics/for what else differs (e.g. noevents/mot_eventsDataFrame).
Migrating from TrackEval
Swap the import — the rest of your evaluation script is unchanged:
# before
import trackeval
# after — same code, motrics underneath
import motrics.compat.trackeval as trackeval
eval_config = trackeval.Evaluator.get_default_eval_config()
evaluator = trackeval.Evaluator(eval_config)
dataset_config = trackeval.datasets.MotChallenge2DBox.get_default_dataset_config()
dataset_config["GT_FOLDER"] = "data/gt/mot_challenge/"
dataset_config["TRACKERS_FOLDER"] = "data/trackers/mot_challenge/"
dataset_list = [trackeval.datasets.MotChallenge2DBox(dataset_config)]
metrics_list = [trackeval.metrics.HOTA(), trackeval.metrics.CLEAR(), trackeval.metrics.Identity()]
results, messages = evaluator.evaluate(dataset_list, metrics_list)
print(results["MotChallenge2DBox"]["my_tracker"]["COMBINED_SEQ"]["pedestrian"]["CLEAR"]["MOTA"])
Same class names, config keys, directory/seqmap conventions
(GT_FOLDER/BENCHMARK-SPLIT/<seq>/gt/gt.txt, a seqmap file, per-sequence
seqinfo.ini), and result shape as real TrackEval, no trackeval/scipy
install required — but only for the subset below. HOTA, Identity, and
CLEAR's MOTA/MOTP fields are verified bit-exact against real TrackEval;
unsupported config or fields raise rather than silently returning a wrong
number.
- No extra needed — this subpackage relies only on
numpy(a core dependency), whose arrays back HOTA's per-alpha fields, matching TrackEval. - Not implemented: parallel evaluation, error-handling config
(
BREAK_ON_ERROR/etc. — always raises immediately), printing/file output/plotting; zipped input (INPUT_AS_ZIP);DO_PREPROC=FalseandBENCHMARK="MOT15"(raise at construction);CLEARfields beyondMOTA/MOTP(MT/PT/ML/Frag/MODA/sMOTA/etc. need mostly-tracked/lost and fragmentation bookkeeping the Rust core doesn't compute yet);IDEucl/JAndF/TrackMAP/VACEmetrics. - See
python/motrics/compat/trackeval/for the full list of what differs from real TrackEval.
Metric name map — TrackEval / py-motmetrics / motrics' native API
Using motrics' own API directly (faster than the compat layer — no
per-frame Python bookkeeping)? Here's how the field names line up:
| Concept | TrackEval | py-motmetrics | motrics (native) |
|---|---|---|---|
| Matched detections (incl. switches) | CLR_TP |
num_detections |
ClearMetrics.num_matches |
| False positives | CLR_FP |
num_false_positives |
ClearMetrics.num_false_positives |
| Misses | CLR_FN |
num_misses |
ClearMetrics.num_misses |
| Identity switches | IDSW |
num_switches |
ClearMetrics.num_switches |
| MOTA / MOTP | MOTA / MOTP |
mota / motp |
ClearMetrics.mota / .motp |
| Identity TP / FP / FN | IDTP/IDFP/IDFN |
idtp/idfp/idfn |
IdentityMetrics.idtp/.idfp/.idfn |
| IDF1 / IDP / IDR | IDF1/IDP/IDR |
idf1/idp/idr |
IdentityMetrics.idf1/.idp/.idr |
| HOTA / DetA / AssA / LocA | HOTA/DetA/AssA/LocA |
— (not in motmetrics) | HotaMetrics.hota/.deta/.assa/.loca |
Benchmarks
On real MOT17 data, release build:
| motrics vs… | CLEAR + Identity | With HOTA |
|---|---|---|
| TrackEval | ~3–4× | ~6× |
| py-motmetrics | ~14× | — |
Numbers are illustrative and machine-dependent. See
benchmarks/README.md for methodology and how to run
it yourself.
Roadmap
- Project scaffolding (build, lint, packaging, CI)
- Bounding-box IoU + assignment (Hungarian/greedy) primitives
- CLEAR metrics (MOTA, MOTP, ID switches, FP/FN)
- Identity metrics (IDF1 / IDP / IDR)
- HOTA (DetA, AssA, alpha sweep)
- MOTChallenge ingest + integration tests
- TrackEval numeric parity tests (CLEAR / Identity / HOTA)
- Benchmark & parity infrastructure vs TrackEval and py-motmetrics,
on real MOTChallenge data, validated in CI.
- Zero-copy NumPy input path (see "broaden core inputs" below).
- Replace TrackEval / py-motmetrics, not just benchmark against them:
- Precomputed-similarity core inputs (
compute_clear_from_similarity,compute_identity_from_similarity) — the piececompat.motmetricsneeded, and the first slice of "broaden core inputs" below. -
motrics.compat.motmetrics— a drop-inMOTAccumulatorreplacement. - Migration guide + metric-name map (see above).
- MOTChallenge ingest with TrackEval-parity preprocessing
(
load_motchallenge_gt+preprocess_motchallenge: distractor-class removal, pedestrian-only, "do not consider" rows dropped) — validated against TrackEval's ownget_preprocessed_seq_data, and now what the real-data benchmark uses. The enabling piece forcompat.trackeval. -
motrics.compat.trackeval— a drop-in for TrackEval'sEvaluator/datasets.MotChallenge2DBox/metrics.{HOTA,CLEAR,Identity}(same class names, config keys, and result shape); see above for what's out of scope (parallel eval, fullCLEARfield set, other metrics). - Broaden core inputs further —
box_format="xywh"alongside the defaultxyxy, and a zero-copy read path for contiguous(N, 4)float64 NumPy arrays, oncompute_clear/compute_identity/compute_hota/iou_matrix/match_boxes.numpyis now the one required runtime dependency of the core.
- Precomputed-similarity core inputs (
- Ergonomic native API —
Framesbundles one side's ids/boxes (ground truth or predictions) so the common case isn't four parallel lists retyped per metric;evaluate()takes twoFramesand returns CLEAR + Identity + HOTA together, computing the gt/pred similarity matrix once and sharing it across all three (compute_clear/compute_identity/compute_hotacalled separately each build their own). The flatcompute_clear/compute_identity/compute_hotafunctions are unchanged, for single-metric use.- Streaming accumulator —
update()per frame,compute()at the end, the shape both py-motmetrics and torchmetrics use, for online evaluation or sequences too large to hold fully in memory. Deferred: HOTA's alpha sweep is naturally a whole-sequence batch computation, so incrementalizing it correctly is real design work, not a thin wrapper around the existing core — worth doing once the dataset-adapter layer below has settledFramesas the shape adapters produce, not before.
- Streaming accumulator —
- Pluggable dataset-adapter layer — one metric core, one small adapter per
benchmark (ingest + preprocessing + similarity), added incrementally:
- DanceTrack — no adapter code needed. Its
gt.txt/results format is byte-for-byte MOTChallenge's (fixed class=1/consider=1 columns), and TrackEval evaluates it via plainMotChallenge2DBoxwith no DanceTrack-specific preprocessing branch.load_motchallenge_gt+preprocess_motchallengealready handle it — confirmed by a round-trip test against TrackEval's real preprocessing and metrics. - KITTI 2D-box — genuinely different format (space-separated,
DontCareclass, 3D fields even for the 2D-box challenge); needs a real parser. - Mask-IoU similarity kernel (KITTI-MOTS, BDD-MOTS, DAVIS) — new Rust core work, not just an adapter; tackle when a mask benchmark is needed.
- 3D similarity kernel (KITTI-3D) — same as above, separate core work.
- DanceTrack — no adapter code needed. Its
Contributing
See CONTRIBUTING.md for the development setup, tooling, and checks to run before opening a PR.
License
MIT © 2026 Kevin Serrano
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file motrics-0.1.0.tar.gz.
File metadata
- Download URL: motrics-0.1.0.tar.gz
- Upload date:
- Size: 178.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: maturin/1.14.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d9e10160766e5692b6167a5b09db4da45c8c147a4922abac44a6d29115dbc2c9
|
|
| MD5 |
6d029d16c2c0c03f6afd5d625cb38631
|
|
| BLAKE2b-256 |
6fec790bef5a4e183f223cf55a128f0ce2a0b27e84a100613a1fe8385cea1a43
|
File details
Details for the file motrics-0.1.0-cp310-abi3-win_amd64.whl.
File metadata
- Download URL: motrics-0.1.0-cp310-abi3-win_amd64.whl
- Upload date:
- Size: 290.5 kB
- Tags: CPython 3.10+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: maturin/1.14.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
beebe3a1cecb970c5838c7232b3779120a111ea4e974e571fe2d5bc1148bc22e
|
|
| MD5 |
0dd8abb353b7467f5d0807699e8bebb9
|
|
| BLAKE2b-256 |
9910e958f0325cdba2d9f84d041259cd7d1af9f424f833fce1658ebf52044f41
|
File details
Details for the file motrics-0.1.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: motrics-0.1.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 413.4 kB
- Tags: CPython 3.10+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: maturin/1.14.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
be7283616aa923178de8ad9ddeaa73ddf0ab9e370bf26167bc853867ab532e06
|
|
| MD5 |
f401300656d2699110d2160b4d45bece
|
|
| BLAKE2b-256 |
c7186e582d639690dd38b430ecc323ad81c0d5f0e5b1377ce18697c2f776f94b
|
File details
Details for the file motrics-0.1.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: motrics-0.1.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 401.4 kB
- Tags: CPython 3.10+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: maturin/1.14.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dfc195b86e0dc3506338d9ee466f18816735ada333418dff450400b3ea9debbf
|
|
| MD5 |
9f67de51cbff88f82fab4a547a9b8ed2
|
|
| BLAKE2b-256 |
4c882e648c5753bf285f5839b73c9c3989451e601bbbb9578a0f1ea9803adfcc
|
File details
Details for the file motrics-0.1.0-cp310-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: motrics-0.1.0-cp310-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 377.4 kB
- Tags: CPython 3.10+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: maturin/1.14.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
befa2bb082cb832e15d5e4a62f8254378b0331ec5acc1166cc95dfe4d1115d37
|
|
| MD5 |
41671a750ee6d9da412318818834a670
|
|
| BLAKE2b-256 |
18f754844b785b0a8a0897d27595f7d34a021205c65328c37bb3c8192935b779
|
File details
Details for the file motrics-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: motrics-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 386.5 kB
- Tags: CPython 3.10+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: maturin/1.14.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
03066d561cf762e009287e3b8c5611d39db823ce39d6a6d1a1205ab6e8470cf1
|
|
| MD5 |
159e5d63e322da82352cb64853cd96f8
|
|
| BLAKE2b-256 |
6205f90a6d2c48d1233ec5fd99e7d7421ba629de85561f3257003480046a4087
|