OTVision 0.6.0__py3-none-any.whl → 0.6.2__py3-none-any.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.
@@ -0,0 +1,228 @@
1
+ from dataclasses import dataclass
2
+
3
+ from OTVision.track.model.detection import Detection, TrackedDetection
4
+ from OTVision.track.model.frame import Frame, TrackedFrame
5
+ from OTVision.track.model.tracking_interfaces import (
6
+ ID_GENERATOR,
7
+ FrameNo,
8
+ S,
9
+ Tracker,
10
+ TrackId,
11
+ )
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class IouParameters:
16
+ sigma_l: float
17
+ sigma_h: float
18
+ sigma_iou: float
19
+ t_min: int
20
+ t_miss_max: int
21
+
22
+
23
+ @dataclass(frozen=True, repr=True)
24
+ class Coordinate:
25
+ x: float
26
+ y: float
27
+
28
+ @staticmethod
29
+ def center_of(detection: Detection) -> "Coordinate":
30
+ return Coordinate(detection.x, detection.y)
31
+
32
+
33
+ @dataclass(frozen=True, repr=True)
34
+ class BoundingBox:
35
+ xmin: float
36
+ ymin: float
37
+ xmax: float
38
+ ymax: float
39
+
40
+ @staticmethod
41
+ def from_xywh(detection: Detection) -> "BoundingBox":
42
+ """Calculates xyxy coordinates from Detection with xywh data:
43
+ pixel values for xcenter, ycenter, width and height.
44
+
45
+ Args:
46
+ detection (Detection): detection to compute BoundingBox for.
47
+
48
+ Returns:
49
+ BoundingBox with pixel coordinates: xmin, ymin, xmay, ymax
50
+ """
51
+ diff_w = detection.w / 2
52
+ diff_h = detection.h / 2
53
+ d = detection
54
+ return BoundingBox(d.x - diff_w, d.y - diff_h, d.x + diff_w, d.y + diff_h)
55
+
56
+ def as_tuple(self) -> tuple[float, float, float, float]:
57
+ return (self.xmin, self.ymin, self.xmax, self.ymax)
58
+
59
+
60
+ @dataclass
61
+ class ActiveIouTrack:
62
+ # TODO check invariant -> at least one element in lists
63
+ id: int
64
+ frame_no: list[FrameNo]
65
+ bboxes: list[BoundingBox]
66
+ center: list[Coordinate]
67
+ conf: list[float]
68
+ classes: list[str]
69
+ max_class: str
70
+ max_conf: float
71
+ first_frame: FrameNo
72
+ last_frame: FrameNo
73
+ track_age: int
74
+
75
+ def __init__(self, id: TrackId, frame: "Frame", detection: "Detection") -> None:
76
+ self.id = id
77
+ self.frame_no = [frame.no]
78
+ self.bboxes = [BoundingBox.from_xywh(detection)]
79
+ self.center = [Coordinate.center_of(detection)]
80
+ self.conf = [detection.conf]
81
+ self.classes = [detection.label]
82
+ self.max_class = detection.label
83
+ self.max_conf = detection.conf
84
+ self.first_frame = frame.no
85
+ self.last_frame = frame.no
86
+ self.track_age = 0
87
+
88
+ def add_detection(self, frame: Frame, detection: Detection) -> None:
89
+ self.frame_no.append(frame.no)
90
+ self.bboxes.append(BoundingBox.from_xywh(detection))
91
+ self.center.append(Coordinate.center_of(detection))
92
+ self.conf.append(detection.conf)
93
+ self.classes.append(detection.label)
94
+ self.max_conf = max(self.max_conf, detection.conf)
95
+ self.last_frame = max(self.last_frame, frame.no)
96
+ self.track_age = 0
97
+
98
+ @property
99
+ def frame_span(self) -> int:
100
+ return self.last_frame - self.first_frame
101
+
102
+ def iou_with(self, detection: Detection) -> float:
103
+ return iou(self.bboxes[-1], BoundingBox.from_xywh(detection))
104
+
105
+
106
+ def iou(
107
+ bbox1: BoundingBox,
108
+ bbox2: BoundingBox,
109
+ ) -> float:
110
+ """
111
+ Calculates the intersection-over-union of two bounding boxes.
112
+
113
+ Args:
114
+ bbox1 (BoundingBox): first bounding box.
115
+ bbox2 (BoundingBox): second bounding box.
116
+
117
+ Returns:
118
+ int: intersection-over-onion of bbox1, bbox2
119
+ """
120
+
121
+ (xmin_1, ymin_1, xmax_1, ymax_1) = bbox1.as_tuple()
122
+ (xmin_2, ymin_2, xmax_2, ymax_2) = bbox2.as_tuple()
123
+
124
+ # get the overlap rectangle
125
+ overlap_xmin = max(xmin_1, xmin_2)
126
+ overlap_ymin = max(ymin_1, ymin_2)
127
+ overlap_xmax = min(xmax_1, xmax_2)
128
+ overlap_ymax = min(ymax_1, ymax_2)
129
+
130
+ # check if there is an overlap
131
+ if overlap_xmax - overlap_xmin <= 0 or overlap_ymax - overlap_ymin <= 0:
132
+ return 0
133
+
134
+ # if yes, calculate the ratio of the overlap to each ROI size and the unified size
135
+ size_1 = (xmax_1 - xmin_1) * (ymax_1 - ymin_1)
136
+ size_2 = (xmax_2 - xmin_2) * (ymax_2 - ymin_2)
137
+
138
+ size_intersection = (overlap_xmax - overlap_xmin) * (overlap_ymax - overlap_ymin)
139
+ size_union = size_1 + size_2 - size_intersection
140
+
141
+ return size_intersection / size_union
142
+
143
+
144
+ class IouTracker(Tracker[S]):
145
+
146
+ def __init__(self, parameters: IouParameters):
147
+ super().__init__()
148
+ self.parameters = parameters
149
+ self.active_tracks: list[ActiveIouTrack] = []
150
+
151
+ @property
152
+ def sigma_l(self) -> float:
153
+ return self.parameters.sigma_l
154
+
155
+ @property
156
+ def sigma_h(self) -> float:
157
+ return self.parameters.sigma_h
158
+
159
+ @property
160
+ def sigma_iou(self) -> float:
161
+ return self.parameters.sigma_iou
162
+
163
+ @property
164
+ def t_min(self) -> int:
165
+ return self.parameters.t_min
166
+
167
+ @property
168
+ def t_miss_max(self) -> int:
169
+ return self.parameters.t_miss_max
170
+
171
+ def track_frame(
172
+ self, frame: Frame[S], id_generator: ID_GENERATOR
173
+ ) -> TrackedFrame[S]:
174
+
175
+ detections = [d for d in frame.detections if d.conf >= self.sigma_l]
176
+ tracked_detections: list[TrackedDetection] = []
177
+
178
+ finished_track_ids: list[TrackId] = []
179
+ discarded_track_ids: list[TrackId] = []
180
+
181
+ saved_tracks: list[ActiveIouTrack] = []
182
+ updated_tracks: list[ActiveIouTrack] = []
183
+ new_tracks: list[ActiveIouTrack] = []
184
+
185
+ # for each active track, check if detection with best iou match can be added
186
+ for track in self.active_tracks:
187
+ if detections:
188
+ # get det with highest iou
189
+ iou_pairs = (
190
+ (i, det, track.iou_with(det)) for i, det in enumerate(detections)
191
+ )
192
+ best_index, best_match, best_iou = max(iou_pairs, key=lambda p: p[2])
193
+
194
+ if best_iou >= self.sigma_iou:
195
+ track.add_detection(frame, best_match)
196
+ updated_tracks.append(track)
197
+
198
+ del detections[best_index]
199
+ tracked_detections.append(best_match.of_track(track.id, False))
200
+
201
+ # if track was not updated, increase age (time without detection)
202
+ # if max age is reached, check confidence, if sufficient finish that track
203
+ if not updated_tracks or track is not updated_tracks[-1]:
204
+ if track.track_age < self.t_miss_max:
205
+ track.track_age += 1
206
+ saved_tracks.append(track)
207
+ elif track.max_conf >= self.sigma_h and track.frame_span >= self.t_min:
208
+ finished_track_ids.append(track.id)
209
+ else:
210
+ discarded_track_ids.append(track.id)
211
+
212
+ # start new track for each detection that could not be assigned
213
+ for det in detections:
214
+ track_id: TrackId = next(id_generator)
215
+ new_tracks.append(ActiveIouTrack(track_id, frame, det))
216
+ tracked_detections.append(det.of_track(track_id, True))
217
+
218
+ self.active_tracks = updated_tracks + saved_tracks + new_tracks
219
+
220
+ return TrackedFrame(
221
+ no=frame.no,
222
+ occurrence=frame.occurrence,
223
+ source=frame.source,
224
+ detections=tracked_detections,
225
+ image=frame.image,
226
+ finished_tracks=set(finished_track_ids),
227
+ discarded_tracks=set(discarded_track_ids),
228
+ )
OTVision/version.py CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "v0.6.0"
1
+ __version__ = "v0.6.2"
2
2
 
