Skip to main content

An extremely fast MOT and HOTA metrics library, written in Rust.

Project description

motrics

CI License Python 3.10+ Ruff

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, numpy the only required runtime dependency.
  • 🔢 Flexible box inputxyxy or xywh, 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 NotImplementedError naming exactly what's missing, rather than a silently wrong number.
  • See python/motrics/compat/motmetrics/ for what else differs (e.g. no events/mot_events DataFrame).

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=False and BENCHMARK="MOT15" (raise at construction); CLEAR fields beyond MOTA/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/VACE metrics.
  • 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 piece compat.motmetrics needed, and the first slice of "broaden core inputs" below.
    • motrics.compat.motmetrics — a drop-in MOTAccumulator replacement.
    • 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 own get_preprocessed_seq_data, and now what the real-data benchmark uses. The enabling piece for compat.trackeval.
    • motrics.compat.trackeval — a drop-in for TrackEval's Evaluator/datasets.MotChallenge2DBox/metrics.{HOTA,CLEAR,Identity} (same class names, config keys, and result shape); see above for what's out of scope (parallel eval, full CLEAR field set, other metrics).
    • Broaden core inputs further — box_format="xywh" alongside the default xyxy, and a zero-copy read path for contiguous (N, 4) float64 NumPy arrays, on compute_clear/compute_identity/ compute_hota/iou_matrix/match_boxes. numpy is now the one required runtime dependency of the core.
  • Ergonomic native API — Frames bundles one side's ids/boxes (ground truth or predictions) so the common case isn't four parallel lists retyped per metric; evaluate() takes two Frames and returns CLEAR + Identity + HOTA together, computing the gt/pred similarity matrix once and sharing it across all three (compute_clear/compute_identity/ compute_hota called separately each build their own). The flat compute_clear/compute_identity/compute_hota functions 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 settled Frames as the shape adapters produce, not before.
  • 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 plain MotChallenge2DBox with no DanceTrack-specific preprocessing branch. load_motchallenge_gt + preprocess_motchallenge already handle it — confirmed by a round-trip test against TrackEval's real preprocessing and metrics.
    • KITTI 2D-box — genuinely different format (space-separated, DontCare class, 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.

Contributing

See CONTRIBUTING.md for the development setup, tooling, and checks to run before opening a PR.

License

MIT © 2026 Kevin Serrano

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

motrics-0.1.0.tar.gz (178.1 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

motrics-0.1.0-cp310-abi3-win_amd64.whl (290.5 kB view details)

Uploaded CPython 3.10+Windows x86-64

motrics-0.1.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (413.4 kB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ x86-64

motrics-0.1.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (401.4 kB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ ARM64

motrics-0.1.0-cp310-abi3-macosx_11_0_arm64.whl (377.4 kB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

motrics-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl (386.5 kB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

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

Hashes for motrics-0.1.0.tar.gz
Algorithm Hash digest
SHA256 d9e10160766e5692b6167a5b09db4da45c8c147a4922abac44a6d29115dbc2c9
MD5 6d029d16c2c0c03f6afd5d625cb38631
BLAKE2b-256 6fec790bef5a4e183f223cf55a128f0ce2a0b27e84a100613a1fe8385cea1a43

See more details on using hashes here.

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

Hashes for motrics-0.1.0-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 beebe3a1cecb970c5838c7232b3779120a111ea4e974e571fe2d5bc1148bc22e
MD5 0dd8abb353b7467f5d0807699e8bebb9
BLAKE2b-256 9910e958f0325cdba2d9f84d041259cd7d1af9f424f833fce1658ebf52044f41

See more details on using hashes here.

File details

Details for the file motrics-0.1.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for motrics-0.1.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 be7283616aa923178de8ad9ddeaa73ddf0ab9e370bf26167bc853867ab532e06
MD5 f401300656d2699110d2160b4d45bece
BLAKE2b-256 c7186e582d639690dd38b430ecc323ad81c0d5f0e5b1377ce18697c2f776f94b

See more details on using hashes here.

File details

Details for the file motrics-0.1.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for motrics-0.1.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 dfc195b86e0dc3506338d9ee466f18816735ada333418dff450400b3ea9debbf
MD5 9f67de51cbff88f82fab4a547a9b8ed2
BLAKE2b-256 4c882e648c5753bf285f5839b73c9c3989451e601bbbb9578a0f1ea9803adfcc

See more details on using hashes here.

File details

Details for the file motrics-0.1.0-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for motrics-0.1.0-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 befa2bb082cb832e15d5e4a62f8254378b0331ec5acc1166cc95dfe4d1115d37
MD5 41671a750ee6d9da412318818834a670
BLAKE2b-256 18f754844b785b0a8a0897d27595f7d34a021205c65328c37bb3c8192935b779

See more details on using hashes here.

File details

Details for the file motrics-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for motrics-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 03066d561cf762e009287e3b8c5611d39db823ce39d6a6d1a1205ab6e8470cf1
MD5 159e5d63e322da82352cb64853cd96f8
BLAKE2b-256 6205f90a6d2c48d1233ec5fd99e7d7421ba629de85561f3257003480046a4087

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page