OTVision 0.6.4__py3-none-any.whl → 0.6.5__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.
- OTVision/abstraction/defaults.py +15 -0
- OTVision/application/config.py +475 -0
- OTVision/application/config_parser.py +280 -0
- OTVision/application/configure_logger.py +1 -1
- OTVision/application/detect/factory.py +1 -1
- OTVision/application/detect/update_detect_config_with_cli_args.py +2 -1
- OTVision/application/get_config.py +2 -1
- OTVision/application/get_current_config.py +1 -1
- OTVision/application/track/__init__.py +0 -0
- OTVision/application/track/get_track_cli_args.py +20 -0
- OTVision/application/track/update_current_track_config.py +42 -0
- OTVision/application/track/update_track_config_with_cli_args.py +52 -0
- OTVision/application/update_current_config.py +1 -1
- OTVision/config.py +61 -668
- OTVision/convert/convert.py +3 -3
- OTVision/detect/builder.py +27 -20
- OTVision/detect/cli.py +3 -3
- OTVision/detect/detected_frame_buffer.py +3 -0
- OTVision/detect/file_based_detect_builder.py +19 -0
- OTVision/detect/otdet.py +54 -1
- OTVision/detect/otdet_file_writer.py +3 -2
- OTVision/detect/rtsp_based_detect_builder.py +37 -0
- OTVision/detect/rtsp_input_source.py +199 -0
- OTVision/detect/timestamper.py +1 -1
- OTVision/detect/video_input_source.py +2 -1
- OTVision/detect/yolo.py +17 -1
- OTVision/domain/cli.py +31 -1
- OTVision/domain/current_config.py +1 -1
- OTVision/domain/object_detection.py +6 -1
- OTVision/domain/serialization.py +12 -0
- OTVision/domain/time.py +13 -0
- OTVision/helpers/files.py +14 -15
- OTVision/plugin/__init__.py +0 -0
- OTVision/plugin/yaml_serialization.py +20 -0
- OTVision/track/builder.py +132 -0
- OTVision/track/cli.py +128 -0
- OTVision/track/exporter/filebased_exporter.py +2 -1
- OTVision/track/id_generator.py +15 -0
- OTVision/track/model/track_exporter.py +2 -1
- OTVision/track/model/tracking_interfaces.py +6 -6
- OTVision/track/parser/frame_group_parser_plugins.py +35 -5
- OTVision/track/track.py +54 -133
- OTVision/track/tracker/filebased_tracking.py +8 -7
- OTVision/track/tracker/tracker_plugin_iou.py +14 -9
- OTVision/transform/transform.py +2 -2
- OTVision/version.py +1 -1
- otvision-0.6.5.dist-info/METADATA +182 -0
- {otvision-0.6.4.dist-info → otvision-0.6.5.dist-info}/RECORD +50 -33
- otvision-0.6.4.dist-info/METADATA +0 -49
- {otvision-0.6.4.dist-info → otvision-0.6.5.dist-info}/WHEEL +0 -0
- {otvision-0.6.4.dist-info → otvision-0.6.5.dist-info}/licenses/LICENSE +0 -0
OTVision/config.py
CHANGED
|
@@ -21,679 +21,71 @@ OTVision config module for setting default values
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
import logging
|
|
24
|
-
from dataclasses import dataclass, field
|
|
25
|
-
from datetime import datetime, timedelta
|
|
26
24
|
from pathlib import Path
|
|
27
25
|
|
|
28
|
-
import
|
|
29
|
-
|
|
26
|
+
from OTVision.application.config import (
|
|
27
|
+
AVAILABLE_WEIGHTS,
|
|
28
|
+
CALIBRATIONS,
|
|
29
|
+
COL_WIDTH,
|
|
30
|
+
CONF,
|
|
31
|
+
CONVERT,
|
|
32
|
+
DEFAULT_FILETYPE,
|
|
33
|
+
DELETE_INPUT,
|
|
34
|
+
DETECT,
|
|
35
|
+
DETECT_END,
|
|
36
|
+
DETECT_START,
|
|
37
|
+
DETECTIONS,
|
|
38
|
+
EXPECTED_DURATION,
|
|
39
|
+
FILETYPES,
|
|
40
|
+
FONT,
|
|
41
|
+
FONT_SIZE,
|
|
42
|
+
FPS_FROM_FILENAME,
|
|
43
|
+
FRAME_WIDTH,
|
|
44
|
+
GUI,
|
|
45
|
+
HALF_PRECISION,
|
|
46
|
+
IMG,
|
|
47
|
+
IMG_SIZE,
|
|
48
|
+
INPUT_FPS,
|
|
49
|
+
IOU,
|
|
50
|
+
LAST_PATHS,
|
|
51
|
+
LOCATION_X,
|
|
52
|
+
LOCATION_Y,
|
|
53
|
+
LOG,
|
|
54
|
+
LOG_LEVEL_CONSOLE,
|
|
55
|
+
LOG_LEVEL_FILE,
|
|
56
|
+
NORMALIZED,
|
|
57
|
+
OTC_ICON,
|
|
58
|
+
OUTPUT_FILETYPE,
|
|
59
|
+
OUTPUT_FPS,
|
|
60
|
+
OVERWRITE,
|
|
61
|
+
PATHS,
|
|
62
|
+
REFPTS,
|
|
63
|
+
ROTATION,
|
|
64
|
+
RUN_CHAINED,
|
|
65
|
+
SEARCH_SUBDIRS,
|
|
66
|
+
SIGMA_H,
|
|
67
|
+
SIGMA_IOU,
|
|
68
|
+
SIGMA_L,
|
|
69
|
+
T_MIN,
|
|
70
|
+
T_MISS_MAX,
|
|
71
|
+
TRACK,
|
|
72
|
+
TRACKS,
|
|
73
|
+
TRANSFORM,
|
|
74
|
+
UNDISTORT,
|
|
75
|
+
VID,
|
|
76
|
+
VID_ROTATABLE,
|
|
77
|
+
VIDEOS,
|
|
78
|
+
WEIGHTS,
|
|
79
|
+
WINDOW,
|
|
80
|
+
YOLO,
|
|
81
|
+
Config,
|
|
82
|
+
)
|
|
83
|
+
from OTVision.application.config_parser import ConfigParser
|
|
30
84
|
from OTVision.helpers.log import LOGGER_NAME
|
|
85
|
+
from OTVision.plugin.yaml_serialization import YamlDeserializer
|
|
31
86
|
|
|
32
87
|
log = logging.getLogger(LOGGER_NAME)
|
|
33
88
|
|
|
34
|
-
# CONFIG dict keys
|
|
35
|
-
AVAILABLE_WEIGHTS = "AVAILABLEWEIGHTS"
|
|
36
|
-
CALIBRATIONS = "CALIBRATIONS"
|
|
37
|
-
COL_WIDTH = "COLWIDTH"
|
|
38
|
-
CONF = "CONF"
|
|
39
|
-
CONVERT = "CONVERT"
|
|
40
|
-
DEFAULT_FILETYPE = "DEFAULT_FILETYPE"
|
|
41
|
-
DELETE_INPUT = "DELETE_INPUT"
|
|
42
|
-
ROTATION = "ROTATION"
|
|
43
|
-
DETECT = "DETECT"
|
|
44
|
-
DETECTIONS = "DETECTIONS"
|
|
45
|
-
FILETYPES = "FILETYPES"
|
|
46
|
-
FONT = "FONT"
|
|
47
|
-
FONT_SIZE = "FONTSIZE"
|
|
48
|
-
FPS_FROM_FILENAME = "FPS_FROM_FILENAME"
|
|
49
|
-
FRAME_WIDTH = "FRAMEWIDTH"
|
|
50
|
-
GUI = "GUI"
|
|
51
|
-
HALF_PRECISION = "HALF_PRECISION"
|
|
52
|
-
INPUT_FPS = "INPUT_FPS"
|
|
53
|
-
IMG = "IMG"
|
|
54
|
-
IMG_SIZE = "IMGSIZE"
|
|
55
|
-
IOU = "IOU"
|
|
56
|
-
LAST_PATHS = "LAST PATHS"
|
|
57
|
-
LOCATION_X = "LOCATION_X"
|
|
58
|
-
LOCATION_Y = "LOCATION_Y"
|
|
59
|
-
NORMALIZED = "NORMALIZED"
|
|
60
|
-
OTC_ICON = "OTC ICON"
|
|
61
|
-
OUTPUT_FPS = "OUTPUT_FPS"
|
|
62
|
-
OUTPUT_FILETYPE = "OUTPUT_FILETYPE"
|
|
63
|
-
OVERWRITE = "OVERWRITE"
|
|
64
|
-
PATHS = "PATHS"
|
|
65
|
-
RUN_CHAINED = "RUN_CHAINED"
|
|
66
|
-
EXPECTED_DURATION = "EXPECTED_DURATION"
|
|
67
|
-
REFPTS = "REFPTS"
|
|
68
|
-
SEARCH_SUBDIRS = "SEARCH_SUBDIRS"
|
|
69
|
-
SIGMA_H = "SIGMA_H"
|
|
70
|
-
SIGMA_IOU = "SIGMA_IOU"
|
|
71
|
-
SIGMA_L = "SIGMA_L"
|
|
72
|
-
T_MIN = "T_MIN"
|
|
73
|
-
T_MISS_MAX = "T_MISS_MAX"
|
|
74
|
-
TRACK = "TRACK"
|
|
75
|
-
TRACKS = "TRACKS"
|
|
76
|
-
TRANSFORM = "TRANSFORM"
|
|
77
|
-
UNDISTORT = "UNDISTORT"
|
|
78
|
-
VID = "VID"
|
|
79
|
-
VID_ROTATABLE = "VID_ROTATABLE"
|
|
80
|
-
VIDEOS = "VIDEOS"
|
|
81
|
-
WEIGHTS = "WEIGHTS"
|
|
82
|
-
WINDOW = "WINDOW"
|
|
83
|
-
YOLO = "YOLO"
|
|
84
|
-
LOG = "LOG"
|
|
85
|
-
LOG_LEVEL_CONSOLE = "LOG_LEVEL_CONSOLE"
|
|
86
|
-
LOG_LEVEL_FILE = "LOG_LEVEL_FILE"
|
|
87
|
-
LOG_DIR = "LOG_DIR"
|
|
88
|
-
START_TIME = "START_TIME"
|
|
89
|
-
DETECT_END = "DETECT_END"
|
|
90
|
-
DETECT_START = "DETECT_START"
|
|
91
|
-
|
|
92
|
-
DATETIME_FORMAT = "%Y-%m-%d_%H-%M-%S"
|
|
93
|
-
|
|
94
|
-
"""Default length of a video is 15 minutes."""
|
|
95
|
-
DEFAULT_EXPECTED_DURATION: timedelta = timedelta(minutes=15)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
@dataclass(frozen=True)
|
|
99
|
-
class _LogConfig:
|
|
100
|
-
log_level_console: str = "WARNING"
|
|
101
|
-
log_level_file: str = "DEBUG"
|
|
102
|
-
|
|
103
|
-
@staticmethod
|
|
104
|
-
def from_dict(d: dict) -> "_LogConfig":
|
|
105
|
-
return _LogConfig(
|
|
106
|
-
d.get(LOG_LEVEL_CONSOLE, _LogConfig.log_level_console),
|
|
107
|
-
d.get(LOG_LEVEL_FILE, _LogConfig.log_level_file),
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
def to_dict(self) -> dict:
|
|
111
|
-
return {
|
|
112
|
-
LOG_LEVEL_CONSOLE: self.log_level_console,
|
|
113
|
-
LOG_LEVEL_FILE: self.log_level_file,
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
@dataclass(frozen=True)
|
|
118
|
-
class _DefaultFiletype:
|
|
119
|
-
video: str = ".mp4"
|
|
120
|
-
image: str = ".jpg"
|
|
121
|
-
detect: str = ".otdet"
|
|
122
|
-
track: str = ".ottrk"
|
|
123
|
-
refpts: str = ".otrfpts"
|
|
124
|
-
|
|
125
|
-
@staticmethod
|
|
126
|
-
def from_dict(d: dict) -> "_DefaultFiletype":
|
|
127
|
-
return _DefaultFiletype(
|
|
128
|
-
d.get(VID, _DefaultFiletype.video),
|
|
129
|
-
d.get(IMG, _DefaultFiletype.image),
|
|
130
|
-
d.get(DETECT, _DefaultFiletype.detect),
|
|
131
|
-
d.get(TRACK, _DefaultFiletype.track),
|
|
132
|
-
d.get(REFPTS, _DefaultFiletype.refpts),
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
def to_dict(self) -> dict:
|
|
136
|
-
return {
|
|
137
|
-
VID: self.video,
|
|
138
|
-
IMG: self.image,
|
|
139
|
-
DETECT: self.detect,
|
|
140
|
-
TRACK: self.track,
|
|
141
|
-
REFPTS: self.refpts,
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
@dataclass(frozen=True)
|
|
146
|
-
class _VideoFiletypes:
|
|
147
|
-
avi: str = ".avi"
|
|
148
|
-
mkv: str = ".mkv"
|
|
149
|
-
mov: str = ".mov"
|
|
150
|
-
mp4: str = ".mp4"
|
|
151
|
-
|
|
152
|
-
def to_list(self) -> list:
|
|
153
|
-
return [
|
|
154
|
-
self.avi,
|
|
155
|
-
self.mkv,
|
|
156
|
-
self.mov,
|
|
157
|
-
self.mp4,
|
|
158
|
-
]
|
|
159
|
-
|
|
160
|
-
def rotatable_to_list(self) -> list:
|
|
161
|
-
return [self.mov, self.mp4]
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
@dataclass(frozen=True)
|
|
165
|
-
class _ImageFiletypes:
|
|
166
|
-
jpg: str = ".jpg"
|
|
167
|
-
jpeg: str = ".jpeg"
|
|
168
|
-
png: str = ".png"
|
|
169
|
-
|
|
170
|
-
def to_list(self) -> list:
|
|
171
|
-
return [self.jpg, self.jpeg, self.png]
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
@dataclass(frozen=True)
|
|
175
|
-
class _Filetypes:
|
|
176
|
-
video_filetypes: _VideoFiletypes = _VideoFiletypes()
|
|
177
|
-
image_filetypes: _ImageFiletypes = _ImageFiletypes()
|
|
178
|
-
detect: str = _DefaultFiletype.detect
|
|
179
|
-
track: str = _DefaultFiletype.track
|
|
180
|
-
refpts: str = _DefaultFiletype.refpts
|
|
181
|
-
transform: str = ".gpkg"
|
|
182
|
-
|
|
183
|
-
def to_dict(self) -> dict:
|
|
184
|
-
return {
|
|
185
|
-
VID: self.video_filetypes.to_list(),
|
|
186
|
-
VID_ROTATABLE: self.video_filetypes.rotatable_to_list(),
|
|
187
|
-
IMG: self.image_filetypes.to_list(),
|
|
188
|
-
DETECT: [self.detect],
|
|
189
|
-
TRACK: [self.track],
|
|
190
|
-
REFPTS: [self.refpts],
|
|
191
|
-
TRANSFORM: [self.transform],
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
@dataclass(frozen=True)
|
|
196
|
-
class _LastPaths:
|
|
197
|
-
videos: list = field(default_factory=list)
|
|
198
|
-
detections: list = field(default_factory=list)
|
|
199
|
-
tracks: list = field(default_factory=list)
|
|
200
|
-
calibrations: list = field(default_factory=list)
|
|
201
|
-
refpts: list = field(default_factory=list)
|
|
202
|
-
|
|
203
|
-
def to_dict(self) -> dict:
|
|
204
|
-
return {
|
|
205
|
-
VIDEOS: self.videos,
|
|
206
|
-
DETECTIONS: self.detections,
|
|
207
|
-
TRACKS: self.tracks,
|
|
208
|
-
CALIBRATIONS: self.calibrations,
|
|
209
|
-
REFPTS: self.refpts,
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
@dataclass(frozen=True)
|
|
214
|
-
class ConvertConfig:
|
|
215
|
-
paths: list[Path] = field(default_factory=list)
|
|
216
|
-
run_chained: bool = True
|
|
217
|
-
output_filetype: str = _VideoFiletypes.mp4
|
|
218
|
-
input_fps: float = 20.0
|
|
219
|
-
output_fps: float = 20.0
|
|
220
|
-
fps_from_filename: bool = True
|
|
221
|
-
delete_input: bool = False
|
|
222
|
-
rotation: int = 0
|
|
223
|
-
overwrite: bool = True
|
|
224
|
-
|
|
225
|
-
@staticmethod
|
|
226
|
-
def from_dict(d: dict) -> "ConvertConfig":
|
|
227
|
-
return ConvertConfig(
|
|
228
|
-
d.get(PATHS, []),
|
|
229
|
-
d.get(RUN_CHAINED, ConvertConfig.run_chained),
|
|
230
|
-
d.get(OUTPUT_FILETYPE, ConvertConfig.output_filetype),
|
|
231
|
-
d.get(INPUT_FPS, ConvertConfig.input_fps),
|
|
232
|
-
d.get(OUTPUT_FPS, ConvertConfig.output_fps),
|
|
233
|
-
d.get(FPS_FROM_FILENAME, ConvertConfig.fps_from_filename),
|
|
234
|
-
d.get(DELETE_INPUT, ConvertConfig.delete_input),
|
|
235
|
-
d.get(ROTATION, ConvertConfig.rotation),
|
|
236
|
-
d.get(OVERWRITE, ConvertConfig.overwrite),
|
|
237
|
-
)
|
|
238
|
-
|
|
239
|
-
def to_dict(self) -> dict:
|
|
240
|
-
return {
|
|
241
|
-
PATHS: self.paths,
|
|
242
|
-
RUN_CHAINED: self.run_chained,
|
|
243
|
-
OUTPUT_FILETYPE: self.output_filetype,
|
|
244
|
-
INPUT_FPS: self.input_fps,
|
|
245
|
-
OUTPUT_FPS: self.output_fps,
|
|
246
|
-
FPS_FROM_FILENAME: self.fps_from_filename,
|
|
247
|
-
DELETE_INPUT: self.delete_input,
|
|
248
|
-
ROTATION: self.rotation,
|
|
249
|
-
OVERWRITE: self.overwrite,
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
@dataclass(frozen=True)
|
|
254
|
-
class _YoloWeights:
|
|
255
|
-
yolov8s: str = "yolov8s"
|
|
256
|
-
yolov8m: str = "yolov8m"
|
|
257
|
-
yolov8l: str = "yolov8l"
|
|
258
|
-
yolov8x: str = "yolov8x"
|
|
259
|
-
|
|
260
|
-
def to_list(self) -> list:
|
|
261
|
-
return [self.yolov8s, self.yolov8m, self.yolov8l, self.yolov8x]
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
@dataclass(frozen=True)
|
|
265
|
-
class YoloConfig:
|
|
266
|
-
"""Represents the configuration for the YOLO model.
|
|
267
|
-
|
|
268
|
-
Attributes:
|
|
269
|
-
weights (str): Path to YOLO model weights.
|
|
270
|
-
available_weights (_YoloWeights): List of available default YOLO model weights.
|
|
271
|
-
conf (float): Confidence threshold.
|
|
272
|
-
iou (float): Intersection over union threshold.
|
|
273
|
-
img_size (int): Size of the input image.
|
|
274
|
-
chunk_size (int): Chunk size for processing.
|
|
275
|
-
normalized (bool): Whether to normalize the bounding boxes.
|
|
276
|
-
"""
|
|
277
|
-
|
|
278
|
-
weights: str = _YoloWeights.yolov8s
|
|
279
|
-
available_weights: _YoloWeights = _YoloWeights()
|
|
280
|
-
conf: float = 0.25
|
|
281
|
-
iou: float = 0.45
|
|
282
|
-
img_size: int = 640
|
|
283
|
-
chunk_size: int = 1
|
|
284
|
-
normalized: bool = False
|
|
285
|
-
|
|
286
|
-
@staticmethod
|
|
287
|
-
def from_dict(d: dict) -> "YoloConfig":
|
|
288
|
-
return YoloConfig(
|
|
289
|
-
weights=d.get(WEIGHTS, YoloConfig.weights),
|
|
290
|
-
conf=d.get(CONF, YoloConfig.conf),
|
|
291
|
-
iou=d.get(IOU, YoloConfig.iou),
|
|
292
|
-
img_size=d.get(IMG_SIZE, YoloConfig.img_size),
|
|
293
|
-
normalized=d.get(NORMALIZED, YoloConfig.normalized),
|
|
294
|
-
)
|
|
295
|
-
|
|
296
|
-
def to_dict(self) -> dict:
|
|
297
|
-
return {
|
|
298
|
-
WEIGHTS: self.weights,
|
|
299
|
-
AVAILABLE_WEIGHTS: self.available_weights.to_list(),
|
|
300
|
-
CONF: self.conf,
|
|
301
|
-
IOU: self.iou,
|
|
302
|
-
IMG_SIZE: self.img_size,
|
|
303
|
-
NORMALIZED: self.normalized,
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
@dataclass(frozen=True)
|
|
308
|
-
class DetectConfig:
|
|
309
|
-
"""Represents the configuration for the `detect` command.
|
|
310
|
-
|
|
311
|
-
Attributes:
|
|
312
|
-
paths (list[Path]): List of files to be processed.
|
|
313
|
-
run_chained (bool): Whether to run chained commands.
|
|
314
|
-
yolo_config (YoloConfig): Configuration for the YOLO model.
|
|
315
|
-
expected_duration (timedelta | None): Expected duration of the video.
|
|
316
|
-
`None` if unknown.
|
|
317
|
-
overwrite (bool): Whether to overwrite existing files.
|
|
318
|
-
half_precision (bool): Whether to use half precision.
|
|
319
|
-
detect_start (int | None): Start frame for detection expressed in seconds.
|
|
320
|
-
Value `None` marks the start of the video.
|
|
321
|
-
detect_end (int | None): End frame for detection expressed in seconds.
|
|
322
|
-
Value `None` marks the end of the video.
|
|
323
|
-
|
|
324
|
-
"""
|
|
325
|
-
|
|
326
|
-
@property
|
|
327
|
-
def confidence(self) -> float:
|
|
328
|
-
"""Gets the confidence level set in the YOLO configuration.
|
|
329
|
-
|
|
330
|
-
Returns:
|
|
331
|
-
float: The intersection over union threshold value.
|
|
332
|
-
"""
|
|
333
|
-
return self.yolo_config.conf
|
|
334
|
-
|
|
335
|
-
@property
|
|
336
|
-
def weights(self) -> str:
|
|
337
|
-
return self.yolo_config.weights
|
|
338
|
-
|
|
339
|
-
@property
|
|
340
|
-
def iou(self) -> float:
|
|
341
|
-
return self.yolo_config.iou
|
|
342
|
-
|
|
343
|
-
@property
|
|
344
|
-
def img_size(self) -> int:
|
|
345
|
-
return self.yolo_config.img_size
|
|
346
|
-
|
|
347
|
-
@property
|
|
348
|
-
def normalized(self) -> bool:
|
|
349
|
-
return self.yolo_config.normalized
|
|
350
|
-
|
|
351
|
-
paths: list[Path] = field(default_factory=list)
|
|
352
|
-
run_chained: bool = True
|
|
353
|
-
yolo_config: YoloConfig = YoloConfig()
|
|
354
|
-
expected_duration: timedelta | None = None
|
|
355
|
-
overwrite: bool = True
|
|
356
|
-
half_precision: bool = False
|
|
357
|
-
start_time: datetime | None = None
|
|
358
|
-
detect_start: int | None = None
|
|
359
|
-
detect_end: int | None = None
|
|
360
|
-
|
|
361
|
-
@staticmethod
|
|
362
|
-
def from_dict(d: dict) -> "DetectConfig":
|
|
363
|
-
yolo_config_dict = d.get(YOLO)
|
|
364
|
-
yolo_config = (
|
|
365
|
-
YoloConfig.from_dict(yolo_config_dict)
|
|
366
|
-
if yolo_config_dict
|
|
367
|
-
else DetectConfig.yolo_config
|
|
368
|
-
)
|
|
369
|
-
|
|
370
|
-
files = [Path(file).expanduser() for file in d.get(PATHS, [])]
|
|
371
|
-
expected_duration = d.get(EXPECTED_DURATION, None)
|
|
372
|
-
if expected_duration is not None:
|
|
373
|
-
expected_duration = timedelta(seconds=int(expected_duration))
|
|
374
|
-
|
|
375
|
-
start_time = DetectConfig._parse_start_time(d)
|
|
376
|
-
return DetectConfig(
|
|
377
|
-
files,
|
|
378
|
-
d.get(RUN_CHAINED, DetectConfig.run_chained),
|
|
379
|
-
yolo_config,
|
|
380
|
-
expected_duration,
|
|
381
|
-
d.get(OVERWRITE, DetectConfig.overwrite),
|
|
382
|
-
d.get(HALF_PRECISION, DetectConfig.half_precision),
|
|
383
|
-
start_time,
|
|
384
|
-
d.get(DETECT_START, DetectConfig.detect_start),
|
|
385
|
-
d.get(DETECT_END, DetectConfig.detect_end),
|
|
386
|
-
)
|
|
387
|
-
|
|
388
|
-
@staticmethod
|
|
389
|
-
def _parse_start_time(d: dict) -> datetime | None:
|
|
390
|
-
if start_time := d.get(START_TIME, DetectConfig.start_time):
|
|
391
|
-
return datetime.strptime(start_time, DATETIME_FORMAT)
|
|
392
|
-
return start_time
|
|
393
|
-
|
|
394
|
-
def to_dict(self) -> dict:
|
|
395
|
-
expected_duration = (
|
|
396
|
-
int(self.expected_duration.total_seconds())
|
|
397
|
-
if self.expected_duration is not None
|
|
398
|
-
else None
|
|
399
|
-
)
|
|
400
|
-
return {
|
|
401
|
-
PATHS: [str(p) for p in self.paths],
|
|
402
|
-
RUN_CHAINED: self.run_chained,
|
|
403
|
-
YOLO: self.yolo_config.to_dict(),
|
|
404
|
-
EXPECTED_DURATION: expected_duration,
|
|
405
|
-
OVERWRITE: self.overwrite,
|
|
406
|
-
HALF_PRECISION: self.half_precision,
|
|
407
|
-
START_TIME: self.start_time,
|
|
408
|
-
DETECT_START: self.detect_start,
|
|
409
|
-
DETECT_END: self.detect_end,
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
@dataclass(frozen=True)
|
|
414
|
-
class _TrackIouConfig:
|
|
415
|
-
sigma_l: float = 0.27
|
|
416
|
-
sigma_h: float = 0.42
|
|
417
|
-
sigma_iou: float = 0.38
|
|
418
|
-
t_min: int = 5
|
|
419
|
-
t_miss_max: int = 51
|
|
420
|
-
|
|
421
|
-
@staticmethod
|
|
422
|
-
def from_dict(d: dict) -> "_TrackIouConfig":
|
|
423
|
-
return _TrackIouConfig(
|
|
424
|
-
d.get(SIGMA_L, _TrackIouConfig.sigma_l),
|
|
425
|
-
d.get(SIGMA_H, _TrackIouConfig.sigma_h),
|
|
426
|
-
d.get(SIGMA_IOU, _TrackIouConfig.sigma_iou),
|
|
427
|
-
d.get(T_MIN, _TrackIouConfig.t_min),
|
|
428
|
-
d.get(T_MISS_MAX, _TrackIouConfig.t_miss_max),
|
|
429
|
-
)
|
|
430
|
-
|
|
431
|
-
def to_dict(self) -> dict:
|
|
432
|
-
return {
|
|
433
|
-
SIGMA_L: self.sigma_l,
|
|
434
|
-
SIGMA_H: self.sigma_h,
|
|
435
|
-
SIGMA_IOU: self.sigma_iou,
|
|
436
|
-
T_MIN: self.t_min,
|
|
437
|
-
T_MISS_MAX: self.t_miss_max,
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
@dataclass(frozen=True)
|
|
442
|
-
class TrackConfig:
|
|
443
|
-
paths: list[Path] = field(default_factory=list)
|
|
444
|
-
run_chained: bool = True
|
|
445
|
-
iou: _TrackIouConfig = _TrackIouConfig()
|
|
446
|
-
overwrite: bool = True
|
|
447
|
-
|
|
448
|
-
@staticmethod
|
|
449
|
-
def from_dict(d: dict) -> "TrackConfig":
|
|
450
|
-
iou_config_dict = d.get(IOU)
|
|
451
|
-
iou_config = (
|
|
452
|
-
_TrackIouConfig.from_dict(iou_config_dict)
|
|
453
|
-
if iou_config_dict
|
|
454
|
-
else TrackConfig.iou
|
|
455
|
-
)
|
|
456
|
-
|
|
457
|
-
return TrackConfig(
|
|
458
|
-
d.get(PATHS, []),
|
|
459
|
-
d.get(RUN_CHAINED, TrackConfig.run_chained),
|
|
460
|
-
iou_config,
|
|
461
|
-
d.get(OVERWRITE, TrackConfig.overwrite),
|
|
462
|
-
)
|
|
463
|
-
|
|
464
|
-
def to_dict(self) -> dict:
|
|
465
|
-
return {
|
|
466
|
-
PATHS: [str(p) for p in self.paths],
|
|
467
|
-
RUN_CHAINED: self.run_chained,
|
|
468
|
-
IOU: self.iou.to_dict(),
|
|
469
|
-
OVERWRITE: self.overwrite,
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
@dataclass(frozen=True)
|
|
474
|
-
class _UndistortConfig:
|
|
475
|
-
overwrite: bool = False
|
|
476
|
-
|
|
477
|
-
@staticmethod
|
|
478
|
-
def from_dict(d: dict) -> "_UndistortConfig":
|
|
479
|
-
return _UndistortConfig(
|
|
480
|
-
d.get(OVERWRITE, _UndistortConfig.overwrite),
|
|
481
|
-
)
|
|
482
|
-
|
|
483
|
-
def to_dict(self) -> dict:
|
|
484
|
-
return {OVERWRITE: self.overwrite}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
@dataclass(frozen=True)
|
|
488
|
-
class _TransformConfig:
|
|
489
|
-
paths: list[Path] = field(default_factory=list)
|
|
490
|
-
run_chained: bool = True
|
|
491
|
-
overwrite: bool = True
|
|
492
|
-
|
|
493
|
-
@staticmethod
|
|
494
|
-
def from_dict(d: dict) -> "_TransformConfig":
|
|
495
|
-
return _TransformConfig(
|
|
496
|
-
d.get(PATHS, []),
|
|
497
|
-
d.get(RUN_CHAINED, _TransformConfig.run_chained),
|
|
498
|
-
d.get(OVERWRITE, _TransformConfig.overwrite),
|
|
499
|
-
)
|
|
500
|
-
|
|
501
|
-
def to_dict(self) -> dict:
|
|
502
|
-
return {
|
|
503
|
-
PATHS: [str(p) for p in self.paths],
|
|
504
|
-
RUN_CHAINED: self.run_chained,
|
|
505
|
-
OVERWRITE: self.overwrite,
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
@dataclass(frozen=True)
|
|
510
|
-
class _GuiWindowConfig:
|
|
511
|
-
location_x: int = 0
|
|
512
|
-
location_y: int = 0
|
|
513
|
-
|
|
514
|
-
@staticmethod
|
|
515
|
-
def from_dict(d: dict) -> "_GuiWindowConfig":
|
|
516
|
-
return _GuiWindowConfig(
|
|
517
|
-
d.get(LOCATION_X, _GuiWindowConfig.location_x),
|
|
518
|
-
d.get(LOCATION_Y, _GuiWindowConfig.location_y),
|
|
519
|
-
)
|
|
520
|
-
|
|
521
|
-
def to_dict(self) -> dict:
|
|
522
|
-
return {LOCATION_X: self.location_x, LOCATION_Y: self.location_y}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
@dataclass(frozen=True)
|
|
526
|
-
class _GuiConfig:
|
|
527
|
-
otc_icon: str = str(Path(__file__).parents[0] / r"view" / r"helpers" / r"OTC.ico")
|
|
528
|
-
font: str = "Open Sans"
|
|
529
|
-
font_size: int = 12
|
|
530
|
-
window_config: _GuiWindowConfig = _GuiWindowConfig()
|
|
531
|
-
frame_width: int = 80
|
|
532
|
-
col_width: int = 50
|
|
533
|
-
|
|
534
|
-
@staticmethod
|
|
535
|
-
def from_dict(d: dict) -> "_GuiConfig":
|
|
536
|
-
window_config_dict = d.get(WINDOW)
|
|
537
|
-
window_config = (
|
|
538
|
-
_GuiWindowConfig.from_dict(window_config_dict)
|
|
539
|
-
if window_config_dict
|
|
540
|
-
else _GuiConfig.window_config
|
|
541
|
-
)
|
|
542
|
-
|
|
543
|
-
return _GuiConfig(
|
|
544
|
-
font=d.get(FONT, _GuiConfig.font),
|
|
545
|
-
font_size=d.get(FONT_SIZE, _GuiConfig.font_size),
|
|
546
|
-
window_config=window_config,
|
|
547
|
-
frame_width=d.get(FRAME_WIDTH, _GuiConfig.frame_width),
|
|
548
|
-
col_width=d.get(COL_WIDTH, _GuiConfig.col_width),
|
|
549
|
-
)
|
|
550
|
-
|
|
551
|
-
def to_dict(self) -> dict:
|
|
552
|
-
return {
|
|
553
|
-
OTC_ICON: self.otc_icon,
|
|
554
|
-
FONT: self.font,
|
|
555
|
-
FONT_SIZE: self.font_size,
|
|
556
|
-
WINDOW: self.window_config.to_dict(),
|
|
557
|
-
FRAME_WIDTH: self.frame_width,
|
|
558
|
-
COL_WIDTH: self.col_width,
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
@dataclass
|
|
563
|
-
class Config:
|
|
564
|
-
"""Represents the OTVision config file.
|
|
565
|
-
|
|
566
|
-
Provides methods to parse in a custom config file from a dict or a YAML file.
|
|
567
|
-
Updates the default configuration with the custom config.
|
|
568
|
-
"""
|
|
569
|
-
|
|
570
|
-
log: _LogConfig = _LogConfig()
|
|
571
|
-
search_subdirs: bool = True
|
|
572
|
-
default_filetype: _DefaultFiletype = _DefaultFiletype()
|
|
573
|
-
filetypes: _Filetypes = _Filetypes()
|
|
574
|
-
last_paths: _LastPaths = _LastPaths()
|
|
575
|
-
convert: ConvertConfig = ConvertConfig()
|
|
576
|
-
detect: DetectConfig = DetectConfig()
|
|
577
|
-
track: TrackConfig = TrackConfig()
|
|
578
|
-
undistort: _UndistortConfig = _UndistortConfig()
|
|
579
|
-
transform: _TransformConfig = _TransformConfig()
|
|
580
|
-
gui: _GuiConfig = _GuiConfig()
|
|
581
|
-
|
|
582
|
-
@staticmethod
|
|
583
|
-
def from_dict(d: dict) -> "Config":
|
|
584
|
-
"""Builds a OTVision `Config` object with a dictionary.
|
|
585
|
-
|
|
586
|
-
Args:
|
|
587
|
-
d (dict): The dictionary to be parsed.
|
|
588
|
-
|
|
589
|
-
Returns:
|
|
590
|
-
Config: The built `Config` object.
|
|
591
|
-
"""
|
|
592
|
-
log_dict = d.get(LOG)
|
|
593
|
-
default_filtetype_dict = d.get(DEFAULT_FILETYPE)
|
|
594
|
-
convert_dict = d.get(CONVERT)
|
|
595
|
-
detect_dict = d.get(DETECT)
|
|
596
|
-
track_dict = d.get(TRACK)
|
|
597
|
-
undistort_dict = d.get(UNDISTORT)
|
|
598
|
-
transform_dict = d.get(TRANSFORM)
|
|
599
|
-
gui_dict = d.get(GUI)
|
|
600
|
-
|
|
601
|
-
log_config = _LogConfig.from_dict(log_dict) if log_dict else Config.log
|
|
602
|
-
default_filetype = (
|
|
603
|
-
_DefaultFiletype.from_dict(default_filtetype_dict)
|
|
604
|
-
if default_filtetype_dict
|
|
605
|
-
else Config.default_filetype
|
|
606
|
-
)
|
|
607
|
-
convert_config = (
|
|
608
|
-
ConvertConfig.from_dict(convert_dict) if convert_dict else Config.convert
|
|
609
|
-
)
|
|
610
|
-
detect_config = (
|
|
611
|
-
DetectConfig.from_dict(detect_dict) if detect_dict else Config.detect
|
|
612
|
-
)
|
|
613
|
-
track_config = TrackConfig.from_dict(track_dict) if track_dict else Config.track
|
|
614
|
-
undistort_config = (
|
|
615
|
-
_UndistortConfig.from_dict(undistort_dict)
|
|
616
|
-
if undistort_dict
|
|
617
|
-
else Config.undistort
|
|
618
|
-
)
|
|
619
|
-
transform_config = (
|
|
620
|
-
_TransformConfig.from_dict(transform_dict)
|
|
621
|
-
if transform_dict
|
|
622
|
-
else Config.transform
|
|
623
|
-
)
|
|
624
|
-
gui_config = _GuiConfig.from_dict(gui_dict) if gui_dict else Config.gui
|
|
625
|
-
|
|
626
|
-
return Config(
|
|
627
|
-
log=log_config,
|
|
628
|
-
search_subdirs=d.get(SEARCH_SUBDIRS, Config.search_subdirs),
|
|
629
|
-
default_filetype=default_filetype,
|
|
630
|
-
convert=convert_config,
|
|
631
|
-
detect=detect_config,
|
|
632
|
-
track=track_config,
|
|
633
|
-
undistort=undistort_config,
|
|
634
|
-
transform=transform_config,
|
|
635
|
-
gui=gui_config,
|
|
636
|
-
)
|
|
637
|
-
|
|
638
|
-
def to_dict(self) -> dict:
|
|
639
|
-
"""Returns the OTVision config as a dict.
|
|
640
|
-
|
|
641
|
-
Returns:
|
|
642
|
-
dict: The OTVision config.
|
|
643
|
-
"""
|
|
644
|
-
return {
|
|
645
|
-
LOG: self.log.to_dict(),
|
|
646
|
-
SEARCH_SUBDIRS: self.search_subdirs,
|
|
647
|
-
DEFAULT_FILETYPE: self.default_filetype.to_dict(),
|
|
648
|
-
FILETYPES: self.filetypes.to_dict(),
|
|
649
|
-
LAST_PATHS: self.last_paths.to_dict(),
|
|
650
|
-
CONVERT: self.convert.to_dict(),
|
|
651
|
-
DETECT: self.detect.to_dict(),
|
|
652
|
-
TRACK: self.track.to_dict(),
|
|
653
|
-
UNDISTORT: self.undistort.to_dict(),
|
|
654
|
-
TRANSFORM: self.transform.to_dict(),
|
|
655
|
-
GUI: self.gui.to_dict(),
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
@staticmethod
|
|
659
|
-
def from_yaml(yaml_file: Path) -> dict:
|
|
660
|
-
"""Parse OTVision yaml configuration file.
|
|
661
|
-
|
|
662
|
-
Args:
|
|
663
|
-
yaml_file (Path): The yaml config file.
|
|
664
|
-
|
|
665
|
-
Returns:
|
|
666
|
-
dict: The parsed config file as a dict.
|
|
667
|
-
"""
|
|
668
|
-
with open(yaml_file, "r") as file:
|
|
669
|
-
try:
|
|
670
|
-
yaml_config = yaml.safe_load(file)
|
|
671
|
-
except yaml.YAMLError:
|
|
672
|
-
log.exception("Unable to parse user config. Using default config.")
|
|
673
|
-
raise
|
|
674
|
-
config = Config.from_dict(yaml_config)
|
|
675
|
-
|
|
676
|
-
return config.to_dict()
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
class ConfigParser:
|
|
680
|
-
def parse(self, config_file: Path) -> Config:
|
|
681
|
-
"""Parse OTVision yaml configuration file.
|
|
682
|
-
|
|
683
|
-
Args:
|
|
684
|
-
config_file (Path): The yaml config file.
|
|
685
|
-
|
|
686
|
-
Returns:
|
|
687
|
-
Config: The parsed config file.
|
|
688
|
-
"""
|
|
689
|
-
with open(config_file, "r") as file:
|
|
690
|
-
try:
|
|
691
|
-
yaml_config = yaml.safe_load(file)
|
|
692
|
-
except yaml.YAMLError:
|
|
693
|
-
log.exception("Unable to parse user config. Using default config.")
|
|
694
|
-
raise
|
|
695
|
-
return Config.from_dict(yaml_config)
|
|
696
|
-
|
|
697
89
|
|
|
698
90
|
def parse_user_config(yaml_file: Path | str) -> Config:
|
|
699
91
|
"""Parses a custom OTVision user config yaml file.
|
|
@@ -702,7 +94,8 @@ def parse_user_config(yaml_file: Path | str) -> Config:
|
|
|
702
94
|
yaml_file (Path |str): The absolute Path to the config file.
|
|
703
95
|
"""
|
|
704
96
|
user_config_file = Path(yaml_file)
|
|
705
|
-
|
|
97
|
+
deserializer = YamlDeserializer()
|
|
98
|
+
user_config = ConfigParser(deserializer).parse(user_config_file)
|
|
706
99
|
CONFIG.update(user_config.to_dict())
|
|
707
100
|
return user_config
|
|
708
101
|
|