3
3
 
4
4
  def otdet_version() -> str:
@@ -26,7 +26,7 @@ import tkinter as tk
26
26
  from OTVision.config import CONFIG, PAD
27
27
  from OTVision.helpers.files import get_files, replace_filetype
28
28
  from OTVision.helpers.log import LOGGER_NAME
29
- from OTVision.track.track import main as track
29
+ from OTVision.track.new_track import main as track
30
30
  from OTVision.view.view_helpers import FrameRun
31
31
 
32
32
  log = logging.getLogger(LOGGER_NAME)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: OTVision
3
- Version: 0.6.0
3
+ Version: 0.6.2
4
4
  Summary: OTVision is a core module of the OpenTrafficCam framework to perform object detection and tracking.
5
5
  Project-URL: Homepage, https://opentrafficcam.org/
6
6
  Project-URL: Documentation, https://opentrafficcam.org/overview/
@@ -20,15 +20,16 @@ Requires-Dist: av==13.0.0
20
20
  Requires-Dist: fire==0.7.0
21
21
  Requires-Dist: geopandas==1.0.1
22
22
  Requires-Dist: ijson==3.3.0
23
+ Requires-Dist: more-itertools==10.5.0
23
24
  Requires-Dist: moviepy==1.0.3
24
25
  Requires-Dist: numpy==1.26.4
