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.
- OTVision/__init__.py +30 -0
- OTVision/application/__init__.py +0 -0
- OTVision/application/configure_logger.py +23 -0
- OTVision/application/detect/__init__.py +0 -0
- OTVision/application/detect/get_detect_cli_args.py +9 -0
- OTVision/application/detect/update_detect_config_with_cli_args.py +95 -0
- OTVision/application/get_config.py +25 -0
- OTVision/config.py +754 -0
- OTVision/convert/__init__.py +0 -0
- OTVision/convert/convert.py +318 -0
- OTVision/dataformat.py +70 -0
- OTVision/detect/__init__.py +0 -0
- OTVision/detect/builder.py +48 -0
- OTVision/detect/cli.py +166 -0
- OTVision/detect/detect.py +296 -0
- OTVision/detect/otdet.py +103 -0
- OTVision/detect/plugin_av/__init__.py +0 -0
- OTVision/detect/plugin_av/rotate_frame.py +37 -0
- OTVision/detect/yolo.py +277 -0
- OTVision/domain/__init__.py +0 -0
- OTVision/domain/cli.py +42 -0
- OTVision/helpers/__init__.py +0 -0
- OTVision/helpers/date.py +26 -0
- OTVision/helpers/files.py +538 -0
- OTVision/helpers/formats.py +139 -0
- OTVision/helpers/log.py +131 -0
- OTVision/helpers/machine.py +71 -0
- OTVision/helpers/video.py +54 -0
- OTVision/track/__init__.py +0 -0
- OTVision/track/iou.py +282 -0
- OTVision/track/iou_util.py +140 -0
- OTVision/track/preprocess.py +451 -0
- OTVision/track/track.py +422 -0
- OTVision/transform/__init__.py +0 -0
- OTVision/transform/get_homography.py +156 -0
- OTVision/transform/reference_points_picker.py +462 -0
- OTVision/transform/transform.py +352 -0
- OTVision/version.py +13 -0
- OTVision/view/__init__.py +0 -0
- OTVision/view/helpers/OTC.ico +0 -0
- OTVision/view/view.py +90 -0
- OTVision/view/view_convert.py +128 -0
- OTVision/view/view_detect.py +146 -0
- OTVision/view/view_helpers.py +417 -0
- OTVision/view/view_track.py +131 -0
- OTVision/view/view_transform.py +140 -0
- otvision-0.5.3.dist-info/METADATA +47 -0
- otvision-0.5.3.dist-info/RECORD +50 -0
- otvision-0.5.3.dist-info/WHEEL +4 -0
- 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
|