Changelog
All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
2.7.0 — 2026-11-05
🚀 Added
-
Optional BoT-SORT appearance Re-ID —
BoTSORTTracker(reid_model=..., reid_config=ReIDConfig(...))accepts an encoder object or model-source string. Thetrackers[reid]extra pins the standalone model package toreid==0.1.0and includes Matplotlib. Embeddings augment the first and unconfirmed association stages; low-confidence second-stage matching remains geometry-only. Re-ID is off by default, requires a BGR frame on everyupdate(), and is integrated into BoT-SORT only (#511, #655). -
Selectable Re-ID fusion —
ReIDConfig(fusion="botsort")takes the better geometric/appearance score; opt-infusion="adaptive"adds appearance with a capped best-versus-runner-up margin bonus, adapted from Deep OC-SORT's appearance weighting (not a new Deep OC-SORT tracker). Both methods gate appearance with distance0.5 * (1 - cosine)(defaultappearance_threshold=0.25) and standard-IoU proximity (defaultproximity_threshold=0.5). Adaptive defaults areappearance_weight=0.75,adaptive_weight_cap=0.5; its similarity range can reach 2.25, so first/unconfirmed association thresholds need retuning when switching methods (#511). -
Re-ID tuning and analysis — public
trackers.reidhelpers includeReIDEncoder,FeatureBank, embedding extraction, appearance similarity, balanced same/different-identity distance sampling, acceptance rates, ROC AUC, frame-gap sweeps and plots.Tuneradds per-runsearch_space,fixed_defaults, and opt-incache_embeddings=False; conditional nestedreid_config.*search fields activate with a fixed encoder and requireimages_dir.fixed_paramsoverridesfixed_defaults; embedding caching trades memory for fewer encoder calls. CLItrackandtuneaccept--reid.*;trackexposes--tracker.reid_config.*(#511). -
Grouped CLI and YAML configuration —
jsonargparsereplaces the argparse entry point, with--config, grouped--detection.*,--filters.*,--output.*,--show.*and tracker settings. The registry exposes IoU variants, state estimators and McByte mask configuration; hyphens/underscores normalize in option names, and booleans accept explicit values. Python constructor parameter names remain unabbreviated (#413, #552). -
Three cookbooks — Re-ID with appearance-threshold analysis, BoT-SORT camera-motion compensation, and McByte mask tracking are linked from Tutorials. They target 2.7.0 and require the indicated external models, datasets and optional dependencies (#511, #630).
-
Camera motion compensation for
OCSORTTracker— opt-inenable_cmc/cmc_method/cmc_downscale, the parameters BoT-SORT and McByte already expose. Withenable_cmc=Trueand the frame passed toupdate(), the sharedCMCmodule's estimate warps every track's Kalman state, stored observations (OCM/OCR/velocity) and frozen ORU state into the current frame before association. Off by default, so OC-SORT's defaults and benchmark numbers are unchanged. Contributor-reported validation-split runs with YOLOX detections show gains on MOT17 and SportsMOT and a slight HOTA loss on DanceTrack, so leave it off for static cameras (#611). -
trackers benchmark mcbytesubcommand — runs McByte over complete MOT17, DanceTrack, SportsMOT, or SoccerNet-tracking benchmark test sets and writes MOTChallenge-format results, with flags for dataset selection (--dataset), per-dataset detection/image roots (--dataset_roots), device, output location, CMC method/downscale, isolated mask matching, skip-existing, and partial-result retention (#541, #543). -
trackers inspectcommand group — visual validation commands for the mask stack and the tracker that uses it:inspect sam(box-prompted mask generation),inspect cutie(mask propagation),inspect mask-manager(mask lifecycle over a frame range, driven either from command-line boxes or from a MOT ground-truth file), andinspect mcbyte(locked-IoU baseline against full mask-conditioned McByte). Each writes annotated per-frame images into a timestamped run directory (#543). -
BoTSORTTracker.update()accepts a precomputed camera-motion transform — the new keyword-onlycmc_transformargument takes a per-frame 2x3 affine (previous to current frame, theCMC.estimate()convention) from external frame registration and applies it in place of the internal frame-based estimate, regardless ofenable_cmc. Passing bothframeandcmc_transform, or a malformed matrix (non-finite, 3x3 homography, wrong shape), raisesValueErrorbefore any state change;CBIoUTracker.update()acceptscmc_transformbut ignores it with aUserWarning(#610, #609).
⚠️ Compatibility changes
- CLI Python module path changed —
trackers.scriptswas removed; use thetrackersexecutable orpython -m trackers.cli. Direct imports from the old implementation modules require migration. The mask-package shims described below do not cover the removed deep modulestrackers.core.mcbyte.masks.{base,sam,cutie,dummy}(#413, #543). - Dependency floor —
supervision>=0.31.0replaces>=0.26.1; the base installation also addsjsonargparse>=4.48.0,<5. Python remains>=3.10(#413, #639).
🔄 Deprecated
-
Legacy CLI spellings — renamed track/detection/output/show/filter flags, long
minimum_/maximum_CLI parameter prefixes, old list forms and download positional dataset selection warn with removal scheduled for 2.10.0. Use the grouped interface; do not combine a legacy flag with its replacement.--tracker NAMEremains a supported shorthand for--tracker.name NAME(#413, #552). -
Legacy mask exports — the old package-level
MaskGenerator,MaskPropagator,MaskOutput,TrackletSnapshot, and oldmask_manager.MaskManagerretain deprecation shims until 3.0; import fromtrackers.core.masksortrackers.core.masks.managerinstead (#543). -
trackers eval --tracker/--tracker_dirrenamed to--predictions/--predictions_dir— old spellings still parse and emitFutureWarning, will be removed in v2.10.0. Use--predictions/--predictions_dirinstead (#541).
🌱 Changed
-
CLI tracking uses source FPS — a finite positive source frame rate now initializes the tracker unless
--tracker.frame_rateis explicitly set, correcting buffer/lifetime behavior on non-30-FPS inputs. Pass--tracker.frame_rate 30to reproduce the previous CLI setting (#569). -
Lower evaluation and prediction overhead — vectorized Identity ID remapping, reusable contiguous prepared IDs, reduced overlap temporaries and distractor-membership work, direct small diagonal covariance construction, and OC-SORT reuse of the predicted box reduce allocation/computation work. Performance claims remain those of the implementation changes; no fresh benchmark was run for these notes (#574, #576, #577, #578, #579, #580).
-
McByte skips mask-only association work when mask evidence is unavailable — clear-match locking and reduced assignment are preserved, while ambiguity and isolated-candidate matrices are no longer built for the default mask-disabled path (#573).
-
McByte CMC now defaults to
cmc_downscale=6— this aggregate-performance default halves median CMC latency versus factor2on the complete 45-clip, 1280x720 SportsMOT validation split and passes the dataset-level mean/median quality criterion. The benchmark used ground-truth detections with masks disabled; 9/45 clips regressed under the previous strict per-clip gate. Passcmc_downscale=2to preserve the previous conservative behavior. GenericCMCConfigandBoTSORTTrackerremain at2. -
Mask stack moved from
trackers.core.mcbyte.maskstotrackers.core.masks— SAM mask generation, Cutie propagation, andMaskManagerreference no tracker and are not McByte-specific, so they now live beside the trackers rather than inside one. Import fromtrackers.core.masksinstead (#543). -
BoT-SORT / C-BIoU / McByte scale-aware noise refresh avoids full bbox decoding — the default center-width-height state now reconstructs only the corner pairs needed for width and height while preserving the previous floating-point operation order and output bits.
-
BoTSORTTrackletandMcByteTrackletshare their scale-aware noise machinery — the byte-identical_SIGMA_*constants,_build_*_noise/_set_scale_aware_noise/_refresh_*_noise_from_statemethods, and the_clamp_*helpers now live once intrackers.utils.scale_aware_tracklet.ScaleAwareNoiseTracklet, with_diagonal_matrixmoved totrackers.utils.matrix. Both tracklets subclass the new base; no public API or behavior change (#596). -
MOT ground-truth class handling is configurable during evaluation preprocessing — scored and distractor classes now come from a
MOTClassConfig(themot17default, amot20preset that treats class 6 as a distractor, or a custom config) instead of hard-coded MOT17 constants. Default MOT17 behavior is unchanged. The config is accepted asclass_configbyevaluate_mot_sequenceandevaluate_mot_sequences(default"mot17") and as--class_configontrackers eval, and aUserWarningfires when a MOT20 sequence is scored with themot17preset (#597, #598).
🔧 Fixed
- Evaluation rejects unknown metric families before file I/O — use
CLEAR,HOTA, orIdentity; the single-sequence path no longer silently returns empty metrics for unsupported names (#598). evaluate_mot_sequencesno longer discovers hidden directories as MOT-layout sequences — the hidden-entry filter in sequence discovery testedp.name, which is alwaysgt.txtfor the<seq>/gt/gt.txtlayout, so it never excluded anything. A hidden directory next to the sequences that contains agt/gt.txt, such as a hidden copy of a sequence left by a backup or sync tool, was evaluated as a sequence, and because no prediction file matches it the whole benchmark aborted withFileNotFoundError: Tracker file not found: .../.MOT17-02-backup.txt. The check now tests the sequence directory name, matching the flat layout, which filtered correctly all along. A MOT-layout sequence directory whose own name starts with.is therefore no longer discovered; list it in aseqmapto evaluate it. Layout detection now skips hidden files too, so a hidden.txtfile at the root of a MOT-layoutgt_dirno longer selects the flat layout and fails withValueError: No sequences found. Discovery of non-hidden sequences is unchanged.- Empty
update()results keep the input'sdatakeys,maskandmetadata— every tracker built an empty result fromsv.Detections.empty(), so on frames without detections, or when all detections were discarded, these fields disappeared:result.data["class_name"]raisedKeyErrorthere while working on every other frame. Empty results are now built like non-empty ones, by indexing the input, so they carry the same fields with zero rows. Empty results also keep the input's dtypes, and an input withoutconfidenceorclass_idnow givesNonefor them on empty frames too, as non-empty frames already did. Non-empty results are unchanged. (#638) - OC-SORT ORU no longer corrupts
XCYCWHStateEstimatortracks — when a lost track was re-observed, observation-centric re-update (ORU) replayed its interpolated virtual boxes into the Kalman filter as raw[x1, y1, x2, y2], bypassingbbox_to_measurement. That is only correct forXYXYStateEstimator; withOCSORTTracker(state_estimator_class=XCYCWHStateEstimator)the filter measures[xc, yc, w, h], so every re-acquisition after a miss left the posterior box far from the detection ([81.9, 38.1, 217.2, 200.0]instead of[140, 100, 190, 200]for a 50x100 box re-observed after a two-frame miss) with no error raised. Virtual boxes are now encoded through the estimator'sbbox_to_measurementbefore the update. DefaultXCYCSRStateEstimatorandXYXYStateEstimatoroutput is bit-identical. Thestate_estimator_classdocstring now listsXCYCWHStateEstimator; OC-SORT's noise tuning is specific toXCYCSRStateEstimator;XYXYStateEstimatorandXCYCWHStateEstimatoronly get its state-covariance and velocity process-noise scaling. - BoT-SORT positional constructor compatibility preserved — restored the parameter order supported by 2.6.1, correcting a regression introduced during development in #611. Existing positional calls remain supported, including calls combined with keyword Re-ID options (#658).
update()rejects non-finite boxes before mutating tracker state — a detection whosexyxycontains NaN or inf raisedValueErrorfrom the IoU guard only after the tracker had already predicted every track and advanced its timestamp anchor (and, inByteTrackTracker, applied stage-1 matches and allocated IDs), so a caller that caught the error and skipped or retried the frame was left with silently aged or half-updated tracks.BaseTracker._validate_detectionsnow runs as the first statement ofupdate()in all six trackers, so a failed call leaves tracks, IDs and the timestamp anchor untouched. Low-confidence non-finite rows no longer slip through either:OCSORTTracker,BoTSORTTracker,CBIoUTrackerandMcByteTrackersilently ignored them (dropping them from the output as of 2.6.0), and all four now raise.trackers evalnames a MOT-layout sequence after its directory, notgt—evaluate_mot_sequencetook the result name from the ground-truth file stem, sotrackers eval --gt <seq>/gt/gt.txtprintedgtin the results table, inSequenceResult.sequenceand in the"sequence"field of the--outputJSON. A<seq>/gt/gt.txtfile is now named<seq>(relative paths are resolved against the working directory) and every other ground-truth file keeps its stem. Scripts that matched the literalgtin that output need updating (#634, #635).
📖 Documentation and maintenance
- Reorganized documentation into usage, tuning, developer and evaluation sections; added CLI/migration, inspect and benchmark-runner guides, and exposed the canonical changelog and contribution guidance on the documentation site (#548, #549, #551, #553, #565).
- Updated development/documentation dependency locks, pre-commit hooks and GitHub Actions; build CI now selects Python 3.12. This is a build-job choice, not a Python minimum increase (#625, #648). No new security fix is claimed solely from dependency bump subjects.
2.6.1
🔧 Fixed
GIoUno longer scores a malformed box above0—_compute_iou_and_enclosingcomputed box area as(x2 - x1) * (y2 - y1)without clamping, so a box inverted on both axes contributed a spuriously positive area: two negative extents multiply to a positive product. That inflated the union past the enclosing area and flipped GIoU's(enclosing - union) / enclosingpenalty into a bonus, driving scores far outside GIoU's documented[-1, 1]range (over+29on random malformed input) so that a degenerate box could outrank a genuine match. Box extents are now clamped at zero exactly as the intersection already was. AffectsGIoUonly:IoU/BIoUdo not use this helper, andDIoU/CIoUare byte-identical because a malformed axis already forces the intersection to zero. Reachable when a tracker is constructed withiou=GIoU()—SORTTracker's defaultXYXYStateEstimatorhas an unconstrained velocity and no downstream box clamp, though every tracker exposing aniouparameter defaults to plainIoU(), andCBIoUTrackerexposes none. Results for well-formed boxes are bit-identical.- OC-SORT now returns low-confidence detections with
tracker_id=-1— detections belowhigh_conf_det_thresholdwere previously dropped silently instead of being emitted, unlikeSORTTracker/ByteTrackTracker.update()now returns one row per input detection, matching the documented contract. Output-contract change: callers may now see additionaltracker_id == -1rows (#566). - McByte mask cleanup survives deferred delivery — removed tracklet IDs stay queued until the mask pipeline receives them, including across skipped frames, duplicate timestamps, and recoverable CUDA out-of-memory failures; tracks pruned early during timestamp updates are included (#568).
- BoT-SORT and McByte refresh only the Kalman noise matrix used by the current step — prediction refreshes process noise (
Q) and measurement updates refresh measurement noise (R), avoiding stale or unnecessary recalculation (#567). - Batched
xcycsr_to_xyxypreserves fractional coordinates from integer input — integer arrays now decode tofloat64;float32input remainsfloat32. Code depending on the old truncated integer dtype should convert explicitly after decoding (#557). - Image-directory frames use natural filename order —
2.jpgis read before10.jpg; zero-padded and non-numeric names retain their prior order (#558). - Mike-deployed documentation URLs point at the
/latest/alias — canonical, Open Graph, structured-data, and sitemap URLs no longer nest the numbered version beneath/latest/(#582).
📖 Documentation
- Updated McByte benchmark and comparison tables, tracker defaults, quickstart and evaluation guidance, dataset details, README terminology, search-result titles, and video poster frames to match this release branch (#539, #547, #555, #564, #583, #585, #587).
2.6.0 — 2026-08-03
🚀 Added
McByteTracker— new mask-conditioned ByteTrack tracker combining mask-conditioned association with SAM box-mask generation and Cutie mask propagation; exported fromtrackers(McByteMaskConfigalso exported) and CLI-discoverable. Newtrackers[mask]extra installstorch,torchvision,rf-segment-anything, andrf-cutie[inference](rf-cutienow ships on PyPI instead of git). Device selection defaults to"auto"(CUDA → MPS → CPU); Cutie streaming behaviour is tunable viacutie_max_internal_size(480),cutie_mem_every(10), andcutie_use_long_term(True), withcutie_channels_last/cutie_compileand AMP left off by default;minimum_mask_creation_frames(3) defers SAM/Cutie encoding for short-lived tracklets. Frames are expected in RGB (BaseTracker's other trackers expect BGR). Mask checkpoints load withweights_only=True(CWE-502 guard, never shipped unsafe) (#388, #418, #441, #452, #459, #481, #491, #508, #513, #519, #520, #521, #522, #523, #524, #525, #526, #529, #532).- Optional
timestamp=onBaseTracker.update()— all six trackers convert elapsed wall-clock seconds into Kalman frame units and prune lost tracks on a seconds budget when timestamps are supplied; omittingtimestamppreserves fixed-rate behaviour (#446). KalmanMotionModelintrackers.utils.motion_models— supplies the KalmanFandQfor a givenframe_step;Fis a trivial constant-velocity matrix (constant_velocity_F) whileScalableProcessNoiseholds the tunedQ, used at the nominal step and DWNA-scaled on timestamp gaps.
⚠️ Breaking Changes
- Invalid lost-track buffer settings now raise
ValueError—lost_track_buffermust be non-negative andframe_ratemust be finite and positive forSORTTracker,ByteTrackTracker,OCSORTTracker,BoTSORTTracker, andCBIoUTracker(which forwards its constructor args toBoTSORTTracker). Explicitlost_track_buffer=0remains valid and means no missed-frame grace period; negative buffers and invalid frame rates previously initialized but produced nonsensical lifecycle behavior (#420). - Confirmed tracks now survive one additional missed frame — all trackers changed from exclusive (
time_since_update < maximum_frames_without_update) to inclusive (<=) boundary semantics to match OC-SORT's previous behavior. Users comparing metric results across this version should expect small IDSW/HOTA shifts (#420).
🌱 Changed
- Improved tracker performance (bit-identical output) — CMC's sparse optical-flow status-mask filtering vectorized (~24x faster for that operation);
KalmanMotionModelnow caches its transition/noise matrices and defers DWNA calibration (~38% lower BoT-SORT/CBIoU predict cost); predicted boxes cached across association stages in BoT-SORT/CBIoU; Kalman-state-estimator copies trimmed and OC-SORT association hygiene improved (#522, #527, #528). - Direct dependency constraint
pydeprecate>=0.7.0raised to>=0.8.0; build backend migrated fromhatchlingtosetuptools(src-layout discovery +py.typedsupport) (#492).
🔧 Fixed
- Positive low-FPS lost-track buffers no longer collapse to zero frames — all trackers now scale positive
lost_track_buffervalues withceil(...)and keep confirmed tracks alive through exactly the scaled number of missed frames, matching OC-SORT's previous inclusive boundary semantics (#420). - BoT-SORT / CBIoU: instant-activated first-frame tracks no longer dropped on a single miss — sticky maturity now triggers once a real
tracker_idis assigned (tracker_id != -1), matching ByteTrack; fixes a default-config ID switch when a frame-1 object is missed once (#478 BoT-SORT, #504 CBIoU). - ByteTrack / BoT-SORT / CBIoU now return unmatched detections between the two confidence thresholds — detections below
track_activation_thresholdbut abovehigh_conf_det_thresholdare now emitted withtracker_id=-1instead of silently dropped, matching the documentedupdate()contract. Output-contract change: callers may now see additionaltracker_id == -1rows (#475). - Signed IoU variants clamped to
[0, 1]before score fusion — the CIoU floor (~-1.5 from the aspect penalty) no longer produces negative fused similarities; the clamp is a no-op for GIoU/DIoU (#476). - OC-SORT: Kalman scale kept positive across frame-step gaps —
clamp_velocityno longer allows scale to collapse negative over long timestamp gaps (#509). - CMC: handle mid-stream frame-resolution changes in optical-flow motion estimation — adds a re-sync/resize path when the input resolution changes between frames (#505).
- CMC: fixed
cv2.resizecrash on tiny downscaled images — dimensions are now clamped tomax(1, ...), covering 1x1, 1x3, and 3x1 edge cases (#488). xcycsr_to_xyxy: prevent zero-division on zero-aspect boxes — anr1 != 0guard now yields height0instead ofinf(#485).- Video output resized on mid-stream resolution change —
VideoOutputnow resizes frames to match the writer's configured size (INTER_AREAon downscale only) (#514). - Package builds fixed for
srclayout — correctedsetuptoolspackage discovery and typed-package (py.typed) support, plus release CI artifact handling and publish triggers (#492).
🔒 Security
- Hardened ZIP extraction against Zip Slip / path traversal (CWE-22) — archive members with absolute paths, traversal segments, empty names, or Windows-style backslash paths are now rejected; validated archives are contained so they cannot write outside the destination directory, including path-swap protection (#495).
2.5.0 — 2026-06-22
🚀 Added
- Pluggable IoU variants —
iou=parameter on all four trackers (SORTTracker,ByteTrackTracker,OCSORTTracker,BoTSORTTracker) accepts anyBaseIoUsubclass. Built-in variants:IoU(standard),GIoU,DIoU,CIoU,BIoU(Buffered IoU) (#403). BaseIoUABC intrackers.utils.iou— defines thecompute(boxes_1, boxes_2)contract; subclass and override_computeto implement a custom similarity metric (#403).normalize_for_fusiononBaseIoU— signed variants (GIoU, DIoU, CIoU) override this to shift[-1, 1]→[0, 1]before BoT-SORT score fusion, preventing ranking inversion (#403).CBIoUTracker— Cascaded-Buffered IoU tracker (Yang et al., WACV 2023). Two-stage matching with independently tunablebuffer_ratio_first/buffer_ratio_secondbuffer scales; inherits ByteTrack-style low-confidence second pass from BoT-SORT (#417).py.typedmarker — PEP 561 compliance; IDEs and type checkers now recognise the package as typed without--ignore-missing-imports.
🔄 Deprecated
SORTTracker.trackers— deprecated alias for.tracks; emitsFutureWarningsince v2.5, will be removed in v3.0. Usetracker.tracksinstead.trackers.core.botsort.cmcmodule —CMCmoved totrackers.utils.cmc; old path re-exports all symbols withDeprecationWarninguntil v3.0. Migrate:from trackers.utils.cmc import CMCorfrom trackers import CMC(#414).BoTSORTTracker.apply_cmc_batch— useCMC.apply_batch(H, tracker.tracks)directly. Will be removed in v3.0 (#414).CMCTMethodtype alias — kept as a back-compat alias forCMCMethod; will be removed in v3.0. Migrate toCMCMethod(#414).CMC.apply_to_xyxyrenamed toCMC.warp_xyxy_corners— old name kept as a deprecated wrapper that forwards to the new name; will be removed in v3.0. Update call sites toCMC.warp_xyxy_corners(#414).
⚠️ Breaking Changes
- Internal tracklet ID counters removed — track IDs are now allocated by each tracker instance instead of the class-level counters on each
*Trackletsubclass (e.g.BoTSORTTracklet.get_next_tracker_id()). Internal tracklet subclassers should allocate IDs in tracker code and assigntracklet.tracker_iddirectly. Useself._allocate_tracker_id()(inherited fromBaseTracker) as the replacement allocator when implementing a custom tracker subclass.
🌱 Changed
CMC,CMCConfig,CMCMethodmoved totrackers.utils.cmcand re-exported from top-leveltrackerspackage — import directly withfrom trackers import CMC; oldtrackers.core.botsort.cmcpath kept as a deprecated shim (#414).CMC.warp_xyxy_corners—apply_to_xyxyrenamed towarp_xyxy_corners; old name kept as a deprecated wrapper until v3.0 (#414).CMC.apply_batchhomogeneity guard — now raisesTypeErrorimmediately when the tracklet list contains mixed state-estimator types, preventing silent state corruption (#414).BoTSORTTracklet.apply_cmcdelegates toCMC.apply_batch— per-track and batch paths now share identical code; behaviour is unchanged (#414).Tunergainsenqueue_defaults,fixed_params,images_dir,seed—enqueue_defaults=True(default) evaluates a baseline trial using each param's__init__default before Optuna samples;fixed_paramsholds selected params constant across all trials;images_direnables frame loading for CMC-enabled trackers;seedmakes TPE sampling reproducible (#427).
🔧 Fixed
- Clarified in docs that
SORTTrackeritself is not deprecated — only the.trackersalias is. - BoT-SORT score fusion with signed IoU —
_fuse_scoremultiplied raw negative IoU values by confidence, inverting track ranking for GIoU/DIoU/CIoU;normalize_for_fusionnow normalises similarity before fusion (#403). - Non-finite box coordinates crash
linear_sum_assignment—BaseIoU.computenow raisesValueErrorwith a clear message for NaN/inf inputs instead of propagating invalid entries into SciPy (#403). - OC-SORT Observation-Centric Recovery now uses standard
IoUper the paper, independent of the configurediou=variant (#403). - Eager division warnings on zero-area boxes — IoU helper switched from
np.where(eager) tonp.divide(..., where=...)(lazy), suppressingRuntimeWarningunder strict NumPy error settings (#403). - CLI argparse crash on
BaseIoUparameter —iou=is now excluded from argparse auto-discovery; the variant must be set programmatically (#403). - ByteTrack tracked nothing when detections lacked confidence scores — the default-fill changed from
np.zerostonp.ones, matching SORT / OC-SORT / BoT-SORT behaviour, so detectors that emitsv.Detectionswithoutconfidencenow produce tracks instead of empty results (#415). - Tracker instances no longer share track ID counters — resetting one tracker instance no longer resets another instance's ID allocator, preventing duplicate live IDs in multi-camera, class-specific, or parallel tracker workflows.
- HOTA per-frame alpha loop vectorized — removes the inner Python loop; large evaluations run significantly faster with no change to numeric output (#462).
- MOT evaluation distractor handling — ground-truth preprocessing now applies distractor class filtering consistent with TrackEval, correcting reported metrics on MOT17 and similar datasets (#466).
2.4.0 — 2026-05-06
🚀 Added
- BoT-SORT tracker (
BoTSORTTracker) — new tracker with optional camera motion compensation (CMC), configurable methods (orb,sift,sparseOptFlow,ecc), and ByteTrack-style score-fused association (#386). tracked_objectsproperty onBaseTrackerand all concrete trackers — exposes every alive track with its Kalman-predicted bounding box, including occluded or detector-missed tracks.update()return value is unchanged for backward compatibility (#373, resolves #105).Tunerclass (trackers.tune.Tuner) — Optuna-based hyperparameter optimisation driven by each tracker's newsearch_spaceClassVar. Supports HOTA / MOTA / IDF1 objectives over MOT-format ground-truth and pre-computed detections (#301).trackers tuneCLI subcommand — wiresTunerinto the CLI; selects tracker, ground-truth directory, detections directory, objective, and--n-trials(#374).load_mot_fileis now public — was_load_mot_file. Now exported fromtrackers.io.motfor use in custom tuning and evaluation scripts (#301, #374).xyxy_to_xywhandxywh_to_xyxyconverters added totrackers.utils.convertersfor center-width-height format support (#310, #386).frameparameter onBaseTracker.update()—update(detections, frame=None). Required by BoT-SORT when CMC is enabled; ignored (withUserWarning) by SORT, ByteTrack, OC-SORT. ThetrackCLI passes the current frame automatically (#386).- Swappable Kalman state estimators —
BaseStateEstimator,XCYCSRStateEstimator,XCYCWHStateEstimator,XYXYStateEstimatorintrackers.utils.state_representations; trackers can opt in viastate_estimator_class=(#310). TrackletProtocolstructural type intrackers.core.base— formalises the contract every tracklet stored inBaseTracker.tracksmust satisfy.search_spaceClassVar on every tracker — declarative hyperparameter spaces consumed byTuner, validated for unknown keys and bad types.- Modern Python 3.10+ type hints across the public surface (#302).
- Documentation:
docs/trackers/botsort.mduser guide,docs/learn/state-estimators.md, expanded comparison page with DanceTrack section.
⚠️ Breaking Changes
SORTTracker.update()no longer mutates its inputsv.Detections— previously assignedtracker_idon the caller's object and returned that same instance; now returns a fresh indexed copy, matching ByteTrack and OC-SORT (#360). Callers that relied on aliasing the input post-update must readtracker_idfrom the returned object.- Per-frame spawn order is now deterministic across SORT, ByteTrack, and OC-SORT — IDs assigned to detections that spawn in the same frame no longer depend on CPython set iteration order (#361). IDs from a recorded run are reproducible across machines but may differ from a v2.3.0 baseline.
- Internal tracklet update contract changed (subclassers of internal
*Trackletclasses only — callers of the publicTracker.update()API are unaffected) — internal tracklet classes (notablyOCSORTTracklet) no longer acceptupdate(None)for unmatched tracks; missed-association logic now lives inpredict()and_get_alive_tracklets. Subclasses that overrode tracklet update behaviour must move that logic intopredict()(#383, follow-up to #376).
🌱 Changed
- Refactored Kalman filter out of tracklet classes — every tracker now shares a single Kalman implementation backed by
BaseStateEstimator. Tracklet classes (SORTTracklet,ByteTrackTracklet,OCSORTTracklet,BoTSORTTracklet) handle association and lifecycle only (#310). - ByteTrack tracklets now count
number_of_successful_consecutive_updatesinstead of totalnumber_of_updates, matching the original ByteTrack reference (#310). - Eval submodule uses lazy
__getattr__forevaluate_mot_sequenceandevaluate_mot_sequencesto avoid circular imports. - Documentation: rewrote landing page, install guide, evaluate guide, ByteTrack page, comparison page; added DanceTrack default tuned numbers.
🔧 Fixed
- ByteTrack: prune unmatched tracks correctly after the Kalman refactor —
time_since_updateadvances on unmatched tracks and_get_alive_trackletsexpires them afterlost_track_bufferempty frames (#376). - Documentation index ByteTrack correction (#371).
2.3.0 — 2026-03-16
🚀 Added
- OC-SORT tracker (
OCSORTTracker) — complete implementation with swappable state estimators (XCYCSRStateEstimator,XYXYStateEstimator), direction-consistency batch calculations, full tracklet lifecycle management, API docs, and unit tests; registered in CLI and public API (#207). trackers downloadCLI subcommand — downloads MOT17 and SportsMOT benchmark datasets to a persistent local cache (~/.cache/trackers) with MD5 verification and Rich-styled progress output; backed by type-safeDataset,DatasetSplit, andDatasetAssetenums (#262).- Integration tests with TrackEval — regression tests for SORT, ByteTrack, and OC-SORT against oracle detections from SportsMOT and DanceTrack; evaluates HOTA, MOTA, IDF1, and IDSW in CI (#298).
- Parameter-tuned benchmark results — tracker comparison page redesigned with tabbed Default / Tuned layout; includes grid-search configs for SportsMOT, SoccerNet, MOT17, and DanceTrack as copyable YAML blocks (#309).
- DanceTrack default parameters — SORT and ByteTrack ship tuned defaults for DanceTrack out of the box (#299).
🌱 Changed
- Coordinate converter hot-path optimisation —
xcycsr_to_xyxyandxyxy_to_xcycsrrestructured for the single-box case, reducing per-frame overhead in tight tracking loops (#296). - Documentation rewrite — landing page, install guide, and evaluate pages comprehensively rewritten; tracker comparison page expanded with dataset videos, paper links, and a DanceTrack section (#322).
- Release and stable branching strategy adopted — repository now follows a
release/stablebranching model (#275).
🔧 Fixed
- Evaluation distractor filtering corrected on the comparison numbers (#322).
- PyPI publish action pinned to verified SHA —
pypa/gh-action-pypi-publishcorrected to the actual v1.13.0 commit SHA (#294).
2.2.0 — 2026-02-18
🚀 Added
- Evaluation metrics — HOTA, CLEAR (MOTA / MOTP / IDSW / MT / PT / ML), and Identity (IDF1 / IDP / IDR) metric implementations in
trackers.eval;evaluate_mot_sequenceandevaluate_mot_sequencespublic API (#210, #212, #223, #224, #226). trackers evalCLI subcommand — runs a tracker over a MOT-format ground-truth directory and prints HOTA / MOTA / IDF1 results; configurable via JSON tracker arguments (#215).- MOT format I/O —
load_mot_fileandsave_mot_fileintrackers.io.motfor reading and writing MOT-format.txtannotation files (#214). MotionAwareTraceAnnotatorwith camera motion compensation — applies homography-based CMC to keep trace paths stable on moving-camera footage (#263).- Tracker auto-registration —
BaseTracker.__init_subclass__now registers every subclass and extracts parameter metadata from__init__docstrings, enabling CLI auto-discovery without a hard-coded tracker list (TrackerInfo,ParameterInfo) (#230). - Benchmark documentation — evaluation metrics (HOTA, MOTA, IDF1) for SORT and ByteTrack published to the docs site (#193).
- Example notebooks — links to runnable Colab notebooks added to docs index (#199).
🌱 Changed
- Apache 2.0 license headers added to all source files via a new pre-commit hook.
- Ruff security rules migrated to S (bandit) —
banditpre-commit hook replaced by Ruff's built-inSrule set (#188). - Dependency trim — removed unused optional extras; install footprint reduced (#192).
2.1.0 — 2026-01-28
🚀 Added
- ByteTrack tracker (
ByteTrackTracker) — two-stage low-score / high-score association with a ByteTrack-specific Kalman box tracker; full API docs and unit tests; registered in CLI and public API (#174).
⚠️ Breaking Changes
- DeepSort removed —
DeepSortTrackerand all associated REID infrastructure removed from the package, docs, tests, and CI workflows. Projects using DeepSort must pin to<2.1.0. - Python 3.9 dropped — minimum supported version is now Python 3.10; type annotations updated to use built-in generics (
list[...],dict[...]) throughout the public API (#200).
🔧 Fixed
- Documentation build — pinned
mkdocstrings-python<2.0.0to resolve docs generation failure.