ARC-SORT
Adaptive, Robust, Compensated Multi-Object Tracking with Interacting Multiple Models
ARC-SORT is a tracking-by-detection multi-object tracker for fast, non-linear motion under a moving camera, such as athletes in sports broadcasts. Nothing is trained. It works with any detector and runs at about 50 FPS on a laptop RTX 3060.
The idea: the tracker measures its own uncertainty, and that measurement decides how it acts.
- IMM Kalman filter (constant velocity / constant acceleration / coordinated turn) gives each track a predictive covariance.
- Uncertainty-adaptive Expansion IoU: the less reliable a track's motion prediction, the larger the region in which a detection may match it.
- Deferred identity arbitration: when two tracks cross and the assignment was a close call, the pair stays under review; at most one corrective identity swap is applied once post-crossing appearance evidence shows the first decision was wrong.
- Camera-motion compensation, BYTE two-stage matching, OC-SORT observation-centric recovery, and appearance (ReID) costs.
- Offline tracklet merge that links players who leave the frame by appearance alone (no motion gate, since extrapolating motion over seconds off-screen carries no information).
Results
Test-set numbers are from the official evaluation servers (CodaBench).
| Benchmark (test) | HOTA | AssA | DetA | MOTA | IDF1 | IDs |
|---|---|---|---|---|---|---|
| SportsMOT | 81.82 | 76.24 | 87.84 | 97.09 | 88.47 | 1414 |
| SportsMOT, Deep-EIoU's detections + embeddings | 80.31 | 74.14 | 87.04 | 96.65 | 86.92 | 1320 |
| DanceTrack | 62.1 | 48.2 | 80.3 | 92.4 | 66.5 | — |
As of August 2026, SportsMOT has the fewest identity switches of all 69 leaderboard entries. On Deep-EIoU's own inputs, ARC-SORT is +3.1 HOTA over Deep-EIoU (77.2).
Validation numbers, reproducible locally (pooled TrackEval):
| Benchmark (val) | HOTA | AssA | IDF1 |
|---|---|---|---|
| SportsMOT | 84.96 | 82.96 | 92.79 |
| DanceTrack | 60.62 | 47.44 | 64.80 |
Installation
Python 3.10+ (developed on 3.12).
git clone https://github.com/Athlitix/arcsort.git && cd arcsort
pip install -e ".[reid,eval]"
For the GPU stages (detection, embeddings), install a CUDA build of PyTorch first; see pytorch.org. Tracking from cached inputs runs on CPU.
External code (clone into external/, which is gitignored):
| Repo | Needed for | Command |
|---|---|---|
| TrackEval | val evaluation | git clone https://github.com/JonathonLuiten/TrackEval external/TrackEval |
| YOLOX | regenerating detections | git clone https://github.com/Megvii-BaseDetection/YOLOX external/YOLOX |
| Deep-OC-SORT | converting DanceTrack ReID weights (bundles FastReID) | git clone https://github.com/GerardMaggiolino/Deep-OC-SORT external/Deep-OC-SORT |
Quick start: track with any detector
ARC-SORT takes boxes and scores from any detector and returns boxes with persistent IDs:
from arcsort import ARCTracker
tracker = ARCTracker.from_preset("sportsmot") # motion-only, runs on CPU
# tracker = ARCTracker.from_preset("sportsmot", reid_weights="weights/sportsmot/sports_osnet_traced.pt")
for frame in video: # BGR numpy image
dets = my_detector(frame) # (N, 6): x1, y1, x2, y2, score, class_id
tracks = tracker.update(dets, frame) # (M, 5): x1, y1, x2, y2, track_id
- Presets:
sportsmot(recommended default for sports footage),dancetrack, andsportsmot_deepeiou_inputs. Override any key, e.g.ARCTracker.from_preset("sportsmot", track_buffer=60, eiou={"adaptive_scale": 3.0}). frameis used for camera-motion compensation and ReID crops. Pass it every frame (oruse_cmc=Falsefor a static camera).- ReID needs the
reidextra, a CUDA GPU and converted weights (see Weights). Without it the tracker runs motion-only. - Score thresholds: the defaults (
track_high_thresh0.6,new_track_thresh0.7) worked best for every detector we tested. Lowering them reduced HOTA for ARC-SORT and for the baselines alike. Run the detector itself with a low confidence floor (e.g. 0.05) so that BYTE's second stage sees low-score boxes. - supervision users:
arcsort.interop.from_supervision(detections)andto_supervision(tracks)convert to and fromsv.Detections(pip install arcsort[viz]). - Start a new video with
tracker.reset(). Several trackers can run in one process (e.g. one per camera); each numbers its own tracks.
A detector factory is included for common backends (pip install arcsort[yolo] for Ultralytics;
RF-DETR and transformers are installed separately):
from arcsort.detection import create_detector
detector = create_detector("yolo11x.pt", imgsz=1280, conf_thresh=0.05, classes=[0])
# also: "rfdetr-large", "PekingU/rtdetr_v2_r101vd", "rtdetr-x.pt" (use imgsz=640), YOLOX
dets = detector.detect(frame) # (N, 6), class ids in COCO-80 order (person = 0)
Tested detectors
ARC-SORT was run unchanged (from_preset("sportsmot") with live ReID and CMC) behind the
detectors below. The test used three SportsMOT validation clips (basketball, football, volleyball;
1,616 frames). Every tracker got identical detections. Baselines are the Roboflow
trackers implementations with default settings.
| Detector | Library | ByteTrack | BoT-SORT | ARC-SORT online | ARC-SORT + offline merge |
|---|---|---|---|---|---|
| YOLOv8x | Ultralytics | 57.0 | 59.4 | 62.2 | 66.2 |
| YOLOv10x | Ultralytics | 61.8 | 63.9 | 61.6 | 65.0 |
| YOLO11x | Ultralytics | 57.5 | 60.3 | 58.6 | 62.6 |
| YOLO12x | Ultralytics | 56.7 | 59.8 | 60.9 | 65.7 |
| YOLO26x | Ultralytics | 57.0 | 57.0 | 59.8 | 64.3 |
| YOLOv13x | iMoonLab fork | 57.2 | 58.6 | 60.2 | 61.5 |
| RT-DETRv2-R101 | transformers | 57.9 | 58.7 | 59.8 | 64.0 |
| RF-DETR-L | rfdetr | 65.2 | 65.9 | 67.6 | 71.6 |
| YOLOX-X (SportsMOT-trained) | YOLOX | 77.0 | 78.6 | 81.4 | 84.7 |
HOTA; bold marks the best online and best overall result per row. This is a compatibility check,
not a benchmark: three clips are too few to rank trackers. COCO detectors also detect referees
and spectators, who are not labelled in SportsMOT, so their absolute scores are low for every
tracker. Run Ultralytics RT-DETR at its training size (imgsz=640); at 1280 it detects only a
third of the players.
Reproducing the paper
One command per row:
scripts/reproduce.sh sportsmot val # prints HOTA 84.957
scripts/reproduce.sh dancetrack val # prints HOTA 60.617
scripts/reproduce.sh sportsmot test # writes outputs/arcsort_sportsmot_test.zip
scripts/reproduce.sh dancetrack test # writes outputs/arcsort_dancetrack_test.zip
scripts/reproduce.sh sportsmot_deepeiou test # Deep-EIoU's inputs
Upload test zips to the SportsMOT or DanceTrack CodaBench server.
The script runs five stages and skips any stage whose output already exists:
- detections: YOLOX-X, one file per sequence (GPU)
- camera motion: one affine transform per frame (CPU)
- embeddings: ReID features for every detection (GPU)
- tracking: online tracker → interpolation (gaps ≤ 10 frames) → offline tracklet merge (CPU)
- evaluation (val) or packaging (test)
Fast path: released caches
The v0.1.0 release contains our cached
detections and camera-motion transforms (arcsort-v0.1.0-caches.tar.gz, ~40 MB). Extract it at
the repository root and stages 1–2 are skipped:
tar xzf arcsort-v0.1.0-caches.tar.gz # creates cache/detections/ and cache/cmc/
cache/
detections/{sportsmot,dancetrack}_{val,test}/<seq>.txt
cmc/{sportsmot,dancetrack}_{val,test}/<seq>.npy
embeddings/{sportsmot,dancetrack}_{val,test}/<seq>.npz # computed by stage 3 (GPU)
The release also contains the result files we submitted to the evaluation servers
(sportsmot_test.zip, dancetrack_test.zip, sportsmot_deepeiou_test.zip). Put them in a folder,
e.g. reference/, and check your run against them:
scripts/regression_gate.sh cache reference
Detector and ReID outputs can differ in the last bits across GPU models and library versions, and a few changed embeddings can flip a few association decisions. On other hardware, expect numbers within noise of the paper, not byte-identical files. Our results were produced on an RTX 3060 Laptop GPU with torch 2.5.1+cu121, numpy 2.4.3, opencv-python 4.13.0, scipy 1.17.1 and lap 0.5.13. On that setup, every stage regenerates byte-identically from the raw upstream downloads.
Datasets
Download SportsMOT and DanceTrack and arrange them in MOTChallenge layout:
data/
SportsMOT/{val,test}/<seq>/{img1/,gt/,seqinfo.ini}
DanceTrack/{val,test}/<seq>/{img1/,gt/,seqinfo.ini}
Frames are needed for stages 1–3 only. Val evaluation needs gt/.
Weights
Weights are not redistributed here. Download them from the original authors:
File in weights/ |
Used for | Source |
|---|---|---|
sportsmot/yolox_x_sports_train.pth.tar |
SportsMOT val detections (train-split checkpoint, so no val frame was seen) | MixSort model zoo |
sportsmot/yolox_x_sports_mix.pth.tar |
SportsMOT test detections (train+val checkpoint) | MixSort model zoo |
ocsort_dance_model.pth.tar |
DanceTrack detections | OC-SORT model zoo |
sportsmot/sports_osnet_traced.pt |
SportsMOT ReID | converted from Deep-EIoU's sports_model.pth.tar-60 (Deep-EIoU), see below |
dance_sbs_S50_traced.pt |
DanceTrack ReID | converted from FastReID dance_sbs_S50.pth (Deep-OC-SORT), see below |
ReID conversion to TorchScript (one-time, needs a CUDA GPU):
# SportsMOT: sports-domain OSNet from Deep-EIoU
python tools/extract_state_dict.py --input sports_model.pth.tar-60 \
--output weights/sportsmot/sports_osnet_clean.pth
python tools/convert_osnet_weights.py --input weights/sportsmot/sports_osnet_clean.pth \
--output weights/sportsmot/sports_osnet_traced.pt
# DanceTrack: FastReID SBS-S50 (needs external/Deep-OC-SORT)
python tools/convert_reid_weights.py --input dance_sbs_S50.pth \
--output weights/dance_sbs_S50_traced.pt --config dancetrack
extract_state_dict.py loads the training checkpoint with weights_only=False, so only run it
on files from the official release.
For the Deep-EIoU-inputs row, download Deep-EIoU's released SportsMOT test detections and
embeddings into external/Deep-EIoU/{detection,embedding}/ (or set DEEP_EIOU_ASSETS).
Repository layout
arcsort/ tracker package (IMM-KF, EIoU, CMC, ReID, arbitration, OC-SORT components)
arcsort/configs/ presets = the exact configurations behind each paper row
configs/detector/ YOLOX experiment files
tools/ caching, tracking, post-processing, evaluation, FPS benchmark, video demo
scripts/ reproduce.sh, regression_gate.sh
tests/ unit tests (pytest)
third_party/ vendored OSNet architecture (MIT)
docs/ project page (GitHub Pages)
Citation
@article{adari2026arcsort,
title = {{ARC-SORT}: Adaptive, Robust, Compensated Multi-Object Tracking with Interacting Multiple Models},
author = {Adari, Girish Kumar},
journal = {arXiv preprint arXiv:2610.22306},
year = {2026}
}
License
Apache License 2.0; see LICENSE and NOTICE. ARC-SORT builds on OC-SORT, ByteTrack, BoT-SORT and Deep OC-SORT; see THIRD_PARTY_NOTICES.md. Pretrained weights and datasets are subject to their own licenses and terms, which may restrict commercial use independently of this code's license.
Metadata
Release files for arcsort 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| arcsort-0.1.0.tar.gz | 108.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| arcsort-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 181.5 kB
Release files / arcsort-0.1.0.tar.gz
| Download URL | arcsort-0.1.0.tar.gz |
|---|---|
| Size | 108.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
da5ba1a2184e533d5decc675f381f31ea1f6a91acf9ed069ce998313a021dab9
|
|
BLAKE2b-256 checksum How to use checksums |
728419ba9533027494be163cd2505314e531e3b3b3d9045a6c2d3473b15aec3e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency logRelease files / arcsort-0.1.0-py3-none-any.whl
| Download URL | arcsort-0.1.0-py3-none-any.whl |
|---|---|
| Size | 72.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ba69433c97bbf979ce7f355a17d4f84560e860413468c3ccd444949497e25018
|
|
BLAKE2b-256 checksum How to use checksums |
4d47171fefa90762eee390de6fc8e63382845fecfdef6783dc4d93ff3ca47ac5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency log