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
@@ -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
@@ -0,0 +1,13 @@
1
+ __version__ = "v0.5.3"
2
+
3
+
4
+ def otdet_version() -> str:
5
+ return "1.3"
6
+
7
+
8
+ def ottrack_version() -> str:
9
+ return "1.1"
10
+
11
+
12
+ def otvision_version() -> str:
13
+ return __version__
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()