Deployment-oriented evaluation framework for 3D pedestrian multi-object tracking.
tracker_eval provides a reproducible framework for benchmarking 3D pedestrian trackers under fixed, shared detections. In addition to standard metrics such as HOTA, it evaluates deployment-relevant tracker behavior through interpretable capability profiles covering trajectory initialization, continuation through missed detections, post-gap identity recovery, close-neighbour continuity, and tracker runtime under varying input load.
The repository contains the complete pipeline for running trackers on the JRDB dataset, generating trajectory outputs, evaluating them with the bundled corrected TrackEval runtime, computing the capability profiles, and visualizing the resulting metrics.
For reproducible comparison, tracker_eval includes compact evaluation runtimes for seven trackers: AB3DMOT, CBMOT, ELPTNet, FastPoly, GNN-PMB, SimpleTrack, and PedRefTrack. These implementations are provided as ready-to-run reference methods for the evaluation protocol and are not intended to replace their respective upstream repositories.
The deployable ROS 2 implementation of PedRefTrack is maintained separately. tracker_eval contains the corresponding pure-Python tracking core and evaluation adapter, while the PedRefTrack repository provides the ROS 2 node for tracking Detection3DArray inputs.
Python 3.10 or newer is required.
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e .For the notebooks, install the optional tools:
python -m pip install -e '.[notebooks]'GNN-PMB additionally needs its Murty C++ extension. The repository contains the architecture-neutral source and does not ship the binary.
sudo apt install cmake libeigen3-dev
tracker-eval-build gnnpmbThe helper performs an out-of-source CMake build with the active Python interpreter and installed pybind11, then places _murty*.so beside the GNN-PMB package. The generated binary and build directory are ignored by Git.
| CLI name | Compact runtime included | Authoritative upstream |
|---|---|---|
ab3dmot |
yes | xinshuoweng/AB3DMOT |
cbmot |
yes | cogsys-tuebingen/CBMOT |
elptnet |
yes, box tracker path | jinzhengguang/ELPTNet |
fastpoly |
yes | lixiaoyu2000/FastPoly |
gnnpmb |
yes; build Murty once | chisyliu/GnnPmbTracker |
simpletrack |
yes | tusen-ai/SimpleTrack |
pedreftrack |
yes | SCAI-Lab/PedRefTrack |
These are compact protocol adapters and required runtime files, not forks intended to replace the upstream projects. Config files used by each adapter are retained in tracker_eval/trackers/implementations/<tracker>/.
Each split is a directory containing one JSON file per sequence:
<split_root>/
├── detections_3D/
│ └── <sequence>.json
└── labels_3d/
└── <sequence>.json
For global-coordinate runs, odometry is read from:
<odometry_root>/<split_name>/odometry/<sequence>.csv
The CSV columns are timestamp_ns,x,y,z,qx,qy,qz,qw. Row i is deliberately matched to frame index i; timestamps are used as tracker timestamps but not to look up a pose row.
The accepted top-level detection containers are detections, dets, or predictions. GT accepts labels, annotations, frames, or data. A direct frame-to-list mapping is also accepted. Frame keys may be integer-like strings or names such as 000123.pcd.
{
"detections": {
"000000.pcd": [
{
"box": {
"cx": 2.1, "cy": -0.4, "cz": 0.85,
"l": 0.5, "w": 0.5, "h": 1.7, "rot_z": 0.0
},
"score": 0.91,
"label_id": "pedestrian:-1"
}
]
}
}box may instead be the list [cx, cy, cz, l, w, h, rot_z]. The internal convention is a center-based box in metres/radians with x forward, y left, z up, length along x, width along y and yaw about +z. Detection score and label_id are optional; detections default to track ID -1. GT uses label_id: "pedestrian:<integer>" as the persistent identity. An explicit integer track_id overrides the ID parsed from label_id.
tracker_eval/data/jrdb_io.py implements this parsing through load_jrdb_detections_3d, load_jrdb_labels_3d and _parse_entry_to_detection.
Ordinary trackers iterate the frame keys present in the detection JSON. GT is loaded only for a tracker that exposes step_with_gt; in this repository that is PedRefTrack. PedRefTrack no_gt does not use labels, while gt_assisted does. Pseudo-detection JSON retains every source GT frame key, including frames whose corruption produces an empty detection list.
There are two representations of the same ground truth, not two independent annotations:
labels_3d/<sequence>.jsonis the source/runner representation. It is used to generate pseudo detections and only supplied to PedRefTrack ingt_assistedmode.- TrackEval consumes KITTI/JRDB text under
<gt_folder>/label_02/<sequence>.txt, plusevaluate_tracking.seqmap.<split>. Generate it from the JSON withtracker-eval-prepare-gt.
The output of a tracker is not JSON ground truth; it is the same KITTI/JRDB text convention used by the evaluator. Preparing evaluation GT therefore converts the source JSON without changing the underlying annotations.
The important transforms are centralized and applied identically to detections and source GT:
| Stage | Operation | Implementation |
|---|---|---|
| local → global | p_global = R(q) @ p_local + t |
transform_box7_local_to_global in common/odometry_transform.py |
| JRDB yaw compensation | yaw_global = yaw_local - ego_yaw |
same function; this empirical sign is required by the existing JRDB pipeline |
| sequence transform | pose row selected by integer frame key | load_odometry_csv, transform_frame_data_to_global, transform_sequence_to_global |
| internal → TrackEval | x=-cy, y=-cz+h/2, z=cx, w=w, h=h, d=l, yaw=(-rot_z) mod 2π |
trackeval_xyzwhd_from_internal_center in export/jrdb_kitti_writer.py |
The odometry CSV is treated as T_world_sensor. Global transformation happens in runner/run_split.py before a tracker step; tracker-eval-prepare-gt --global-coords applies the same transform when building global evaluation GT.
write_sequence_kitti_txt writes one object per line, sorted by (frame, track_id):
frame track_id class truncated occluded alpha x1 y1 x2 y2 x y z w h d yaw [score]
Columns 6–9 are dummy 2D boxes for 3D-only evaluation. Columns 10–16 are the TrackEval xyzwhd box after the mapping above. KITTI/JRDB output is mandatory because it is the input to every evaluation path. Duplicate IDs within a frame are rejected.
Tracker runs are saved as:
<out_root>/<tracker_output_name>/<split_name>/
├── data/<sequence>.txt
├── frame_stats/<sequence>.csv
├── runtime_summary.csv
└── runtime_summary.json
Sequential mode records real per-frame step timings and is the mode to use for runtime measurements. Parallel mode runs sequences in a shared process pool for throughput; timing and per-frame profiling are intentionally disabled because concurrent workers make those measurements scientifically unreliable.
Run one tracker sequentially (valid runtime statistics):
tracker-eval \
--split_root /data/JRDB/test \
--split_name test \
--out_root /data/tracker_outputs \
--trackers pedreftrack \
--pedreftrack_modes no_gt \
--global_coords \
--odometry_root /data/JRDB/odometryRun several trackers concurrently (throughput, not timing):
tracker-eval \
--split_root /data/JRDB/test \
--split_name test \
--out_root /data/tracker_outputs \
--trackers cbmot elptnet fastpoly gnnpmb simpletrack pedreftrack \
--parallel --num_workers 12 \
--global_coords --odometry_root /data/JRDB/odometryGenerate all clean/dropout/instability/combined pseudo detections:
tracker-eval-generate-pseudo \
--split_root /data/JRDB/test \
--split_name test \
--odometry_root /data/JRDB/odometry \
--spec tracker_eval/cli/pseudo_det_spec.yamlRun trackers over the manifest conditions:
tracker-eval-pseudo \
--manifest /data/JRDB/test/detections_3D_pseudo/manifest.json \
--out_root /data/tracker_outputs \
--trackers cbmot fastpoly pedreftrack \
--pedreftrack_modes no_gt \
--parallel --num_workers 12 \
--global_coordsPseudo runs use one canonical folder order: <tracker>__global[_pedreftrack_mode]_<variant>, for example fastpoly__global_instability_L1 or pedreftrack__global_no_gt_clean. The protocol evaluator preserves these names in its result directory.
Prepare global evaluation GT and evaluate normal tracker outputs:
tracker-eval-prepare-gt \
--labels-dir /data/JRDB/test/labels_3d \
--gt-folder /data/eval_gt_global \
--split test \
--global-coords --odometry-root /data/JRDB/odometry
tracker-eval-evaluate \
--gt-folder /data/eval_gt_global \
--trackers-folder /data/tracker_outputs \
--output-folder /data/evaluation_results \
--split testEvaluate all pseudo-detection conditions against that same GT:
tracker-eval-protocol pseudo \
--trackers-dir /data/tracker_outputs \
--gt-folder /data/eval_gt_global \
--output-dir /data/pseudo_results \
--split test --workers 12Compute the tracker capability tables with one command:
tracker-eval-protocol capabilities \
--trackers-base-dir /data/tracker_outputs \
--gt-folder /data/eval_gt_global \
--local-gt-folder /data/eval_gt_local \
--detections-dir /data/JRDB/test/detections_3D \
--output-dir /data/protocol_results \
--trackers pedreftrack__global_gt_assisted,pedreftrack__global_no_gt,cbmot__global,fastpoly__global \
--reference-tracker pedreftrack__global_gt_assisted \
--split test --workers 8This command builds/reuses common event caches, per-tracker capability/HOTA caches and final tables. The internal modules are protocol/hota_cache.py, profiles.py and results.py; users should normally call only tracker-eval-protocol.
The public command defaults to the corrected two-second RAL protocol:
- gap prediction is evaluated through
2.0seconds; - recovery bins are
1,4,7,10,13,16,19,22,25,28,31frames; - the remaining IoU, continuity, initialization, NN-bin, quantile and bootstrap defaults match the settings used for the corrected RAL tables.
tables/initialization_latency_summary.csv contains all initialization views in one run. Use event_type == "trajectory_start" for genuine trajectory initialization only, or event_type == "all" for trajectory initialization plus post-gap reacquisition when all evaluated trackers already lost the trajectory. The plotting notebook exposes this as INITIALIZATION_EVENT_TYPE; it does not require rerunning the protocol.
Changing a protocol-defining setting for an existing output directory is rejected to protect cached results. Use a new output directory, or pass --force when replacement of the old protocol cache is intentional.
Plot saved results by opening:
notebooks/plot_tracker_results.ipynbnotebooks/plot_pseudo_detection_results.ipynbnotebooks/ral_visualization_figures_jrdb.ipynb
Set the path variables in the first configuration cell, then run all cells. A command-focused walkthrough is in docs/PIPELINE.md.
tracker_eval/ runner, adapters, compact tracker runtimes
trackeval/ corrected compact JRDB 3D evaluation runtime
tracker_eval/protocol/ reusable cache/profile/result stages
notebooks/ result and manuscript plotting
third_party/ TrackEval license and upstream notice
See THIRD_PARTY_NOTICES.md for upstream references. Project-authored code is MIT licensed; retained third-party files remain under their upstream terms.