25
26
  Requires-Dist: opencv-python==4.9.0.80
26
- Requires-Dist: pandas==2.2.2
27
+ Requires-Dist: pandas==2.2.3
27
28
  Requires-Dist: pyyaml==6.0.2
28
29
  Requires-Dist: setuptools==74.0.0
29
30
  Requires-Dist: torch==2.3.1
30
31
  Requires-Dist: torchvision==0.18.1
31
- Requires-Dist: tqdm==4.66.5
32
+ Requires-Dist: tqdm==4.67.1
32
33
  Requires-Dist: ujson==5.10.0
33
34
  Requires-Dist: ultralytics==8.3.74
34
35
  Description-Content-Type: text/markdown
@@ -1,7 +1,7 @@
1
1
  OTVision/__init__.py,sha256=b2goS-TYrN9y8WHmjG22z1oVwCrjV0WtGJE32NJfvJM,1042
2
2
  OTVision/config.py,sha256=x_Bh3mrfV434puVchuG0zLsTtjIl-_C0-ZbmRW3tyFM,24371
3
3
  OTVision/dataformat.py,sha256=BHF7qHzyNb80hI1EKfwcdJ9bgG_X4bp_hCXzdg7_MSA,1941
4
- OTVision/version.py,sha256=hg37ZIKD--k-ygX1n3HmsaIYwlaSQQT7L32Crd0BdTQ,175
4
+ OTVision/version.py,sha256=fADL6JlfA7K_Y8SkaM8PKO79Wscnqe81tRANXC3Fy2U,175
5
5
  OTVision/abstraction/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  OTVision/abstraction/observer.py,sha256=ZFGxUUjI3wUpf5ogXg2yDe-QjCcXre6SxH5zOogOx2U,1350
