OTVision 0.5.3__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 (50) hide show
  1. OTVision/__init__.py +30 -0
  2. OTVision/application/__init__.py +0 -0
  3. OTVision/application/configure_logger.py +23 -0
  4. OTVision/application/detect/__init__.py +0 -0
  5. OTVision/application/detect/get_detect_cli_args.py +9 -0
  6. OTVision/application/detect/update_detect_config_with_cli_args.py +95 -0
  7. OTVision/application/get_config.py +25 -0
  8. OTVision/config.py +754 -0
  9. OTVision/convert/__init__.py +0 -0
  10. OTVision/convert/convert.py +318 -0
  11. OTVision/dataformat.py +70 -0
  12. OTVision/detect/__init__.py +0 -0
  13. OTVision/detect/builder.py +48 -0
  14. OTVision/detect/cli.py +166 -0
  15. OTVision/detect/detect.py +296 -0
  16. OTVision/detect/otdet.py +103 -0
  17. OTVision/detect/plugin_av/__init__.py +0 -0
  18. OTVision/detect/plugin_av/rotate_frame.py +37 -0
  19. OTVision/detect/yolo.py +277 -0
  20. OTVision/domain/__init__.py +0 -0
  21. OTVision/domain/cli.py +42 -0
  22. OTVision/helpers/__init__.py +0 -0
  23. OTVision/helpers/date.py +26 -0
  24. OTVision/helpers/files.py +538 -0
  25. OTVision/helpers/formats.py +139 -0
  26. OTVision/helpers/log.py +131 -0
  27. OTVision/helpers/machine.py +71 -0
  28. OTVision/helpers/video.py +54 -0
  29. OTVision/track/__init__.py +0 -0
  30. OTVision/track/iou.py +282 -0
  31. OTVision/track/iou_util.py +140 -0
  32. OTVision/track/preprocess.py +451 -0
  33. OTVision/track/track.py +422 -0
  34. OTVision/transform/__init__.py +0 -0
  35. OTVision/transform/get_homography.py +156 -0
  36. OTVision/transform/reference_points_picker.py +462 -0
  37. OTVision/transform/transform.py +352 -0
  38. OTVision/version.py +13 -0
  39. OTVision/view/__init__.py +0 -0
  40. OTVision/view/helpers/OTC.ico +0 -0
  41. OTVision/view/view.py +90 -0
  42. OTVision/view/view_convert.py +128 -0
  43. OTVision/view/view_detect.py +146 -0
  44. OTVision/view/view_helpers.py +417 -0
  45. OTVision/view/view_track.py +131 -0
  46. OTVision/view/view_transform.py +140 -0
  47. otvision-0.5.3.dist-info/METADATA +47 -0
  48. otvision-0.5.3.dist-info/RECORD +50 -0
  49. otvision-0.5.3.dist-info/WHEEL +4 -0
  50. otvision-0.5.3.dist-info/licenses/LICENSE +674 -0
File without changes
@@ -0,0 +1,318 @@
1
+ """
2
+ OTVision main module for converting videos to other formats and frame rates.
3
+ """
4
+
5
+ # Copyright (C) 2022 OpenTrafficCam Contributors
6
+ # <https://github.com/OpenTrafficCam
7
+ # <team@opentrafficcam.org>
8
+ #
9
+ # This program is free software: you can redistribute it and/or modify
10
+ # it under the terms of the GNU General Public License as published by
11
+ # the Free Software Foundation, either version 3 of the License, or
12
+ # (at your option) any later version.
13
+ #
14
+ # This program is distributed in the hope that it will be useful,
15
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ # GNU General Public License for more details.
18
+ #
19
+ # You should have received a copy of the GNU General Public License
20
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
21
+
22
+
23
+ import logging
24
+ import subprocess
25
+ from pathlib import Path
26
+ from typing import Optional
27
+
28
+ from tqdm import tqdm
29
+
30
+ from OTVision.config import (
31
+ CONFIG,
32
+ CONVERT,
33
+ DELETE_INPUT,
34
+ FILETYPES,
35
+ FPS_FROM_FILENAME,
36
+ INPUT_FPS,
37
+ OUTPUT_FILETYPE,
38
+ OVERWRITE,
39
+ ROTATION,
40
+ VID,
41
+ VID_ROTATABLE,
42
+ )
43
+ from OTVision.helpers.files import get_files
44
+ from OTVision.helpers.formats import _get_fps_from_filename
45
+ from OTVision.helpers.log import LOGGER_NAME
46
+
47
+ log = logging.getLogger(LOGGER_NAME)
48
+
49
+ OUTPUT_FPS: Optional[float] = None
50
+ CONVERTABLE_FILETYPES = list(
51
+ set(CONFIG[FILETYPES][VID]).union([".h264"]).difference([".mp4"])
52
+ )
53
+
54
+
55
+ def main(
56
+ paths: list[Path],
57
+ output_filetype: str = CONFIG[CONVERT][OUTPUT_FILETYPE],
58
+ input_fps: float = CONFIG[CONVERT][INPUT_FPS],
59
+ fps_from_filename: bool = CONFIG[CONVERT][FPS_FROM_FILENAME],
60
+ rotation: int = CONFIG[CONVERT][ROTATION],
61
+ overwrite: bool = CONFIG[CONVERT][OVERWRITE],
62
+ delete_input: bool = CONFIG[CONVERT][DELETE_INPUT],
63
+ ) -> None:
64
+ """Converts multiple h264-based videos into other formats.
65
+
66
+ Args:
67
+ paths (list[Path]): List of paths to .h264 files
68
+ (or other video files)
69
+ output_filetype (str, optional): Extension and format of video file created.
70
+ Defaults to CONFIG["CONVERT"]["OUTPUT_FILETYPE"].
71
+ input_fps (float, optional): Frame rate of input h264.
72
+ If fps_from_filename is set to True, input_fps will be ignored.
73
+ Defaults to CONFIG["CONVERT"]["INPUT_FPS"].
74
+ fps_from_filename (bool, optional): Whether to parse frame rate
75
+ from file name. Defaults to CONFIG["CONVERT"]["FPS_FROM_FILENAME"].
76
+ rotation (int, optional): Add rotation information to video metadata.
77
+ Defaults to CONFIG["CONVERT"]["ROTATION"].
78
+ overwrite (bool, optional): Whether to overwrite existing video files.
79
+ Defaults to CONFIG["CONVERT"]["OVERWRITE"].
80
+ delete_input (bool, optional): Whether to delete the input h264.
81
+ Defaults to CONFIG["CONVERT"]["DELETE_INPUT"].
82
+ """
83
+ files = get_files(paths, CONVERTABLE_FILETYPES)
84
+
85
+ start_msg = f"Start conversion of {len(files)} files"
86
+ log.info(start_msg)
87
+ print(start_msg)
88
+
89
+ if not files:
90
+ log.warning("No files found to convert!")
91
+ return
92
+
93
+ check_ffmpeg()
94
+
95
+ for _file in tqdm(files, desc="Converted files", unit="files"):
96
+ log.info(f"Convert {_file} to {output_filetype}")
97
+ convert(
98
+ _file,
99
+ output_filetype,
100
+ input_fps,
101
+ fps_from_filename,
102
+ rotation,
103
+ overwrite,
104
+ delete_input,
105
+ )
106
+ log.info(f"Successfully converted {_file} to {output_filetype}")
107
+
108
+ finished_msg = "Finished conversion"
109
+ log.info(finished_msg)
110
+ print(finished_msg)
111
+
112
+
113
+ def convert(
114
+ input_video_file: Path,
115
+ output_filetype: str = CONFIG[CONVERT][OUTPUT_FILETYPE],
116
+ input_fps: float = CONFIG[CONVERT][INPUT_FPS],
117
+ fps_from_filename: bool = CONFIG[CONVERT][FPS_FROM_FILENAME],
118
+ rotation: int = CONFIG[CONVERT][ROTATION],
119
+ overwrite: bool = CONFIG[CONVERT][OVERWRITE],
120
+ delete_input: bool = CONFIG[CONVERT][DELETE_INPUT],
121
+ ) -> None:
122
+ """Converts h264-based videos into other formats and/or other frame rates.
123
+ Also input frame rates can be given.
124
+ If input video file is raw h264 and no input frame rate is given convert
125
+ tries to parse frame rate from filename, otherwise sets default frame rate.
126
+
127
+ Currently only works for windows as ffmpeg.exe is utilized.
128
+
129
+ Args:
130
+ input_video_file (Path): Path to h264 video file (or other format).
131
+ output_filetype (str, optional): Type of video file created.
132
+ Defaults to CONFIG["CONVERT"]["OUTPUT_FILETYPE"].
133
+ input_fps (float, optional): Frame rate of input h264.
134
+ If fps_from_filename is set to True, input_fps will be ignored.
135
+ Defaults to CONFIG["CONVERT"]["INPUT_FPS"].
136
+ fps_from_filename (bool, optional): Whether to parse frame rate
137
+ from file name. Defaults to CONFIG["CONVERT"]["FPS_FROM_FILENAME"].
138
+ rotation (int, optional): Add rotation information to video metadata.
139
+ Defaults to CONFIG["CONVERT"]["ROTATION"].
140
+ overwrite (bool, optional): Whether to overwrite existing video files.
141
+ Defaults to CONFIG["CONVERT"]["OVERWRITE"].
142
+ delete_input (bool, optional): Whether to delete the input h264.
143
+ Defaults to CONFIG["CONVERT"]["DELETE_INPUT"].
144
+
145
+ Raises:
146
+ TypeError: If output video filetype is not supported.
147
+ TypeError: If input video filetype is not supported.
148
+
149
+ Returns:
150
+ None: If not on a windows machine.
151
+ None: If output video file already exists and overwrite is not enabled.
152
+ """
153
+
154
+ _check_types(
155
+ output_filetype=output_filetype,
156
+ input_fps=input_fps,
157
+ fps_from_filename=fps_from_filename,
158
+ rotation=rotation,
159
+ overwrite=overwrite,
160
+ delete_input=delete_input,
161
+ )
162
+
163
+ output_fps = OUTPUT_FPS
164
+ if output_fps is not None:
165
+ delete_input = False # Never delete input if re-encoding file.
166
+
167
+ input_filename = input_video_file.stem
168
+ input_filetype = input_video_file.suffix
169
+ output_video_file = input_video_file.with_suffix(output_filetype)
170
+
171
+ if not overwrite and output_video_file.is_file():
172
+ log.warning(
173
+ f"{output_video_file} already exists. To overwrite, set overwrite to True"
174
+ )
175
+ return None
176
+ vid_filetypes = CONFIG["FILETYPES"]["VID"]
177
+
178
+ if input_filetype in CONVERTABLE_FILETYPES and output_filetype in vid_filetypes:
179
+ if fps_from_filename:
180
+ input_fps = _get_fps_from_filename(input_filename)
181
+
182
+ ffmpeg_cmd = _get_ffmpeg_command(
183
+ input_video_file, input_fps, rotation, output_fps, output_video_file
184
+ )
185
+
186
+ subprocess.run(
187
+ ffmpeg_cmd,
188
+ check=True,
189
+ stdout=subprocess.DEVNULL,
190
+ stderr=subprocess.STDOUT,
191
+ )
192
+ log.info(f"{output_video_file} created an input fps of {input_fps}")
193
+
194
+ if delete_input:
195
+ _delete_input_video_file(input_video_file, output_video_file)
196
+ else:
197
+ raise TypeError(f"Output video filetype {output_filetype} is not supported")
198
+
199
+
200
+ def _get_ffmpeg_command(
201
+ input_video_file: Path,
202
+ input_fps: float,
203
+ rotation: int,
204
+ output_fps: Optional[float],
205
+ output_video_file: Path,
206
+ filter_cmds: Optional[list[str]] = None,
207
+ ) -> list[str]:
208
+ """
209
+ Generate an ffmpeg command using the given options.
210
+
211
+ Args:
212
+ input_video_file (Path): Path to h264 video file (or other format).
213
+ input_fps (float, optional): Frame rate of input h264.
214
+ If fps_from_filename is set to True, input_fps will be ignored.
215
+ rotation (int, optional): Add rotation information to video metadata.
216
+ output_fps (Optional[float]): Frame rate of the output file.
217
+ output_video_file (Path): Path to the output video file.
218
+ filter_cmds (Optional[list[str]]): Filter to use with ffmpeg. Filters (maybe
219
+ necessary for special cases, insert if needed)
220
+
221
+ Returns:
222
+
223
+ """
224
+ # ? Change -framerate to -r?
225
+ input_fps_cmds = ["-r", str(input_fps)]
226
+
227
+ if rotation == 0:
228
+ rotation_cmds: list[str] = []
229
+ else:
230
+ if output_video_file.suffix not in CONFIG[FILETYPES][VID_ROTATABLE]:
231
+ raise TypeError(
232
+ f"{output_video_file.suffix} files are not rotatable."
233
+ f"Use {CONFIG[FILETYPES][VID_ROTATABLE]} or rotation=0 instead."
234
+ )
235
+ rotation_cmds = ["-display_rotation", str(rotation)]
236
+
237
+ if output_fps is not None:
238
+ output_fps_cmds: list[str] = ["-r", str(output_fps)]
239
+ copy_cmds: list[str] = []
240
+ else:
241
+ output_fps_cmds = []
242
+ copy_cmds = ["-vcodec", "copy"] # No re-encoding, only demuxing
243
+
244
+ input_file_cmds = ["-i", str(input_video_file)]
245
+
246
+ filter_cmds = filter_cmds if filter_cmds else []
247
+
248
+ output_file_cmds = ["-y", str(output_video_file)]
249
+
250
+ ffmpeg_cmd = (
251
+ ["ffmpeg"]
252
+ + rotation_cmds
253
+ + input_fps_cmds
254
+ + input_file_cmds
255
+ + filter_cmds
256
+ + output_fps_cmds
257
+ + copy_cmds
258
+ + output_file_cmds
259
+ )
260
+ log.debug(f"ffmpeg command: {ffmpeg_cmd}")
261
+ return ffmpeg_cmd
262
+
263
+
264
+ def _delete_input_video_file(input_video_file: Path, output_video_file: Path) -> None:
265
+ in_size = input_video_file.stat().st_size
266
+ out_size = output_video_file.stat().st_size
267
+ if in_size <= out_size:
268
+ log.debug(f"Input file ({in_size}) <= output file ({out_size}).")
269
+ input_video_file.unlink()
270
+ log.info(f"Input file {input_video_file} deleted.")
271
+
272
+
273
+ def check_ffmpeg() -> None:
274
+ """Checks, if ffmpeg is available"""
275
+
276
+ exception_msg = "ffmpeg can not be called, check it's installed correctly"
277
+
278
+ try:
279
+ subprocess.run(
280
+ ["ffmpeg", "-version"],
281
+ check=True,
282
+ stdout=subprocess.DEVNULL,
283
+ stderr=subprocess.STDOUT,
284
+ )
285
+ log.info("ffmpeg was found")
286
+ except FileNotFoundError:
287
+ log.exception(exception_msg)
288
+ raise
289
+ except subprocess.CalledProcessError:
290
+ log.exception(exception_msg)
291
+ raise
292
+ except Exception:
293
+ log.exception("")
294
+ raise
295
+
296
+
297
+ def _check_types(
298
+ output_filetype: str,
299
+ input_fps: float,
300
+ fps_from_filename: bool,
301
+ rotation: int,
302
+ overwrite: bool,
303
+ delete_input: bool,
304
+ ) -> None:
305
+ """Raise ValueErrors if wrong types"""
306
+
307
+ if not isinstance(output_filetype, str):
308
+ raise ValueError("output_filetype has to be str")
309
+ if not isinstance(input_fps, (int, float)):
310
+ raise ValueError("input_fps has to be int or float")
311
+ if not isinstance(fps_from_filename, bool):
312
+ raise ValueError("fps_from_filename has to be bool")
313
+ if not isinstance(rotation, int):
314
+ raise ValueError("rotation has to be int")
315
+ if not isinstance(overwrite, bool):
316
+ raise ValueError("overwrite has to be bool")
317
+ if not isinstance(delete_input, bool):
318
+ raise ValueError("delete_input has to be bool")
OTVision/dataformat.py ADDED
@@ -0,0 +1,70 @@
1
+ METADATA: str = "metadata"
2
+ OTDET_VERSION: str = "otdet_version"
3
+ OTTRACK_VERSION: str = "ottrk_version"
4
+ OTVISION_VERSION: str = "otvision_version"
5
+ VIDEO: str = "video"
6
+ DETECTION: str = "detection"
7
+ TRACKING: str = "tracking"
8
+ FILENAME: str = "filename"
9
+ FILETYPE: str = "filetype"
10
+ WIDTH: str = "width"
11
+ HEIGHT: str = "height"
12
+ ACTUAL_FPS: str = "actual_fps"
13
+ RECORDED_FPS: str = "recorded_fps"
14
+ FRAMES: str = "frames"
15
+ EXPECTED_DURATION: str = "expected_duration"
16
+ RECORDED_START_DATE: str = "recorded_start_date"
17
+ LENGTH: str = "length"
18
+ NUMBER_OF_FRAMES: str = "number_of_frames"
19
+
20
+ DATE_FORMAT: str = "%Y-%m-%d %H:%M:%S.%f"
21
+ INPUT_FILE_PATH: str = "input_file_path"
22
+ DATA: str = "data"
23
+ DETECTIONS: str = "detections"
24
+ CLASS: str = "class"
25
+ FRAME: str = "frame"
26
+ OCCURRENCE: str = "occurrence"
27
+ LABEL: str = "label"
28
+ CONFIDENCE: str = "confidence"
29
+ X: str = "x"
30
+ Y: str = "y"
31
+ W: str = "w"
32
+ H: str = "h"
33
+
34
+ TRACK_ID: str = "track-id"
35
+ INTERPOLATED_DETECTION: str = "interpolated-detection"
36
+
37
+ # Detector config
38
+ MODEL: str = "model"
39
+ CHUNKSIZE: str = "chunksize"
40
+ NORMALIZED_BBOX: str = "normalized_bbox"
41
+ # Detektor model config
42
+ NAME: str = "name"
43
+ WEIGHTS: str = "weights"
44
+ IOU_THRESHOLD: str = "iou_threshold"
45
+ IMAGE_SIZE: str = "image_size"
46
+ MAX_CONFIDENCE: str = "max_confidence"
47
+ HALF_PRECISION: str = "half_precision"
48
+ CLASSES: str = "classes"
49
+
50
+ # Tracker config
51
+ TRACKING_RUN_ID: str = "tracking_run_id"
52
+ FRAME_GROUP: str = "frame_group"
53
+ FIRST_TRACKED_VIDEO_START: str = "first_tracked_video_start"
54
+ LAST_TRACKED_VIDEO_END: str = "last_tracked_video_end"
55
+ TRACKER: str = "tracker"
56
+ SIGMA_L: str = "sigma_l"
57
+ SIGMA_H: str = "sigma_h"
58
+ SIGMA_IOU: str = "sigma_iou"
59
+ T_MIN: str = "t_min"
60
+ T_MISS_MAX: str = "t_miss_max"
61
+
62
+ # iou
63
+ BBOXES: str = "bboxes"
64
+ CENTER: str = "center"
65
+ AGE: str = "age"
66
+ MAX_CLASS: str = "max_class"
67
+ MAX_CONF: str = "max_conf"
68
+ FIRST: str = "first"
69
+ FINISHED: str = "finished"
70
+ START_FRAME: str = "start_frame"
File without changes
@@ -0,0 +1,48 @@
1
+ from argparse import ArgumentParser
2
+ from functools import cached_property
3
+
4
+ from OTVision.application.configure_logger import ConfigureLogger
5
+ from OTVision.application.detect.get_detect_cli_args import GetDetectCliArgs
6
+ from OTVision.application.detect.update_detect_config_with_cli_args import (
7
+ UpdateDetectConfigWithCliArgs,
8
+ )
9
+ from OTVision.application.get_config import GetConfig
10
+ from OTVision.config import ConfigParser
11
+ from OTVision.detect.cli import ArgparseDetectCliParser
12
+ from OTVision.detect.otdet import OtdetBuilder
13
+ from OTVision.domain.cli import DetectCliParser
14
+
15
+
16
+ class DetectBuilder:
17
+ @cached_property
18
+ def get_config(self) -> GetConfig:
19
+ return GetConfig(self.config_parser)
20
+
21
+ @cached_property
22
+ def config_parser(self) -> ConfigParser:
23
+ return ConfigParser()
24
+
25
+ @cached_property
26
+ def get_detect_cli_args(self) -> GetDetectCliArgs:
27
+ return GetDetectCliArgs(self.detect_cli_parser)
28
+
29
+ @cached_property
30
+ def detect_cli_parser(self) -> DetectCliParser:
31
+ return ArgparseDetectCliParser(
32
+ parser=ArgumentParser("Detect objects in videos or images"), argv=self.argv
33
+ )
34
+
35
+ @cached_property
36
+ def update_detect_config_with_ci_args(self) -> UpdateDetectConfigWithCliArgs:
37
+ return UpdateDetectConfigWithCliArgs(self.get_detect_cli_args)
38
+
39
+ @cached_property
40
+ def configure_logger(self) -> ConfigureLogger:
41
+ return ConfigureLogger()
42
+
43
+ @cached_property
44
+ def otdet_builder(self) -> OtdetBuilder:
45
+ return OtdetBuilder()
46
+
47
+ def __init__(self, argv: list[str] | None = None) -> None:
48
+ self.argv = argv
OTVision/detect/cli.py ADDED
@@ -0,0 +1,166 @@
1
+ from argparse import ArgumentParser, BooleanOptionalAction, Namespace
2
+ from datetime import timedelta
3
+ from pathlib import Path
4
+
5
+ from OTVision.domain.cli import CliParseError, DetectCliArgs, DetectCliParser
6
+ from OTVision.helpers.files import check_if_all_paths_exist
7
+ from OTVision.helpers.log import DEFAULT_LOG_FILE, VALID_LOG_LEVELS
8
+
9
+
10
+ class ArgparseDetectCliParser(DetectCliParser):
11
+ def __init__(
12
+ self,
13
+ parser: ArgumentParser,
14
+ argv: list[str] | None = None,
15
+ ) -> None:
16
+ self._parser = parser
17
+ self._argv = argv
18
+ self.__setup()
19
+
20
+ def __setup(self) -> None:
21
+ self._parser.add_argument(
22
+ "-p",
23
+ "--paths",
24
+ nargs="+",
25
+ type=str,
26
+ help=(
27
+ "Path/list of paths to image or video or folder "
28
+ "containing videos/images"
29
+ ),
30
+ required=False,
31
+ )
32
+ self._parser.add_argument(
33
+ "-c",
34
+ "--config",
35
+ type=str,
36
+ help="Path to custom user configuration yaml file.",
37
+ required=False,
38
+ )
39
+ self._parser.add_argument(
40
+ "-w",
41
+ "--weights",
42
+ type=str,
43
+ help="Name of weights from PyTorch hub or Path to weights file",
44
+ required=False,
45
+ )
46
+ self._parser.add_argument(
47
+ "--conf",
48
+ type=float,
49
+ help="The YOLOv5 models confidence threshold.",
50
+ required=False,
51
+ )
52
+ self._parser.add_argument(
53
+ "--iou",
54
+ type=float,
55
+ help="The YOLOv5 models IOU threshold.",
56
+ required=False,
57
+ )
58
+ self._parser.add_argument(
59
+ "--imagesize",
60
+ type=int,
61
+ help="YOLOv5 image size.",
62
+ required=False,
63
+ )
64
+ self._parser.add_argument(
65
+ "--half",
66
+ action=BooleanOptionalAction,
67
+ help="Use half precision for detection.",
68
+ )
69
+ self._parser.add_argument(
70
+ "--expected_duration",
71
+ type=int,
72
+ help="Expected duration of a single video in seconds.",
73
+ required=False,
74
+ )
75
+ self._parser.add_argument(
76
+ "-o",
77
+ "--overwrite",
78
+ action=BooleanOptionalAction,
79
+ help="Overwrite existing output files",
80
+ )
81
+ self._parser.add_argument(
82
+ "--log_level_console",
83
+ type=str,
84
+ choices=VALID_LOG_LEVELS,
85
+ help="Log level for logging to the console",
86
+ required=False,
87
+ )
88
+ self._parser.add_argument(
89
+ "--log_level_file",
90
+ type=str,
91
+ choices=VALID_LOG_LEVELS,
92
+ help="Log level for logging to a log file",
93
+ required=False,
94
+ )
95
+ self._parser.add_argument(
96
+ "--logfile",
97
+ default=str(DEFAULT_LOG_FILE),
98
+ type=str,
99
+ help="Specify log file directory.",
100
+ required=False,
101
+ )
102
+ self._parser.add_argument(
103
+ "--logfile_overwrite",
104
+ action="store_true",
105
+ help="Overwrite log file if it already exists.",
106
+ required=False,
107
+ )
108
+ self._parser.add_argument(
109
+ "--detect_start",
110
+ default=None,
111
+ type=int,
112
+ help="Specify start of detection in seconds.",
113
+ required=False,
114
+ )
115
+ self._parser.add_argument(
116
+ "--detect_end",
117
+ default=None,
118
+ type=int,
119
+ help="Specify end of detection in seconds.",
120
+ required=False,
121
+ )
122
+
123
+ def parse(self) -> DetectCliArgs:
124
+ args = self._parser.parse_args(self._argv)
125
+ self.__assert_cli_args_valid(args)
126
+
127
+ return DetectCliArgs(
128
+ paths=self._parse_files(args.paths),
129
+ config_file=args.config,
130
+ weights=args.weights,
131
+ conf=float(args.conf) if args.conf is not None else None,
132
+ iou=float(args.iou) if args.iou is not None else None,
133
+ imagesize=int(args.imagesize) if args.imagesize is not None else None,
134
+ expected_duration=(
135
+ timedelta(seconds=args.expected_duration)
136
+ if args.expected_duration is not None
137
+ else None
138
+ ),
139
+ half=bool(args.half) if args.half else None,
140
+ overwrite=args.overwrite,
141
+ detect_start=(
142
+ int(args.detect_start) if args.detect_start is not None else None
143
+ ),
144
+ detect_end=int(args.detect_end) if args.detect_end is not None else None,
145
+ logfile=Path(args.logfile),
146
+ log_level_console=args.log_level_console,
147
+ log_level_file=args.log_level_file,
148
+ logfile_overwrite=args.logfile_overwrite,
149
+ )
150
+
151
+ def __assert_cli_args_valid(self, args: Namespace) -> None:
152
+ if args.paths is None and args.config is None:
153
+ raise CliParseError(
154
+ (
155
+ "No paths have been passed as command line args."
156
+ "No paths have been defined in the user config."
157
+ )
158
+ )
159
+
160
+ def _parse_files(self, files: list[str] | None) -> list[Path] | None:
161
+ if files is None:
162
+ return None
163
+
164
+ result = [Path(file).expanduser() for file in files]
165
+ check_if_all_paths_exist(result)
166
+ return result