trackforge 0.5.0__cp315-cp315-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
trackforge/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ from .trackforge import * # noqa: F403
2
+ from .ultralytics import from_ultralytics, track_ultralytics
3
+
4
+ __doc__ = trackforge.__doc__ # noqa: F405
5
+ if hasattr(trackforge, "__all__"): # noqa: F405
6
+ __all__ = [*trackforge.__all__, "from_ultralytics", "track_ultralytics"] # noqa: F405
trackforge/py.typed ADDED
File without changes
@@ -0,0 +1,315 @@
1
+ from typing import List, Optional, Tuple
2
+
3
+ __all__ = [
4
+ "BOTSORT",
5
+ "BYTETRACK",
6
+ "DEEPOCSORT",
7
+ "DEEPSORT",
8
+ "OCSORT",
9
+ "SORT",
10
+ "TRACKTRACK",
11
+ ]
12
+
13
+ class BYTETRACK:
14
+ """
15
+ ByteTrack tracker implementation.
16
+
17
+ **Usage Example:**
18
+
19
+ ```python
20
+ from trackforge import BYTETRACK
21
+
22
+ tracker = BYTETRACK(
23
+ track_thresh=0.5,
24
+ track_buffer=30,
25
+ match_thresh=0.8,
26
+ det_thresh=0.6,
27
+ )
28
+
29
+ detections = [
30
+ ([100.0, 100.0, 50.0, 100.0], 0.9, 0),
31
+ ([200.0, 200.0, 60.0, 120.0], 0.85, 0),
32
+ ]
33
+
34
+ tracks = tracker.update(detections)
35
+ for track_id, box, score, class_id, det_ind in tracks:
36
+ print(f"Track ID: {track_id}, Box: {box}")
37
+ ```
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ track_thresh: float = 0.5,
43
+ track_buffer: int = 30,
44
+ match_thresh: float = 0.8,
45
+ det_thresh: float = 0.6,
46
+ second_match_thresh: float = 0.5,
47
+ ) -> None: ...
48
+ def update(
49
+ self, output_results: List[Tuple[List[float], float, int]]
50
+ ) -> List[Tuple[int, List[float], float, int, Optional[int]]]: ...
51
+
52
+ class SORT:
53
+ """
54
+ SORT (Simple Online and Realtime Tracking) tracker implementation.
55
+
56
+ **Usage Example:**
57
+
58
+ ```python
59
+ from trackforge import SORT
60
+
61
+ tracker = SORT(max_age=1, min_hits=3, iou_threshold=0.3)
62
+
63
+ detections = [
64
+ ([100.0, 100.0, 50.0, 100.0], 0.9, 0),
65
+ ]
66
+
67
+ tracks = tracker.update(detections)
68
+ for track_id, box, score, class_id, det_ind in tracks:
69
+ print(f"Track ID: {track_id}, Box: {box}")
70
+ ```
71
+ """
72
+
73
+ def __init__(
74
+ self,
75
+ max_age: int = 1,
76
+ min_hits: int = 3,
77
+ iou_threshold: float = 0.3,
78
+ ) -> None: ...
79
+ def update(
80
+ self, detections: List[Tuple[List[float], float, int]]
81
+ ) -> List[Tuple[int, List[float], float, int, Optional[int]]]: ...
82
+
83
+ class OCSORT:
84
+ """
85
+ OC-SORT (Observation-Centric SORT) tracker implementation.
86
+
87
+ **Usage Example:**
88
+
89
+ ```python
90
+ from trackforge import OCSORT
91
+
92
+ tracker = OCSORT(
93
+ max_age=30,
94
+ min_hits=3,
95
+ iou_threshold=0.3,
96
+ delta_t=3,
97
+ inertia=0.2,
98
+ )
99
+
100
+ detections = [
101
+ ([100.0, 100.0, 50.0, 100.0], 0.9, 0),
102
+ ]
103
+
104
+ tracks = tracker.update(detections)
105
+ for track_id, box, score, class_id, det_ind in tracks:
106
+ print(f"Track ID: {track_id}, Box: {box}")
107
+ ```
108
+ """
109
+
110
+ def __init__(
111
+ self,
112
+ max_age: int = 30,
113
+ min_hits: int = 3,
114
+ iou_threshold: float = 0.3,
115
+ delta_t: int = 3,
116
+ inertia: float = 0.2,
117
+ ) -> None: ...
118
+ def update(
119
+ self, detections: List[Tuple[List[float], float, int]]
120
+ ) -> List[Tuple[int, List[float], float, int, Optional[int]]]: ...
121
+
122
+ class DEEPSORT:
123
+ """
124
+ DeepSORT tracker implementation with appearance feature matching.
125
+
126
+ **Usage Example:**
127
+
128
+ ```python
129
+ from trackforge import DEEPSORT
130
+ import numpy as np
131
+
132
+ tracker = DEEPSORT(
133
+ max_age=70,
134
+ n_init=3,
135
+ max_iou_distance=0.7,
136
+ max_cosine_distance=0.2,
137
+ nn_budget=100,
138
+ )
139
+
140
+ detections = [
141
+ ([100.0, 100.0, 50.0, 100.0], 0.9, 0),
142
+ ]
143
+ embeddings = [np.random.rand(128).tolist()]
144
+
145
+ tracks = tracker.update(detections, embeddings)
146
+ for track_id, box, score, class_id, det_ind in tracks:
147
+ print(f"Track ID: {track_id}, Box: {box}")
148
+ ```
149
+ """
150
+
151
+ def __init__(
152
+ self,
153
+ max_age: int = 70,
154
+ n_init: int = 3,
155
+ max_iou_distance: float = 0.7,
156
+ max_cosine_distance: float = 0.2,
157
+ nn_budget: int = 100,
158
+ ) -> None: ...
159
+ def update(
160
+ self,
161
+ detections: List[Tuple[List[float], float, int]],
162
+ embeddings: List[List[float]],
163
+ ) -> List[Tuple[int, List[float], float, int, Optional[int]]]: ...
164
+
165
+ class DEEPOCSORT:
166
+ """
167
+ Deep OC-SORT tracker: OC-SORT motion with appearance association.
168
+
169
+ Blends a cosine distance to a per-track feature gallery into the OC-SORT
170
+ motion cost. Pass embeddings for appearance-aware tracking, or omit them to
171
+ track on motion only.
172
+
173
+ **Usage Example:**
174
+
175
+ ```python
176
+ from trackforge import DEEPOCSORT
177
+
178
+ tracker = DEEPOCSORT(
179
+ max_age=30,
180
+ min_hits=3,
181
+ iou_threshold=0.3,
182
+ delta_t=3,
183
+ inertia=0.2,
184
+ appearance_weight=0.5,
185
+ max_cosine_distance=0.2,
186
+ nn_budget=100,
187
+ )
188
+
189
+ detections = [
190
+ ([100.0, 100.0, 50.0, 100.0], 0.9, 0),
191
+ ]
192
+ embeddings = [[0.1, 0.2, 0.3]]
193
+
194
+ tracks = tracker.update(detections, embeddings)
195
+ for track_id, box, score, class_id, det_ind in tracks:
196
+ print(f"Track ID: {track_id}, Box: {box}")
197
+ ```
198
+ """
199
+
200
+ def __init__(
201
+ self,
202
+ max_age: int = 30,
203
+ min_hits: int = 3,
204
+ iou_threshold: float = 0.3,
205
+ delta_t: int = 3,
206
+ inertia: float = 0.2,
207
+ appearance_weight: float = 0.5,
208
+ max_cosine_distance: float = 0.2,
209
+ nn_budget: int = 100,
210
+ ) -> None: ...
211
+ def update(
212
+ self,
213
+ detections: List[Tuple[List[float], float, int]],
214
+ embeddings: List[List[float]] = ...,
215
+ camera_motion: Optional[List[float]] = ...,
216
+ ) -> List[Tuple[int, List[float], float, int, Optional[int]]]: ...
217
+
218
+ class BOTSORT:
219
+ """
220
+ BoT-SORT tracker: ByteTrack with camera motion and appearance fusion.
221
+
222
+ Adds camera motion compensation and an appearance-fused first association stage
223
+ on top of ByteTrack's two-stage cascade. Pass embeddings for appearance-aware
224
+ tracking, or omit them to track on motion only.
225
+
226
+ **Usage Example:**
227
+
228
+ ```python
229
+ from trackforge import BOTSORT
230
+
231
+ tracker = BOTSORT(
232
+ track_thresh=0.5,
233
+ track_buffer=30,
234
+ match_thresh=0.8,
235
+ det_thresh=0.6,
236
+ proximity_thresh=0.5,
237
+ appearance_thresh=0.25,
238
+ )
239
+
240
+ detections = [
241
+ ([100.0, 100.0, 50.0, 100.0], 0.9, 0),
242
+ ]
243
+ embeddings = [[0.1, 0.2, 0.3]]
244
+
245
+ tracks = tracker.update(detections, embeddings)
246
+ for track_id, box, score, class_id, det_ind in tracks:
247
+ print(f"Track ID: {track_id}, Box: {box}")
248
+ ```
249
+ """
250
+
251
+ def __init__(
252
+ self,
253
+ track_thresh: float = 0.5,
254
+ track_buffer: int = 30,
255
+ match_thresh: float = 0.8,
256
+ det_thresh: float = 0.6,
257
+ second_match_thresh: float = 0.5,
258
+ proximity_thresh: float = 0.5,
259
+ appearance_thresh: float = 0.25,
260
+ ) -> None: ...
261
+ def update(
262
+ self,
263
+ detections: List[Tuple[List[float], float, int]],
264
+ embeddings: List[List[float]] = ...,
265
+ camera_motion: Optional[List[float]] = ...,
266
+ ) -> List[Tuple[int, List[float], float, int, Optional[int]]]: ...
267
+
268
+ class TRACKTRACK:
269
+ """
270
+ TrackTrack tracker: track-perspective association with track-aware initialization.
271
+
272
+ Each track picks its own best detection and a pair matches only when the choice
273
+ is mutual, repeating with a cost gate that tightens each round. New tracks start
274
+ only from confident detections that do not overlap existing tracks. Pass
275
+ embeddings for appearance-aware tracking, or omit them to track on motion only.
276
+
277
+ **Usage Example:**
278
+
279
+ ```python
280
+ from trackforge import TRACKTRACK
281
+
282
+ tracker = TRACKTRACK(
283
+ det_thresh=0.6,
284
+ match_thresh=0.7,
285
+ track_buffer=30,
286
+ min_hits=3,
287
+ )
288
+
289
+ detections = [
290
+ ([100.0, 100.0, 50.0, 100.0], 0.9, 0),
291
+ ]
292
+
293
+ tracks = tracker.update(detections)
294
+ for track_id, box, score, class_id, det_ind in tracks:
295
+ print(f"Track ID: {track_id}, Box: {box}")
296
+ ```
297
+ """
298
+
299
+ def __init__(
300
+ self,
301
+ det_thresh: float = 0.6,
302
+ match_thresh: float = 0.7,
303
+ track_buffer: int = 30,
304
+ min_hits: int = 3,
305
+ init_thresh: float = 0.7,
306
+ tai_thresh: float = 0.55,
307
+ penalty_low: float = 0.2,
308
+ reduce_step: float = 0.05,
309
+ ) -> None: ...
310
+ def update(
311
+ self,
312
+ detections: List[Tuple[List[float], float, int]],
313
+ embeddings: List[List[float]] = ...,
314
+ camera_motion: Optional[List[float]] = ...,
315
+ ) -> List[Tuple[int, List[float], float, int, Optional[int]]]: ...
@@ -0,0 +1,83 @@
1
+ """Helpers for feeding Ultralytics YOLO results into trackforge trackers.
2
+
3
+ They only read the result objects, so ``ultralytics`` stays an optional dependency
4
+ and is never imported here.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any, Callable, Iterator, List, Optional, Sequence, Tuple
10
+
11
+ Detection = Tuple[List[float], float, int]
12
+ Embedder = Callable[[Any, List[List[float]]], Sequence[Sequence[float]]]
13
+
14
+ __all__ = ["from_ultralytics", "track_ultralytics"]
15
+
16
+
17
+ def from_ultralytics(result: Any) -> List[Detection]:
18
+ """Convert one Ultralytics result into trackforge detections.
19
+
20
+ Args:
21
+ result: A ``Results`` object from ``model.predict(...)`` or ``model(...)``, or
22
+ the one-element list those calls return for a single image.
23
+
24
+ Returns:
25
+ A list of ``(tlwh, score, class_id)`` detections, in the same order as
26
+ ``result.boxes``, so a track's ``det_ind`` indexes into ``result.boxes``.
27
+
28
+ Raises:
29
+ ValueError: If a list holding more than one result is passed.
30
+ """
31
+ if isinstance(result, (list, tuple)):
32
+ if len(result) != 1:
33
+ raise ValueError(
34
+ f"expected the result of a single image, got {len(result)} results"
35
+ )
36
+ result = result[0]
37
+
38
+ boxes = getattr(result, "boxes", None)
39
+ if boxes is None or len(boxes) == 0:
40
+ return []
41
+
42
+ return [
43
+ ([x1, y1, x2 - x1, y2 - y1], float(score), int(cls))
44
+ for (x1, y1, x2, y2), score, cls in zip(
45
+ boxes.xyxy.tolist(), boxes.conf.tolist(), boxes.cls.tolist()
46
+ )
47
+ ]
48
+
49
+
50
+ def track_ultralytics(
51
+ model: Any,
52
+ source: Any,
53
+ tracker: Any,
54
+ embedder: Optional[Embedder] = None,
55
+ **predict_kwargs: Any,
56
+ ) -> Iterator[Tuple[Any, list]]:
57
+ """Run an Ultralytics model over a source and track every frame.
58
+
59
+ Args:
60
+ model: A loaded ``ultralytics.YOLO`` (or RT-DETR) model.
61
+ source: Anything ``model.predict`` accepts: a video path, webcam index,
62
+ stream URL, image folder, and so on.
63
+ tracker: A trackforge tracker such as ``trackforge.BYTETRACK()``.
64
+ embedder: Optional ``embedder(frame, tlwh_boxes) -> embeddings`` callable for
65
+ appearance trackers. ``DEEPSORT`` needs one; ``DEEPOCSORT``, ``BOTSORT``
66
+ and ``TRACKTRACK`` use it when given.
67
+ **predict_kwargs: Passed to ``model.predict``, for example ``classes=[0]``
68
+ or ``conf=0.3``.
69
+
70
+ Yields:
71
+ ``(result, tracks)`` per frame, where ``tracks`` is the tracker output and
72
+ ``result.orig_img`` is the BGR frame.
73
+ """
74
+ predict_kwargs.setdefault("stream", True)
75
+ predict_kwargs.setdefault("verbose", False)
76
+ for result in model.predict(source, **predict_kwargs):
77
+ detections = from_ultralytics(result)
78
+ if embedder is None:
79
+ tracks = tracker.update(detections)
80
+ else:
81
+ boxes = [d[0] for d in detections]
82
+ tracks = tracker.update(detections, embedder(result.orig_img, boxes))
83
+ yield result, tracks