7
7
  OTVision/abstraction/pipes_and_filter.py,sha256=rzYaWDalXnlMbfpkgI91a0FL0q1llUlCRuIQsidYDv8,940
@@ -26,7 +26,7 @@ OTVision/convert/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
26
26
  OTVision/convert/convert.py,sha256=vRIFcrAUf9MUWAuvF_q7JQ9aN89B1ouBOCtXrCLuDp8,11013
27
27
  OTVision/detect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
28
  OTVision/detect/builder.py,sha256=fOhbspd4TuCJMDR2Vvbc4hOJqyfHTo2f6x7l_wf-IiA,6971
29
- OTVision/detect/cli.py,sha256=U2kXxfHWuxcsWMFfnfj_2ieOOIWw8yosYicucnddn2c,6026
29
+ OTVision/detect/cli.py,sha256=qsmbt43j0KAyj8XVuo-QEHxQZJ80DVmpDGZ-d1lE14I,6025
30
30
  OTVision/detect/detect.py,sha256=YaVS-DJXdEmh-OzwE31UPNl2uk7mcFyO_CKKTgMeiuM,1328
31
31
  OTVision/detect/detected_frame_buffer.py,sha256=SlnwWIqvJsSp-qt9u5Vk094JCnhfpr5luA6A2h5ZRVc,1336
32
32
  OTVision/detect/otdet.py,sha256=VsprZPBH2hUna9wYQBAQVM9rM4X9L7cD5aNS9N5RFr0,4485
@@ -47,16 +47,30 @@ OTVision/domain/input_source_detect.py,sha256=9DzkTg5dh7_KmxE9oxdmxrcTYhvZY8hHLZ
47
47
  OTVision/domain/object_detection.py,sha256=gJ5Jd5uWGSjdIue-XKddb9krFynLZ59_XctMY4HE1ww,1288
48
48
  OTVision/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
49
  OTVision/helpers/date.py,sha256=XK0fmXMombQ2CJ2_RXjk-bc-R8qqXRK2rDmCMBi0_G0,854
50
- OTVision/helpers/files.py,sha256=LMbXuLd7Nid565BtPJxv1SPqkvJhX4BF-yLJ8KMZavo,18118
50
+ OTVision/helpers/files.py,sha256=g_ix1e_ti1ZlW4zOulvkUiupcE8KJV_OHGNGXQTQ71I,18478
51
51
  OTVision/helpers/formats.py,sha256=YLo_QLA2nhVREyv5N-xNW20c4nIL7DIF40E1lrsAyLE,4365
52
+ OTVision/helpers/input_types.py,sha256=d94IYX0e-ersz3bl8xy9SklGUom-LjPUQ-BRsOmpH68,649
52
53
  OTVision/helpers/log.py,sha256=fOSMTXQRQ3_3zzYL8pDlx85IXPwyDsI2WGpK-V_R47Q,4985
53
54
  OTVision/helpers/machine.py,sha256=8Bz_Eg7PS0IL4riOVeJcEIi5D9E8Ju8-JomTkW975p8,2166
54
55
  OTVision/helpers/video.py,sha256=xyI35CiWXqoeGd3HeLhZUPxrLz8GccWyzHusxoweJr4,1480
55
56
  OTVision/track/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
