OTVision 0.6.4__py3-none-any.whl → 0.6.6__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.
Files changed (54) hide show
  1. OTVision/abstraction/defaults.py +15 -0
  2. OTVision/application/config.py +475 -0
  3. OTVision/application/config_parser.py +280 -0
  4. OTVision/application/configure_logger.py +1 -1
  5. OTVision/application/detect/detected_frame_factory.py +1 -0
  6. OTVision/application/detect/factory.py +1 -1
  7. OTVision/application/detect/update_detect_config_with_cli_args.py +2 -1
  8. OTVision/application/get_config.py +2 -1
  9. OTVision/application/get_current_config.py +1 -1
  10. OTVision/application/track/__init__.py +0 -0
  11. OTVision/application/track/get_track_cli_args.py +20 -0
  12. OTVision/application/track/update_current_track_config.py +42 -0
  13. OTVision/application/track/update_track_config_with_cli_args.py +52 -0
  14. OTVision/application/update_current_config.py +1 -1
  15. OTVision/config.py +61 -668
  16. OTVision/convert/convert.py +3 -3
  17. OTVision/detect/builder.py +27 -20
  18. OTVision/detect/cli.py +3 -3
  19. OTVision/detect/detected_frame_buffer.py +3 -0
  20. OTVision/detect/file_based_detect_builder.py +19 -0
  21. OTVision/detect/otdet.py +54 -1
  22. OTVision/detect/otdet_file_writer.py +4 -3
  23. OTVision/detect/rtsp_based_detect_builder.py +37 -0
  24. OTVision/detect/rtsp_input_source.py +207 -0
  25. OTVision/detect/timestamper.py +2 -1
  26. OTVision/detect/video_input_source.py +9 -5
  27. OTVision/detect/yolo.py +17 -1
  28. OTVision/domain/cli.py +31 -1
  29. OTVision/domain/current_config.py +1 -1
  30. OTVision/domain/frame.py +6 -0
  31. OTVision/domain/object_detection.py +6 -1
  32. OTVision/domain/serialization.py +12 -0
  33. OTVision/domain/time.py +13 -0
  34. OTVision/helpers/files.py +14 -15
  35. OTVision/plugin/__init__.py +0 -0
  36. OTVision/plugin/yaml_serialization.py +20 -0
  37. OTVision/track/builder.py +132 -0
  38. OTVision/track/cli.py +128 -0
  39. OTVision/track/exporter/filebased_exporter.py +2 -1
  40. OTVision/track/id_generator.py +15 -0
  41. OTVision/track/model/track_exporter.py +2 -1
  42. OTVision/track/model/tracking_interfaces.py +6 -6
  43. OTVision/track/parser/chunk_parser_plugins.py +1 -0
  44. OTVision/track/parser/frame_group_parser_plugins.py +35 -5
  45. OTVision/track/track.py +54 -133
  46. OTVision/track/tracker/filebased_tracking.py +8 -7
  47. OTVision/track/tracker/tracker_plugin_iou.py +15 -9
  48. OTVision/transform/transform.py +2 -2
  49. OTVision/version.py +1 -1
  50. otvision-0.6.6.dist-info/METADATA +182 -0
  51. {otvision-0.6.4.dist-info → otvision-0.6.6.dist-info}/RECORD +53 -36
  52. otvision-0.6.4.dist-info/METADATA +0 -49
  53. {otvision-0.6.4.dist-info → otvision-0.6.6.dist-info}/WHEEL +0 -0
  54. {otvision-0.6.4.dist-info → otvision-0.6.6.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 yaml
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
- user_config = ConfigParser().parse(user_config_file)
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