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
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OTVision main module for transforming tracks from pixel to world coordinates
|
|
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
|
+
from copy import deepcopy
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Union
|
|
27
|
+
|
|
28
|
+
import cv2
|
|
29
|
+
import geopandas as gpd
|
|
30
|
+
import numpy as np
|
|
31
|
+
import pandas as pd
|
|
32
|
+
from tqdm import tqdm
|
|
33
|
+
|
|
34
|
+
from OTVision.config import (
|
|
35
|
+
CONFIG,
|
|
36
|
+
DEFAULT_FILETYPE,
|
|
37
|
+
FILETYPES,
|
|
38
|
+
OVERWRITE,
|
|
39
|
+
REFPTS,
|
|
40
|
+
TRACK,
|
|
41
|
+
TRANSFORM,
|
|
42
|
+
)
|
|
43
|
+
from OTVision.helpers.files import (
|
|
44
|
+
get_files,
|
|
45
|
+
get_metadata,
|
|
46
|
+
read_json,
|
|
47
|
+
replace_filetype,
|
|
48
|
+
write_json,
|
|
49
|
+
)
|
|
50
|
+
from OTVision.helpers.formats import _get_epsg_from_utm_zone, _ottrk_detections_to_df
|
|
51
|
+
from OTVision.helpers.log import LOGGER_NAME
|
|
52
|
+
|
|
53
|
+
from .get_homography import get_homography
|
|
54
|
+
|
|
55
|
+
log = logging.getLogger(LOGGER_NAME)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def main(
|
|
59
|
+
paths: list[Path],
|
|
60
|
+
refpts_file: Union[Path, None] = None,
|
|
61
|
+
overwrite: bool = CONFIG[TRANSFORM][OVERWRITE],
|
|
62
|
+
) -> None: # sourcery skip: merge-dict-assign
|
|
63
|
+
"""Transform tracks files containing trajectories in pixel coordinates to .gpkg
|
|
64
|
+
files with trajectories in utm coordinates using either one single refpts file for
|
|
65
|
+
all tracks files containing corresponding reference points in both pixel and utm
|
|
66
|
+
coordinates or using specific refpts files for each tracks file
|
|
67
|
+
(path must be the same except for the extension).
|
|
68
|
+
Info: refpts file can be created using OTVision.transform.reference_points_picker
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
paths (list[Path]): List of paths to tracks files.
|
|
72
|
+
refpts_file (Path, optional): Path to reference points file.
|
|
73
|
+
If given, this file will be used for transformation of all tracks files.
|
|
74
|
+
If not given, for every tracks file a refpts file with same stem is needed.
|
|
75
|
+
Defaults to None.
|
|
76
|
+
overwrite (bool, optional): Whether or not to overwrite existing tracks files in
|
|
77
|
+
world coordinates.
|
|
78
|
+
Defaults to CONFIG["TRANSFORM"]["OVERWRITE"].
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
track_filetype = CONFIG[FILETYPES][TRACK]
|
|
82
|
+
|
|
83
|
+
if refpts_file:
|
|
84
|
+
log.info(f"Reading global reference points file at {refpts_file}")
|
|
85
|
+
|
|
86
|
+
refpts_filetype = CONFIG[FILETYPES][REFPTS]
|
|
87
|
+
|
|
88
|
+
if not refpts_file.exists():
|
|
89
|
+
raise FileNotFoundError(
|
|
90
|
+
f"No reference points file with filetype: '{refpts_filetype}' found!"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
refpts = read_refpts(reftpts_file=refpts_file)
|
|
94
|
+
(
|
|
95
|
+
homography,
|
|
96
|
+
refpts_utm_upshift_predecimal,
|
|
97
|
+
upshift_utm,
|
|
98
|
+
utm_zone,
|
|
99
|
+
hemisphere,
|
|
100
|
+
homography_eval_dict,
|
|
101
|
+
) = get_homography(refpts=refpts)
|
|
102
|
+
|
|
103
|
+
log.info("Successfully read reference points file")
|
|
104
|
+
|
|
105
|
+
tracks_files = get_files(paths=paths, filetypes=CONFIG[FILETYPES][TRACK])
|
|
106
|
+
|
|
107
|
+
start_msg = f"Start transformation of {len(tracks_files)} video files"
|
|
108
|
+
log.info(start_msg)
|
|
109
|
+
print(start_msg)
|
|
110
|
+
|
|
111
|
+
if not tracks_files:
|
|
112
|
+
raise FileNotFoundError(
|
|
113
|
+
f"No files of type '{track_filetype}' found to transform!"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
for tracks_file in tqdm(
|
|
117
|
+
tracks_files, desc="Transformed track files", unit=" files"
|
|
118
|
+
):
|
|
119
|
+
gpkg_file = tracks_file.with_suffix(".gpkg")
|
|
120
|
+
|
|
121
|
+
if not overwrite and gpkg_file.is_file():
|
|
122
|
+
log.warning(
|
|
123
|
+
f"{gpkg_file} already exists. To overwrite, set overwrite to True"
|
|
124
|
+
)
|
|
125
|
+
continue
|
|
126
|
+
|
|
127
|
+
log.info(f"Transform {tracks_file}")
|
|
128
|
+
|
|
129
|
+
# Try reading refpts and getting homography if not done above
|
|
130
|
+
if not refpts_file:
|
|
131
|
+
associated_refpts_file = replace_filetype(
|
|
132
|
+
files=[tracks_file], new_filetype=CONFIG[DEFAULT_FILETYPE][REFPTS]
|
|
133
|
+
)[0]
|
|
134
|
+
if not associated_refpts_file.exists():
|
|
135
|
+
raise FileNotFoundError(
|
|
136
|
+
f"No reference points file found for tracks file: {tracks_file}!"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
log.info(f"Found associated refpts file {associated_refpts_file}")
|
|
140
|
+
refpts = read_refpts(reftpts_file=associated_refpts_file)
|
|
141
|
+
log.info("Read read associated refpts file")
|
|
142
|
+
(
|
|
143
|
+
homography,
|
|
144
|
+
refpts_utm_upshift_predecimal,
|
|
145
|
+
upshift_utm,
|
|
146
|
+
utm_zone,
|
|
147
|
+
hemisphere,
|
|
148
|
+
homography_eval_dict,
|
|
149
|
+
) = get_homography(refpts=refpts)
|
|
150
|
+
|
|
151
|
+
log.debug("Homography matrix created")
|
|
152
|
+
|
|
153
|
+
# Read tracks
|
|
154
|
+
tracks_px_df, metadata_dict = read_tracks(tracks_file)
|
|
155
|
+
log.debug("Tracks read")
|
|
156
|
+
|
|
157
|
+
# Actual transformation
|
|
158
|
+
tracks_utm_df = transform(
|
|
159
|
+
tracks_px=tracks_px_df,
|
|
160
|
+
homography=homography,
|
|
161
|
+
refpts_utm_upshifted_predecimal_pt1_1row=refpts_utm_upshift_predecimal,
|
|
162
|
+
upshift_utm=upshift_utm,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
log.debug("Tracks transformed")
|
|
166
|
+
|
|
167
|
+
# Add crs information tp metadata dict
|
|
168
|
+
# TODO: Declare constant for the dictionary keys
|
|
169
|
+
metadata_dict["transformer"] = {}
|
|
170
|
+
metadata_dict["transformer"]["utm"] = True
|
|
171
|
+
metadata_dict["transformer"]["utm_zone"] = utm_zone
|
|
172
|
+
metadata_dict["transformer"]["hemisphere"] = hemisphere
|
|
173
|
+
metadata_dict["transformer"]["epsg"] = _get_epsg_from_utm_zone(
|
|
174
|
+
utm_zone=utm_zone, hemisphere=hemisphere
|
|
175
|
+
)
|
|
176
|
+
metadata_dict["transformer"]["accuracy"] = homography_eval_dict
|
|
177
|
+
|
|
178
|
+
log.debug(f"metadata: {metadata_dict}")
|
|
179
|
+
|
|
180
|
+
# Write tracks
|
|
181
|
+
write_tracks(
|
|
182
|
+
tracks_utm_df=tracks_utm_df,
|
|
183
|
+
metadata_dict=metadata_dict,
|
|
184
|
+
utm_zone=utm_zone,
|
|
185
|
+
hemisphere=hemisphere,
|
|
186
|
+
tracks_file=tracks_file,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
log.info(f"Successfully transformed and wrote {tracks_file}")
|
|
190
|
+
|
|
191
|
+
finished_msg = "Finished transformation"
|
|
192
|
+
log.info(finished_msg)
|
|
193
|
+
print(finished_msg)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# TODO: Type hint nested dict during refactoring
|
|
197
|
+
def read_tracks(tracks_file: Path) -> tuple[pd.DataFrame, dict]:
|
|
198
|
+
"""Reads .ottrk file, returns pandas DataFrame of trajectories
|
|
199
|
+
in pixel coordinates and dict of metadata
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
tracks_file (str or Pathlib.Path): Path to .ottrk file
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
pandas.DataFrame: DataFrame of trajectories in pixel coordinates
|
|
206
|
+
dict: Dict of metadata
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
# Read dicts and turn tracks into DataFrame
|
|
210
|
+
tracks_dict = read_json(tracks_file, filetype=tracks_file.suffix)
|
|
211
|
+
metadata_dict = get_metadata(tracks_dict)
|
|
212
|
+
tracks_df = _ottrk_detections_to_df(tracks_dict["data"]["detections"])
|
|
213
|
+
|
|
214
|
+
return tracks_df, metadata_dict
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def read_refpts(
|
|
218
|
+
reftpts_file: Path,
|
|
219
|
+
) -> dict: # TODO: Type hint nested dict during refactoring
|
|
220
|
+
"""Reads .otrfpts file, returns dict of matching reference points
|
|
221
|
+
in both pixel and utm coordinates
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
reftpts_file (str or pathlib.Path): Path to .rfpts file
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
dict: Matching reference points in both pixel and utm coordinates
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
return read_json(reftpts_file, filetype=reftpts_file.suffix, decompress=False)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def transform(
|
|
234
|
+
tracks_px: pd.DataFrame,
|
|
235
|
+
homography: np.ndarray,
|
|
236
|
+
refpts_utm_upshifted_predecimal_pt1_1row: np.ndarray,
|
|
237
|
+
upshift_utm: np.ndarray,
|
|
238
|
+
x_col: str = "x",
|
|
239
|
+
y_col: str = "y",
|
|
240
|
+
lon_col: str = "lon_utm",
|
|
241
|
+
lat_col: str = "lat_utm",
|
|
242
|
+
) -> pd.DataFrame:
|
|
243
|
+
"""Transforms trajectories from pixel to utm coordinates using homography from
|
|
244
|
+
get_homography using corresponding reference points, adds utm coordinates to
|
|
245
|
+
trajectories
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
tracks_px (pandas.DataFrame): Trajectories in pixel coordinates
|
|
249
|
+
homography (numpy.ndarry): Homography matrix between pixel and utm coordinates
|
|
250
|
+
refpts_utm_upshifted_predecimal_pt1_1row (numpy.ndarry): Thousands digits
|
|
251
|
+
of reference points in utm coorindates
|
|
252
|
+
upshift_utm (numpy.ndarry): Upshift of reference points coordinates
|
|
253
|
+
x_col (str, optional): Column name of x-pixel values. Defaults to "x".
|
|
254
|
+
y_col (str, optional): Column name of y-pixel values. Defaults to "y".
|
|
255
|
+
lon_col (str, optional): Column name of lon values. Defaults to "lon_utm".
|
|
256
|
+
lat_col (str, optional): Column name of lat values. Defaults to "lat_utm".
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
pandas.DataFrame: Trajectories in utm coordinates
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
tracks_utm = deepcopy(tracks_px)
|
|
263
|
+
|
|
264
|
+
# Transform pandas DataFrame to numpy array, add 1 dimension and apply OpenCV´s
|
|
265
|
+
# perspective transformation
|
|
266
|
+
tracks_px_np = tracks_px[[x_col, y_col]].to_numpy(dtype="float32")
|
|
267
|
+
tracks_px_np_tmp = np.array([tracks_px_np], dtype="float32")
|
|
268
|
+
tracks_utm_upshifted_np_disassembled_3d = cv2.perspectiveTransform(
|
|
269
|
+
tracks_px_np_tmp, homography
|
|
270
|
+
)
|
|
271
|
+
tracks_utm_upshifted_np_disassembled = np.squeeze(
|
|
272
|
+
tracks_utm_upshifted_np_disassembled_3d
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
# Concatenate the thousands digits truncated before transformation
|
|
276
|
+
tracks_utm_upshifted_np = np.add(
|
|
277
|
+
np.multiply(refpts_utm_upshifted_predecimal_pt1_1row, 1000),
|
|
278
|
+
tracks_utm_upshifted_np_disassembled,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# Shift back down both y and y world coordinates (same values as reference points
|
|
282
|
+
# were upshifted)
|
|
283
|
+
tracks_utm_np = np.subtract(tracks_utm_upshifted_np, upshift_utm)
|
|
284
|
+
|
|
285
|
+
# In trajectory DataFrame, overwrite pixel with utm coordinates (from numpy array)
|
|
286
|
+
tracks_utm[[lon_col, lat_col]] = tracks_utm_np
|
|
287
|
+
|
|
288
|
+
return tracks_utm
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
# TODO: Type hint nested dict during refactoring
|
|
292
|
+
def write_refpts(refpts: dict, refpts_file: Path) -> None:
|
|
293
|
+
"""Writes corresponding reference points in both pixel and utm coordinates
|
|
294
|
+
to a json-like .otrfpts file
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
refpts (dict): Corresponding reference points in both pixel and utm coordinates
|
|
298
|
+
refpts_file (str or pathlib.Path): Path of the refpts file to be written
|
|
299
|
+
"""
|
|
300
|
+
write_json(
|
|
301
|
+
dict_to_write=refpts,
|
|
302
|
+
file=refpts_file,
|
|
303
|
+
filetype=CONFIG["DEFAULT_FILETYPE"]["REFPTS"],
|
|
304
|
+
overwrite=True,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
# TODO: Type hint nested dict during refactoring
|
|
309
|
+
def write_tracks(
|
|
310
|
+
tracks_utm_df: pd.DataFrame,
|
|
311
|
+
metadata_dict: dict,
|
|
312
|
+
utm_zone: int,
|
|
313
|
+
hemisphere: str,
|
|
314
|
+
tracks_file: Path,
|
|
315
|
+
filetype: str = "gpkg",
|
|
316
|
+
overwrite: bool = CONFIG[TRANSFORM][OVERWRITE],
|
|
317
|
+
) -> None:
|
|
318
|
+
"""Writes tracks as .gpkg and in specific epsg projection
|
|
319
|
+
according to UTM zone and hemisphere
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
tracks_utm_df (geopandas.GeoDataFrame): Trajectories in utm coordinates
|
|
323
|
+
config_dict (dict): Meta data dict
|
|
324
|
+
utm_zone (str): UTM zone.
|
|
325
|
+
hemisphere (str): Hemisphere where trajectories were recorded. "N" or "S".
|
|
326
|
+
tracks_file (str or pathlib.Path): Path to tracks file (in pixel coordinates)
|
|
327
|
+
filetype (str, optional): _description_. Defaults to "gpkg".
|
|
328
|
+
"""
|
|
329
|
+
|
|
330
|
+
# TODO: Write metadata
|
|
331
|
+
|
|
332
|
+
if filetype == "gpkg": # TODO: Extend guard with overwrite parameter
|
|
333
|
+
gpkg_file = tracks_file.with_suffix(".gpkg")
|
|
334
|
+
gpkg_file_already_exists = gpkg_file.is_file()
|
|
335
|
+
if overwrite or not gpkg_file_already_exists:
|
|
336
|
+
log.debug(f"Write {gpkg_file}")
|
|
337
|
+
# Get CRS UTM EPSG number
|
|
338
|
+
epsg = _get_epsg_from_utm_zone(utm_zone=utm_zone, hemisphere=hemisphere)
|
|
339
|
+
# Create, set crs and write geopandas.GeoDataFrame
|
|
340
|
+
gpd.GeoDataFrame(
|
|
341
|
+
tracks_utm_df,
|
|
342
|
+
geometry=gpd.points_from_xy(
|
|
343
|
+
tracks_utm_df["lon_utm"], tracks_utm_df["lat_utm"]
|
|
344
|
+
),
|
|
345
|
+
).set_crs(f"epsg:{epsg}").to_file(filename=gpkg_file, driver="GPKG")
|
|
346
|
+
if gpkg_file_already_exists:
|
|
347
|
+
log.debug(f"{gpkg_file} overwritten")
|
|
348
|
+
else:
|
|
349
|
+
log.debug(f"{gpkg_file} written")
|
|
350
|
+
else:
|
|
351
|
+
log.debug(f"{gpkg_file} already exists. To overwrite, set overwrite=True")
|
|
352
|
+
# TODO: Export tracks as ottrk (json)
|
OTVision/version.py
ADDED
|
File without changes
|
|
Binary file
|
OTVision/view/view.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OTVision main gui module
|
|
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 tkinter as tk
|
|
24
|
+
|
|
25
|
+
from OTVision.config import CONFIG, PAD
|
|
26
|
+
from OTVision.helpers.machine import ON_LINUX, ON_WINDOWS
|
|
27
|
+
from OTVision.view.view_convert import FrameConvert
|
|
28
|
+
from OTVision.view.view_detect import FrameDetect
|
|
29
|
+
from OTVision.view.view_helpers import FrameFileTree, FrameRunChained
|
|
30
|
+
from OTVision.view.view_track import FrameTrack
|
|
31
|
+
from OTVision.view.view_transform import FrameTransform
|
|
32
|
+
|
|
33
|
+
FRAME_WIDTH = 50
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class WindowOTVision(tk.Tk):
|
|
37
|
+
def __init__(self, **kwargs):
|
|
38
|
+
super().__init__(**kwargs)
|
|
39
|
+
self.title("OTVision")
|
|
40
|
+
if ON_WINDOWS:
|
|
41
|
+
self.iconbitmap(CONFIG["GUI"]["OTC ICON"])
|
|
42
|
+
self.set_layout()
|
|
43
|
+
self.minsize(900, 620)
|
|
44
|
+
if ON_LINUX:
|
|
45
|
+
self.state("normal")
|
|
46
|
+
else:
|
|
47
|
+
self.state("zoomed")
|
|
48
|
+
|
|
49
|
+
def set_layout(self):
|
|
50
|
+
for col in range(3):
|
|
51
|
+
self.columnconfigure(index=col, weight=1)
|
|
52
|
+
self.rowconfigure(index=0, weight=1) # Stretches frame_files only
|
|
53
|
+
# Treeview files
|
|
54
|
+
self.frame_files = FrameFileTree(master=self, text="Choose files")
|
|
55
|
+
self.frame_files.grid(**PAD, row=0, column=0, columnspan=4, sticky="nsew")
|
|
56
|
+
# Settings
|
|
57
|
+
# Convert
|
|
58
|
+
self.frame_convert = FrameConvert(master=self, text="Convert")
|
|
59
|
+
self.frame_convert.grid(**PAD, row=1, column=0, sticky="nsew")
|
|
60
|
+
# Detect
|
|
61
|
+
self.frame_detect = FrameDetect(master=self, text="Detect")
|
|
62
|
+
self.frame_detect.grid(**PAD, row=1, column=1, sticky="nsew")
|
|
63
|
+
# Track
|
|
64
|
+
self.frame_track = FrameTrack(master=self, text="Track")
|
|
65
|
+
self.frame_track.grid(**PAD, row=1, column=2, sticky="nsew")
|
|
66
|
+
# # Undistort # TODO
|
|
67
|
+
# self.frame_undistort = FrameUndistort(master=self, text="Undistort")
|
|
68
|
+
# self.frame_undistort.pack(**PAD, side="left", expand=True)
|
|
69
|
+
# # Transform # TODO
|
|
70
|
+
self.frame_transform = FrameTransform(master=self, text="Transform")
|
|
71
|
+
self.frame_transform.grid(**PAD, row=1, column=3, sticky="nsew")
|
|
72
|
+
# Run chained
|
|
73
|
+
self.frame_run_chained = FrameRunChained(master=self, text="Run chained")
|
|
74
|
+
self.frame_run_chained.grid(**PAD, row=2, column=0, columnspan=4, sticky="ew")
|
|
75
|
+
|
|
76
|
+
def toggle_frame_detect(self, event):
|
|
77
|
+
if self.checkbutton_convert_var.get():
|
|
78
|
+
self.frame_convert.configure(state="normal")
|
|
79
|
+
else:
|
|
80
|
+
self.frame_convert.configure(state="disabled")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def main():
|
|
84
|
+
global app
|
|
85
|
+
app = WindowOTVision()
|
|
86
|
+
app.mainloop()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if __name__ == "__main__":
|
|
90
|
+
main()
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OTVision gui module for convert.py
|
|
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 tkinter as tk
|
|
25
|
+
import tkinter.ttk as ttk
|
|
26
|
+
|
|
27
|
+
from OTVision.config import CONFIG, PAD
|
|
28
|
+
from OTVision.convert.convert import main as convert
|
|
29
|
+
from OTVision.helpers.files import get_files, replace_filetype
|
|
30
|
+
from OTVision.helpers.log import LOGGER_NAME
|
|
31
|
+
from OTVision.view.view_helpers import FrameRun
|
|
32
|
+
|
|
33
|
+
log = logging.getLogger(LOGGER_NAME)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class FrameConvert(tk.LabelFrame):
|
|
37
|
+
def __init__(self, **kwargs):
|
|
38
|
+
super().__init__(**kwargs)
|
|
39
|
+
self.frame_options = FrameConvertOptions(master=self)
|
|
40
|
+
self.frame_options.pack(**PAD, fill="x", expand=1, anchor="n")
|
|
41
|
+
self.frame_run = FrameRunConversion(master=self)
|
|
42
|
+
self.frame_run.pack(**PAD, side="left", fill="both", expand=1, anchor="s")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class FrameConvertOptions(tk.Frame):
|
|
46
|
+
def __init__(self, **kwargs):
|
|
47
|
+
super().__init__(**kwargs)
|
|
48
|
+
# File type
|
|
49
|
+
self.label_filtype = tk.Label(master=self, text="Output file type")
|
|
50
|
+
self.label_filtype.grid(row=0, column=0, sticky="w")
|
|
51
|
+
self.combo_filtype = ttk.Combobox(
|
|
52
|
+
master=self,
|
|
53
|
+
values=[str.replace(".", "") for str in CONFIG["FILETYPES"]["VID"]],
|
|
54
|
+
width=5,
|
|
55
|
+
)
|
|
56
|
+
self.combo_filtype.grid(row=0, column=1, sticky="w")
|
|
57
|
+
self.combo_filtype.set(CONFIG["DEFAULT_FILETYPE"]["VID"].replace(".", ""))
|
|
58
|
+
# Frame rate from video
|
|
59
|
+
self.checkbutton_use_framerate_var = tk.BooleanVar()
|
|
60
|
+
self.checkbutton_use_framerate = tk.Checkbutton(
|
|
61
|
+
master=self,
|
|
62
|
+
text="Use frame rate from file name",
|
|
63
|
+
variable=self.checkbutton_use_framerate_var,
|
|
64
|
+
)
|
|
65
|
+
self.checkbutton_use_framerate.bind(
|
|
66
|
+
"<ButtonRelease-1>", self.toggle_entry_framerate
|
|
67
|
+
)
|
|
68
|
+
self.checkbutton_use_framerate.grid(row=1, column=0, columnspan=2, sticky="w")
|
|
69
|
+
self.checkbutton_use_framerate.select()
|
|
70
|
+
# Input frame rate
|
|
71
|
+
self.label_framerate = tk.Label(master=self, text="Input frame rate")
|
|
72
|
+
self.label_framerate.grid(row=2, column=0, sticky="w")
|
|
73
|
+
self.entry_framerate = tk.Entry(master=self, width=4)
|
|
74
|
+
self.entry_framerate.grid(row=2, column=1, sticky="w")
|
|
75
|
+
self.entry_framerate.insert(index=0, string=CONFIG["CONVERT"]["INPUT_FPS"])
|
|
76
|
+
self.entry_framerate.configure(state="disabled")
|
|
77
|
+
# Overwrite
|
|
78
|
+
self.checkbutton_overwrite_var = tk.BooleanVar()
|
|
79
|
+
self.checkbutton_overwrite = tk.Checkbutton(
|
|
80
|
+
master=self,
|
|
81
|
+
text="Overwrite existing videos",
|
|
82
|
+
variable=self.checkbutton_overwrite_var,
|
|
83
|
+
)
|
|
84
|
+
self.checkbutton_overwrite.grid(row=3, column=0, columnspan=2, sticky="w")
|
|
85
|
+
if CONFIG["CONVERT"]["OVERWRITE"]:
|
|
86
|
+
self.checkbutton_overwrite.select()
|
|
87
|
+
|
|
88
|
+
def toggle_entry_framerate(self, event):
|
|
89
|
+
if self.checkbutton_use_framerate_var.get():
|
|
90
|
+
self.entry_framerate.configure(state="normal")
|
|
91
|
+
self.entry_framerate.delete(0, "end")
|
|
92
|
+
self.entry_framerate.insert(0, CONFIG["CONVERT"]["FPS"])
|
|
93
|
+
else:
|
|
94
|
+
self.entry_framerate.configure(state="disabled")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class FrameRunConversion(FrameRun):
|
|
98
|
+
def __init__(self, **kwargs):
|
|
99
|
+
super().__init__(**kwargs)
|
|
100
|
+
self.button_run.bind("<ButtonRelease-1>", self.run)
|
|
101
|
+
if CONFIG["CONVERT"]["RUN_CHAINED"]:
|
|
102
|
+
self.checkbutton_run_chained.select()
|
|
103
|
+
|
|
104
|
+
def run(self, event):
|
|
105
|
+
fps_from_filename = (
|
|
106
|
+
self.master.frame_options.checkbutton_use_framerate_var.get()
|
|
107
|
+
)
|
|
108
|
+
files = replace_filetype(
|
|
109
|
+
files=self.master.master.frame_files.get_tree_files(), new_filetype=".h264"
|
|
110
|
+
)
|
|
111
|
+
files = get_files(
|
|
112
|
+
paths=files,
|
|
113
|
+
filetypes=[".h264"],
|
|
114
|
+
)
|
|
115
|
+
output_filetype = f".{self.master.frame_options.combo_filtype.get()}"
|
|
116
|
+
input_fps = self.master.frame_options.entry_framerate.get()
|
|
117
|
+
output_fps = self.master.frame_options.entry_framerate.get()
|
|
118
|
+
overwrite = self.master.frame_options.checkbutton_use_framerate_var.get()
|
|
119
|
+
log.info("Call convert from GUI")
|
|
120
|
+
convert(
|
|
121
|
+
paths=files,
|
|
122
|
+
output_filetype=output_filetype,
|
|
123
|
+
input_fps=input_fps,
|
|
124
|
+
output_fps=output_fps,
|
|
125
|
+
fps_from_filename=fps_from_filename,
|
|
126
|
+
overwrite=overwrite,
|
|
127
|
+
)
|
|
128
|
+
self.master.master.frame_files.update_files_dict()
|