- OTVision/track/iou.py,sha256=zpb4TbB34wZjUorhMLqMy6WspfsK-TkYvD75L-_ODM8,9491
57
- OTVision/track/iou_util.py,sha256=mqylrarqiauSewinxOm9CSa6dgVSl8G9bpCv--2m02M,4465
58
- OTVision/track/preprocess.py,sha256=QTKCdVWO8lj3mZ1nqDkKMLomHsiglCMYgqCe-zneick,14194
59
- OTVision/track/track.py,sha256=fKDP6jQ2pDLwxJOiDUYqs9fD3W67fNgwO3l8zUTYOw4,15344
57
+ OTVision/track/track.py,sha256=BfErHX9_55unbNqVFksDku8uAjKjH5hTfdfmhXFIC3A,5060
58
+ OTVision/track/exporter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
+ OTVision/track/exporter/filebased_exporter.py,sha256=7LFoHcqrlNCd0KDQZA5qI2sZFlxecazJ6sBwdVvvdWE,947
60
+ OTVision/track/model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
61
+ OTVision/track/model/detection.py,sha256=SZLP-87XE3NcTkeYz7GTqp4oPMiqI1P5gILp1_yHtxY,3761
62
+ OTVision/track/model/frame.py,sha256=p07Va0zhXPvkXSZR9ewgMuBK0XHD7AFNRA4HhCfaew0,5290
63
+ OTVision/track/model/track_exporter.py,sha256=FUuMsdNqWmjqRQasqB6A_0h73NAt-Mt8V93O_ajCHf4,3228
64
+ OTVision/track/model/tracking_interfaces.py,sha256=VqI8K_0DbeKgOX1d1D25b0jcn9zIvgVDlzLrfEpUEqM,10992
65
+ OTVision/track/model/filebased/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
66
+ OTVision/track/model/filebased/frame_chunk.py,sha256=Wuz0pcgaf7P1a3EOipzDTAlCIYULC_f9DbncfKdnc50,6761
67
+ OTVision/track/model/filebased/frame_group.py,sha256=f-hXS1Vc5U_qf2cgNbYVeSTZ3dg5NUJhasOEHuuX1HE,2977
68
+ OTVision/track/parser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
69
+ OTVision/track/parser/chunk_parser_plugins.py,sha256=0fj5KtSqPtlzvttyQ-tizPF9w6gwVwHSpupr5ONj8cU,3092
70
+ OTVision/track/parser/frame_group_parser_plugins.py,sha256=N5jS6Aq2Aq4gc0ouX0UOBqCUagNLRQRjS_81k_OzZi4,4575
71
+ OTVision/track/tracker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
+ OTVision/track/tracker/filebased_tracking.py,sha256=kb8CmzwyBipDaPrXdLg2g2rTZGalTC6uAvGbGQwmdVI,6553
73
+ OTVision/track/tracker/tracker_plugin_iou.py,sha256=aMZoKptwG9_TPmlohnT8VVNlbmoVODPf1GLxaINi-ZE,7235
60
74
  OTVision/transform/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
61
75
  OTVision/transform/get_homography.py,sha256=29waW61uzCCB7tlBAS2zck9sliAxZqnjjOa4jOOHHIc,5970
62
76
  OTVision/transform/reference_points_picker.py,sha256=LOmwzCaqbJnXVhaTSaZtkCzTMDamYjbTpY8I99pN0rg,16578
@@ -66,10 +80,10 @@ OTVision/view/view.py,sha256=sBFGfr-lf8lG1BTaQ3Iu46GpZGhaVhSrqg8p5XUPJ4I,3302
66
80
  OTVision/view/view_convert.py,sha256=NUtmrmAAFCYdQS1U2av_eYUScXugQYjhD3e3H0pro6Q,5157
67
81
  OTVision/view/view_detect.py,sha256=E1JP9w5iv44hYkOr86In5d6V3sgn7Ld85KaRcY0oFZU,5951
68
82
  OTVision/view/view_helpers.py,sha256=a5yV_6ZxO5bxsSymOmxdHqzOEv0VFq4wFBopVRGuVRo,16195
69
- OTVision/view/view_track.py,sha256=ioHenonuTJQn8yDzlIDV8Jc1yzWA0GHbC9CotYGtxHQ,5389
83
+ OTVision/view/view_track.py,sha256=vmfMqpbUfnzg_EsWiL-IIKNOApVF09dzSojHpUfYY6M,5393
70
84
  OTVision/view/view_transform.py,sha256=HvRd8g8geKRy0OoiZUDn_oC3SJC5nuXhZf3uZelfGKg,5473
71
85
  OTVision/view/helpers/OTC.ico,sha256=G9kwlDtgBXmXO3yxW6Z-xVFV2q4nUGuz9E1VPHSu_I8,21662
72
- otvision-0.6.0.dist-info/METADATA,sha256=zVpew3Pg7uOrFOE7-a3RWmHQl56GNFWwpr2hAXNnOa0,2244
73
- otvision-0.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
74
- otvision-0.6.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
75
- otvision-0.6.0.dist-info/RECORD,,
86
+ otvision-0.6.2.dist-info/METADATA,sha256=8-TR4TiioaiSZsl6UjTD1mDHflVsjpg7gAvYjHCT9Oc,2282
87
+ otvision-0.6.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
88
+ otvision-0.6.2.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
89
+ otvision-0.6.2.dist-info/RECORD,,
OTVision/track/iou.py DELETED
@@ -1,282 +0,0 @@
1
- """
2
- OTVision module to track road users in frames detected by OTVision
3
- """
4
-
5
- # based on IOU Tracker written by Erik Bochinski originally licensed under the
6
- # MIT License, see
7
- # https://github.com/bochinski/iou-tracker/blob/master/LICENSE.
8
-
9
-
10
- # Copyright (C) 2022 OpenTrafficCam Contributors
11
- # <https://github.com/OpenTrafficCam
12
- # <team@opentrafficcam.org>
13
- #
14
- # This program is free software: you can redistribute it and/or modify
15
- # it under the terms of the GNU General Public License as published by
16
- # the Free Software Foundation, either version 3 of the License, or
17
- # (at your option) any later version.
18
- #
19
- # This program is distributed in the hope that it will be useful,
20
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
21
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
- # GNU General Public License for more details.
23
- #
24
- # You should have received a copy of the GNU General Public License
25
- # along with this program. If not, see <https://www.gnu.org/licenses/>.
26
- from collections import defaultdict
27
- from dataclasses import dataclass
28
- from typing import Iterator
29
-
30
- from tqdm import tqdm
31
-
32
- from OTVision.config import CONFIG
33
- from OTVision.dataformat import (
34
- AGE,
35
- BBOXES,
36
- CENTER,
37
- CLASS,
38
- CONFIDENCE,
39
- DETECTIONS,
40
- FINISHED,
41
- FIRST,
42
- FRAMES,
43
- MAX_CLASS,
44
- MAX_CONF,
45
- START_FRAME,
46
- TRACK_ID,
47
- H,
48
- W,
49
- X,
50
- Y,
51
- )
52
-
53
- from .iou_util import iou
54
-
55
-
56
- class TrackedDetections:
57
- def __init__(
58
- self,
59
- detections: dict[str, dict],
60
- detected_ids: set[int],
61
- active_track_ids: set[int],
62
- ) -> None:
63
- self._detections = detections
64
- self._detected_ids = detected_ids
65
- self._active_track_ids = active_track_ids
66
-
67
- def update_active_track_ids(self, new_active_ids: set[int]) -> None:
68
- self._active_track_ids = {
69
- _id for _id in self._active_track_ids if _id in new_active_ids
70
- }
71
-
72
- def is_finished(self) -> bool:
73
- return len(self._active_track_ids) == 0
74
-
75
-
76
- @dataclass(frozen=True)
77
- class TrackingResult:
78
- tracked_detections: TrackedDetections
79
- active_tracks: list[dict]
80
- last_track_frame: dict[int, int]
81
-
82
-
83
- def make_bbox(obj: dict) -> tuple[float, float, float, float]:
84
- """Calculates xyxy coordinates from dict of xywh.
85
-
86
- Args:
87
- obj (dict): dict of pixel values for xcenter, ycenter, width and height
88
-
89
- Returns:
90
- tuple[float, float, float, float]: xmin, ymin, xmay, ymax
91
- """
92
- return (
93
- obj[X] - obj[W] / 2,
94
- obj[Y] - obj[H] / 2,
95
- obj[X] + obj[W] / 2,
96
- obj[Y] + obj[H] / 2,
97
- )
98
-
99
-
100
- def center(obj: dict) -> tuple[float, float]:
101
- """Retrieves center coordinates from dict.
102
-
103
- Args:
104
- obj (dict): _description_
105
-
106
- Returns:
107
- tuple[float, float]: _description_
108
- """
109
- return obj[X], obj[Y]
110
-
111
-
112
- def id_generator() -> Iterator[int]:
113
- ID: int = 0
114
- while True:
115
- ID += 1
116
- yield ID
117
-
118
-
119
- def track_iou(
120
- detections: list, # TODO: Type hint nested list during refactoring
121
- sigma_l: float = CONFIG["TRACK"]["IOU"]["SIGMA_L"],
122
- sigma_h: float = CONFIG["TRACK"]["IOU"]["SIGMA_H"],
123
- sigma_iou: float = CONFIG["TRACK"]["IOU"]["SIGMA_IOU"],
124
- t_min: int = CONFIG["TRACK"]["IOU"]["T_MIN"],
125
- t_miss_max: int = CONFIG["TRACK"]["IOU"]["T_MISS_MAX"],
126
- previous_active_tracks: list = [],
127
- vehicle_id_generator: Iterator[int] = id_generator(),
128
- ) -> TrackingResult: # sourcery skip: low-code-quality
129
- """
130
- Simple IOU based tracker.
131
- See "High-Speed Tracking-by-Detection Without Using Image Information
132
- by E. Bochinski, V. Eiselein, T. Sikora" for
133
- more information.
134
-
135
- Args:
136
- detections (list): list of detections per frame, usually generated
137
- by util.load_mot
138
- sigma_l (float): low detection threshold.
139
- sigma_h (float): high detection threshold.
140
- sigma_iou (float): IOU threshold.
141
- t_min (float): minimum track length in frames.
142
- previous_active_tracks (list): a list of remaining active tracks
143
- from previous iterations.
144
- vehicle_id_generator (Iterator[int]): provides ids for new tracks
145
-
146
- Returns:
147
- TrackingResult: new detections, a list of active tracks
148
- and a lookup dic for each tracks last detection frame.
149
- """
150
-
151
- _check_types(sigma_l, sigma_h, sigma_iou, t_min, t_miss_max)
152
-
153
- tracks_active: list = []
154
- tracks_active.extend(previous_active_tracks)
155
- # tracks_finished = []
156
-
157
- vehIDs_finished: list = []
158
- new_detections: dict = {}
159
-
160
- for frame_num in tqdm(detections, desc="Tracked frames", unit=" frames"):
161
- detections_frame = detections[frame_num][DETECTIONS]
162
- # apply low threshold to detections
163
- dets = [det for det in detections_frame if det[CONFIDENCE] >= sigma_l]
164
- new_detections[frame_num] = {}
165
- updated_tracks: list = []
166
- saved_tracks: list = []
167
- for track in tracks_active:
168
- if dets:
169
- # get det with highest iou
170
- best_match = max(
171
- dets, key=lambda x: iou(track[BBOXES][-1], make_bbox(x))
172
- )
173
- if iou(track[BBOXES][-1], make_bbox(best_match)) >= sigma_iou:
174
- track[FRAMES].append(int(frame_num))
175
- track[BBOXES].append(make_bbox(best_match))
176
- track[CENTER].append(center(best_match))
177
- track[CONFIDENCE].append(best_match[CONFIDENCE])
178
- track[CLASS].append(best_match[CLASS])
179
- track[MAX_CONF] = max(track[MAX_CONF], best_match[CONFIDENCE])
180
- track[AGE] = 0
181
-
182
- updated_tracks.append(track)
183
-
184
- # remove best matching detection from detections
185
- del dets[dets.index(best_match)]
186
- # best_match[TRACK_ID] = track[TRACK_ID]
187
- best_match[FIRST] = False
188
- new_detections[frame_num][track[TRACK_ID]] = best_match
189
-
190
- # if track was not updated
191
- if not updated_tracks or track is not updated_tracks[-1]:
192
- # finish track when the conditions are met
193
- if track[AGE] < t_miss_max:
194
- track[AGE] += 1
195
- saved_tracks.append(track)
196
- elif (
197
- track[MAX_CONF] >= sigma_h
198
- and track[FRAMES][-1] - track[FRAMES][0] >= t_min
199
- ):
200
- # tracks_finished.append(track)
201
- vehIDs_finished.append(track[TRACK_ID])
202
- # TODO: Alter der Tracks
203
- # create new tracks
204
- new_tracks = []
205
- for det in dets:
206
- vehID = next(vehicle_id_generator)
207
- new_tracks.append(
208
- {
209
- FRAMES: [int(frame_num)],
210
- BBOXES: [make_bbox(det)],
211
- CENTER: [center(det)],
212
- CONFIDENCE: [det[CONFIDENCE]],
213
- CLASS: [det[CLASS]],
214
- MAX_CLASS: det[CLASS],
215
- MAX_CONF: det[CONFIDENCE],
216
- TRACK_ID: vehID,
217
- START_FRAME: int(frame_num),
218
- AGE: 0,
219
- }
220
- )
221
- # det[TRACK_ID] = vehID
222
- det[FIRST] = True
223
- new_detections[frame_num][vehID] = det
224
- tracks_active = updated_tracks + saved_tracks + new_tracks
225
-
226
- # finish all remaining active tracks
227
- # tracks_finished += [
228
- # track
229
- # for track in tracks_active
230
- # if (
231
- # track["max_conf"] >= sigma_h
232
- # and track["frames"][-1] - track["frames"][0] >= t_min
233
- # )
234
- # ]
235
-
236
- # for track in tracks_finished:
237
- # track["max_class"] = pd.Series(track["class"]).mode().iat[0]
238
-
239
- # TODO: #82 Use dict comprehensions in track_iou
240
- # save last occurrence frame of tracks
241
- last_track_frame: dict[int, int] = defaultdict(lambda: -1)
242
-
243
- for frame_num, frame_det in tqdm(
244
- new_detections.items(), desc="New detection frames", unit=" frames"
245
- ):
246
- for vehID, det in frame_det.items():
247
- det[FINISHED] = False
248
- det[TRACK_ID] = vehID
249
- last_track_frame[vehID] = max(frame_num, last_track_frame[vehID])
250
-
251
- # return tracks_finished
252
- # TODO: #83 Remove unnecessary code (e.g. for tracks_finished) from track_iou
253
-
254
- active_track_ids = {t[TRACK_ID] for t in tracks_active}
255
- detected_ids = set(last_track_frame.keys())
256
- return TrackingResult(
257
- TrackedDetections(
258
- detections=new_detections,
259
- detected_ids=detected_ids,
260
- active_track_ids={_id for _id in detected_ids if _id in active_track_ids},
261
- ),
262
- active_tracks=tracks_active,
263
- last_track_frame=last_track_frame,
264
- )
265
- # return new_detections, tracks_active, last_track_frame
266
-
267
-
268
- def _check_types(
269
- sigma_l: float, sigma_h: float, sigma_iou: float, t_min: int, t_miss_max: int
270
- ) -> None:
271
- """Raise ValueErrors if wrong types"""
272
-
273
- if not isinstance(sigma_l, (int, float)):
274
- raise ValueError("sigma_l has to be int or float")
275
- if not isinstance(sigma_h, (int, float)):
276
- raise ValueError("sigma_h has to be int or float")
277
- if not isinstance(sigma_iou, (int, float)):
278
- raise ValueError("sigma_iou has to be int or float")
279
- if not isinstance(t_min, int):
280
- raise ValueError("t_min has to be int")
281
- if not isinstance(t_miss_max, int):
282
- raise ValueError("t_miss_max has to be int")