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,146 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OTVision gui module for detect.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.detect.detect import main as detect
|
|
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 FrameDetect(tk.LabelFrame):
|
|
37
|
+
def __init__(self, **kwargs):
|
|
38
|
+
super().__init__(**kwargs)
|
|
39
|
+
self.frame_options = FrameDetectOptions(
|
|
40
|
+
master=self
|
|
41
|
+
) # Always name this "frame_options"
|
|
42
|
+
self.frame_options.pack(**PAD, fill="both", expand=1, anchor="n")
|
|
43
|
+
self.frame_run = FrameRunDetection(master=self)
|
|
44
|
+
self.frame_run.pack(**PAD, side="left", fill="both", expand=1, anchor="s")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class FrameDetectOptions(tk.Frame):
|
|
48
|
+
def __init__(self, **kwargs):
|
|
49
|
+
super().__init__(**kwargs)
|
|
50
|
+
# Weights
|
|
51
|
+
self.label_weights = tk.Label(master=self, text="Weights")
|
|
52
|
+
self.label_weights.grid(row=0, column=0, sticky="w")
|
|
53
|
+
self.combo_weights = ttk.Combobox(
|
|
54
|
+
master=self, values=CONFIG["DETECT"]["YOLO"]["AVAILABLEWEIGHTS"], width=8
|
|
55
|
+
)
|
|
56
|
+
self.combo_weights.grid(row=0, column=1, sticky="w")
|
|
57
|
+
self.combo_weights.set(CONFIG["DETECT"]["YOLO"]["WEIGHTS"])
|
|
58
|
+
# Confidence
|
|
59
|
+
self.label_conf = tk.Label(master=self, text="Confidence")
|
|
60
|
+
self.label_conf.grid(row=1, column=0, sticky="sw")
|
|
61
|
+
self.scale_conf = tk.Scale(
|
|
62
|
+
master=self, from_=0, to=1, resolution=0.01, orient="horizontal"
|
|
63
|
+
)
|
|
64
|
+
self.scale_conf.grid(row=1, column=1, sticky="w")
|
|
65
|
+
self.scale_conf.set(CONFIG["DETECT"]["YOLO"]["CONF"])
|
|
66
|
+
# IOU
|
|
67
|
+
self.label_iou = tk.Label(master=self, text="IOU")
|
|
68
|
+
self.label_iou.grid(row=2, column=0, sticky="sw")
|
|
69
|
+
self.scale_iou = tk.Scale(
|
|
70
|
+
master=self, from_=0, to=1, resolution=0.01, orient="horizontal"
|
|
71
|
+
)
|
|
72
|
+
self.scale_iou.grid(row=2, column=1, sticky="w")
|
|
73
|
+
self.scale_iou.set(CONFIG["DETECT"]["YOLO"]["IOU"])
|
|
74
|
+
# Image size
|
|
75
|
+
self.label_imgsize = tk.Label(master=self, text="Image size")
|
|
76
|
+
self.label_imgsize.grid(row=3, column=0, sticky="sw")
|
|
77
|
+
self.scale_imgsize = tk.Scale(
|
|
78
|
+
master=self, from_=100, to=1000, resolution=10, orient="horizontal"
|
|
79
|
+
)
|
|
80
|
+
self.scale_imgsize.grid(row=3, column=1, sticky="w")
|
|
81
|
+
self.scale_imgsize.set(CONFIG["DETECT"]["YOLO"]["IMGSIZE"])
|
|
82
|
+
# Chunk size
|
|
83
|
+
self.label_chunksize = tk.Label(master=self, text="Chunk size")
|
|
84
|
+
self.label_chunksize.grid(row=4, column=0, sticky="sw")
|
|
85
|
+
self.scale_chunksize = tk.Scale(
|
|
86
|
+
master=self, from_=1, to=20, resolution=1, orient="horizontal"
|
|
87
|
+
)
|
|
88
|
+
self.scale_chunksize.grid(row=4, column=1, sticky="w")
|
|
89
|
+
self.scale_chunksize.set(CONFIG["DETECT"]["YOLO"]["CHUNKSIZE"])
|
|
90
|
+
# Normalized
|
|
91
|
+
self.checkbutton_normalized_var = tk.BooleanVar()
|
|
92
|
+
self.checkbutton_normalized = tk.Checkbutton(
|
|
93
|
+
master=self, text="Normalized", variable=self.checkbutton_normalized_var
|
|
94
|
+
)
|
|
95
|
+
self.checkbutton_normalized.grid(row=5, column=0, columnspan=2, sticky="w")
|
|
96
|
+
# self.checkbutton_overwrite.select() # BUG: Still selected
|
|
97
|
+
# Overwrite
|
|
98
|
+
self.checkbutton_overwrite_var = tk.BooleanVar()
|
|
99
|
+
self.checkbutton_overwrite = tk.Checkbutton(
|
|
100
|
+
master=self, text="Overwrite", variable=self.checkbutton_overwrite_var
|
|
101
|
+
)
|
|
102
|
+
self.checkbutton_overwrite.grid(row=6, column=0, columnspan=2, sticky="w")
|
|
103
|
+
if CONFIG["DETECT"]["OVERWRITE"]:
|
|
104
|
+
self.checkbutton_overwrite.select()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class FrameRunDetection(FrameRun):
|
|
108
|
+
def __init__(self, **kwargs):
|
|
109
|
+
super().__init__(**kwargs)
|
|
110
|
+
self.button_run.bind("<ButtonRelease-1>", self.run)
|
|
111
|
+
if CONFIG["DETECT"]["RUN_CHAINED"]:
|
|
112
|
+
self.checkbutton_run_chained.select()
|
|
113
|
+
|
|
114
|
+
def run(self, event):
|
|
115
|
+
input_filetype = f".{self.master.master.frame_files.combo_vid_filetype.get()}"
|
|
116
|
+
|
|
117
|
+
files = replace_filetype(
|
|
118
|
+
files=self.master.master.frame_files.get_tree_files(),
|
|
119
|
+
new_filetype=input_filetype,
|
|
120
|
+
)
|
|
121
|
+
files = get_files(
|
|
122
|
+
paths=files,
|
|
123
|
+
filetypes=[input_filetype],
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
weights = self.master.frame_options.combo_weights.get()
|
|
127
|
+
conf = self.master.frame_options.scale_conf.get()
|
|
128
|
+
iou = self.master.frame_options.scale_iou.get()
|
|
129
|
+
size = self.master.frame_options.scale_imgsize.get()
|
|
130
|
+
chunksize = self.master.frame_options.scale_chunksize.get()
|
|
131
|
+
normalized = self.master.frame_options.checkbutton_normalized_var.get()
|
|
132
|
+
overwrite = self.master.frame_options.checkbutton_overwrite_var.get()
|
|
133
|
+
log.info("Call detect from GUI")
|
|
134
|
+
detect(
|
|
135
|
+
paths=files,
|
|
136
|
+
filetypes=[input_filetype],
|
|
137
|
+
weights=weights,
|
|
138
|
+
conf=conf,
|
|
139
|
+
iou=iou,
|
|
140
|
+
size=size,
|
|
141
|
+
chunksize=chunksize,
|
|
142
|
+
normalized=normalized,
|
|
143
|
+
overwrite=overwrite,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
self.master.master.frame_files.update_files_dict()
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OTVision helper 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 logging
|
|
24
|
+
import tkinter as tk
|
|
25
|
+
import tkinter.ttk as ttk
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from tkinter import filedialog
|
|
28
|
+
|
|
29
|
+
from OTVision.config import CONFIG, PAD
|
|
30
|
+
from OTVision.helpers.files import get_files
|
|
31
|
+
from OTVision.helpers.log import LOGGER_NAME
|
|
32
|
+
|
|
33
|
+
log = logging.getLogger(LOGGER_NAME)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class FrameFileTree(tk.LabelFrame):
|
|
37
|
+
def __init__(self, **kwargs):
|
|
38
|
+
super().__init__(**kwargs)
|
|
39
|
+
|
|
40
|
+
# File dict
|
|
41
|
+
self.files_dict = {}
|
|
42
|
+
self.vid_filetype = CONFIG["DEFAULT_FILETYPE"]["VID"].replace(".", "")
|
|
43
|
+
|
|
44
|
+
# Frame for controls
|
|
45
|
+
self.frame_controls = tk.Frame(master=self)
|
|
46
|
+
self.frame_controls.pack()
|
|
47
|
+
|
|
48
|
+
# Search subdir checkbox
|
|
49
|
+
self.checkbutton_subdir_var = tk.BooleanVar()
|
|
50
|
+
self.checkbutton_subdir = tk.Checkbutton(
|
|
51
|
+
master=self.frame_controls,
|
|
52
|
+
text="Search subfolders",
|
|
53
|
+
variable=self.checkbutton_subdir_var,
|
|
54
|
+
)
|
|
55
|
+
self.checkbutton_subdir.grid(row=0, column=0, sticky="w")
|
|
56
|
+
self.checkbutton_subdir_var.set(CONFIG["SEARCH_SUBDIRS"])
|
|
57
|
+
|
|
58
|
+
# File type dropdowns
|
|
59
|
+
self.label_vid_filetype = tk.Label(
|
|
60
|
+
master=self.frame_controls, text="Video file type"
|
|
61
|
+
)
|
|
62
|
+
self.label_vid_filetype.grid(row=0, column=5, sticky="w")
|
|
63
|
+
self.combo_vid_filetype = ttk.Combobox(
|
|
64
|
+
master=self.frame_controls,
|
|
65
|
+
values=[str.replace(".", "") for str in CONFIG["FILETYPES"]["VID"]],
|
|
66
|
+
width=5,
|
|
67
|
+
)
|
|
68
|
+
self.combo_vid_filetype.grid(row=0, column=6, sticky="w")
|
|
69
|
+
self.combo_vid_filetype.bind("<<ComboboxSelected>>", self.set_vid_filetype)
|
|
70
|
+
self.combo_vid_filetype.set(self.vid_filetype)
|
|
71
|
+
|
|
72
|
+
# File buttons
|
|
73
|
+
self.button_add_folder = tk.Button(
|
|
74
|
+
master=self.frame_controls, text="Add folder"
|
|
75
|
+
)
|
|
76
|
+
self.button_add_folder.bind("<ButtonRelease-1>", self.add_dirs)
|
|
77
|
+
self.button_add_folder.grid(row=0, column=1, sticky="ew")
|
|
78
|
+
self.button_add_file = tk.Button(master=self.frame_controls, text="Add files")
|
|
79
|
+
self.button_add_file.bind("<ButtonRelease-1>", self.add_files)
|
|
80
|
+
self.button_add_file.grid(row=0, column=2, sticky="ew")
|
|
81
|
+
self.button_rem_sel = tk.Button(
|
|
82
|
+
master=self.frame_controls, text="Remove selected"
|
|
83
|
+
)
|
|
84
|
+
self.button_rem_sel.bind("<ButtonRelease-1>", self.remove_selected)
|
|
85
|
+
self.button_rem_sel.grid(row=0, column=3, sticky="ew")
|
|
86
|
+
self.button_rem_all = tk.Button(master=self.frame_controls, text="Remove all")
|
|
87
|
+
self.button_rem_all.bind("<ButtonRelease-1>", self.remove_all)
|
|
88
|
+
self.button_rem_all.grid(row=0, column=4, sticky="ew")
|
|
89
|
+
|
|
90
|
+
# Frame for treeview
|
|
91
|
+
self.frame_tree = tk.Frame(master=self)
|
|
92
|
+
self.frame_tree.pack(
|
|
93
|
+
**PAD, fill="both", expand=True
|
|
94
|
+
) # BUG: Treeview fills only to the left
|
|
95
|
+
|
|
96
|
+
# Files treeview
|
|
97
|
+
self.tree_files = ttk.Treeview(master=self.frame_tree, height=5)
|
|
98
|
+
self.tree_files.bind("<ButtonRelease-3>", self.deselect_tree_files)
|
|
99
|
+
tree_files_cols = {
|
|
100
|
+
"#0": "File",
|
|
101
|
+
"h264": "h264",
|
|
102
|
+
"video": self.vid_filetype,
|
|
103
|
+
"otdet": "otdet",
|
|
104
|
+
"ottrk": "ottrk",
|
|
105
|
+
"otrfpts": "otrfpts",
|
|
106
|
+
}
|
|
107
|
+
self.tree_files["columns"] = tuple(
|
|
108
|
+
{k: v for k, v in tree_files_cols.items() if k != "#0"}.keys()
|
|
109
|
+
)
|
|
110
|
+
for tree_files_col_id, tree_files_col_text in tree_files_cols.items():
|
|
111
|
+
if tree_files_col_id == "#0":
|
|
112
|
+
anchor = "w"
|
|
113
|
+
width = 2000
|
|
114
|
+
minwidth = 200
|
|
115
|
+
stretch = True
|
|
116
|
+
self.tree_files.column(
|
|
117
|
+
tree_files_col_id,
|
|
118
|
+
minwidth=minwidth,
|
|
119
|
+
stretch=stretch,
|
|
120
|
+
anchor=anchor,
|
|
121
|
+
)
|
|
122
|
+
else:
|
|
123
|
+
anchor = "center"
|
|
124
|
+
width = 45
|
|
125
|
+
minwidth = 45
|
|
126
|
+
stretch = False
|
|
127
|
+
self.tree_files.column(
|
|
128
|
+
tree_files_col_id,
|
|
129
|
+
width=width,
|
|
130
|
+
stretch=stretch,
|
|
131
|
+
anchor=anchor,
|
|
132
|
+
)
|
|
133
|
+
self.tree_files.heading(
|
|
134
|
+
tree_files_col_id, text=tree_files_col_text, anchor=anchor
|
|
135
|
+
)
|
|
136
|
+
self.tree_files.pack(side="left", fill="both", expand=True)
|
|
137
|
+
|
|
138
|
+
# Scrollbar for treeview
|
|
139
|
+
self.tree_scrollbar = ttk.Scrollbar(
|
|
140
|
+
master=self.frame_tree, orient="vertical", command=self.tree_files.yview
|
|
141
|
+
)
|
|
142
|
+
self.tree_scrollbar.pack(side="right", fill="y")
|
|
143
|
+
self.tree_files.configure(yscrollcommand=self.tree_scrollbar.set)
|
|
144
|
+
|
|
145
|
+
def set_vid_filetype(self, event):
|
|
146
|
+
self.vid_filetype = self.combo_vid_filetype.get()
|
|
147
|
+
for path in self.files_dict.keys():
|
|
148
|
+
self.update_files_dict_values(path)
|
|
149
|
+
self.tree_files.heading("video", text=self.vid_filetype)
|
|
150
|
+
self.update_tree_files()
|
|
151
|
+
|
|
152
|
+
def add_dirs(self, event):
|
|
153
|
+
new_dir = filedialog.askdirectory(title="Select a folder", mustexist=True)
|
|
154
|
+
# NOTE: h264 is only included on Windows for now
|
|
155
|
+
new_files = get_files(
|
|
156
|
+
[Path(new_dir)],
|
|
157
|
+
filetypes=[
|
|
158
|
+
".h264",
|
|
159
|
+
f".{self.vid_filetype}",
|
|
160
|
+
CONFIG["DEFAULT_FILETYPE"]["DETECT"],
|
|
161
|
+
],
|
|
162
|
+
search_subdirs=self.checkbutton_subdir_var.get(),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
unique_new_files = []
|
|
166
|
+
for file in new_files:
|
|
167
|
+
if file.with_suffix("") not in [
|
|
168
|
+
f.with_suffix("") for f in unique_new_files
|
|
169
|
+
]:
|
|
170
|
+
unique_new_files.append(file)
|
|
171
|
+
self.add_to_files_dict(unique_new_files)
|
|
172
|
+
|
|
173
|
+
def add_files(self, event):
|
|
174
|
+
# Show filedialog
|
|
175
|
+
new_paths_str = list(
|
|
176
|
+
filedialog.askopenfilenames(
|
|
177
|
+
title="Select one or multiple files",
|
|
178
|
+
# NOTE: h264 is only included on Windows for now
|
|
179
|
+
filetypes=[
|
|
180
|
+
(".h264", ".h264"),
|
|
181
|
+
(".mp4", ".mp4"),
|
|
182
|
+
(".otdet", ".otdet"),
|
|
183
|
+
(".ottrk", ".ottrk"),
|
|
184
|
+
("all files", "*.*"),
|
|
185
|
+
],
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
# Check paths
|
|
189
|
+
new_paths = [Path(path_str) for path_str in new_paths_str]
|
|
190
|
+
new_paths = get_files(new_paths)
|
|
191
|
+
self.add_to_files_dict(new_paths)
|
|
192
|
+
|
|
193
|
+
def remove_selected(self, event):
|
|
194
|
+
for item in self.tree_files.selection():
|
|
195
|
+
del self.files_dict[Path(self.tree_files.item(item)["text"])]
|
|
196
|
+
self.update_tree_files()
|
|
197
|
+
|
|
198
|
+
def remove_all(self, event):
|
|
199
|
+
self.files_dict = {}
|
|
200
|
+
self.update_tree_files()
|
|
201
|
+
|
|
202
|
+
def add_to_files_dict(self, paths):
|
|
203
|
+
for path in paths:
|
|
204
|
+
self.files_dict[path] = {}
|
|
205
|
+
self.update_files_dict_values(path)
|
|
206
|
+
self.update_tree_files()
|
|
207
|
+
|
|
208
|
+
def update_files_dict(self):
|
|
209
|
+
for path in self.files_dict.keys():
|
|
210
|
+
self.update_files_dict_values(path)
|
|
211
|
+
self.update_tree_files()
|
|
212
|
+
|
|
213
|
+
def update_files_dict_values(self, path):
|
|
214
|
+
TRUE_SYMBOL = "\u2705" # "\u2713" # "\u2714"
|
|
215
|
+
FALSE_SYMBOL = "\u274E" # "\u2717" # "\u2718"
|
|
216
|
+
self.files_dict[path]["filename"] = Path(path).stem
|
|
217
|
+
self.files_dict[path]["h264"] = (
|
|
218
|
+
TRUE_SYMBOL if Path(path).with_suffix(".h264").is_file() else FALSE_SYMBOL
|
|
219
|
+
)
|
|
220
|
+
self.files_dict[path]["video"] = (
|
|
221
|
+
TRUE_SYMBOL
|
|
222
|
+
if Path(path).with_suffix(f".{self.vid_filetype}").is_file()
|
|
223
|
+
else FALSE_SYMBOL
|
|
224
|
+
)
|
|
225
|
+
self.files_dict[path]["otdet"] = (
|
|
226
|
+
TRUE_SYMBOL if Path(path).with_suffix(".otdet").is_file() else FALSE_SYMBOL
|
|
227
|
+
)
|
|
228
|
+
self.files_dict[path]["ottrk"] = (
|
|
229
|
+
TRUE_SYMBOL if Path(path).with_suffix(".ottrk").is_file() else FALSE_SYMBOL
|
|
230
|
+
)
|
|
231
|
+
self.files_dict[path]["otrfpts"] = (
|
|
232
|
+
TRUE_SYMBOL
|
|
233
|
+
if Path(path).with_suffix(".otrfpts").is_file()
|
|
234
|
+
else FALSE_SYMBOL
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
def update_tree_files(self):
|
|
238
|
+
self.tree_files.delete(*self.tree_files.get_children())
|
|
239
|
+
for path, file_values in self.files_dict.items():
|
|
240
|
+
self.tree_files.insert(
|
|
241
|
+
parent="",
|
|
242
|
+
index="end",
|
|
243
|
+
text=str(path),
|
|
244
|
+
values=(
|
|
245
|
+
file_values["h264"],
|
|
246
|
+
file_values["video"],
|
|
247
|
+
file_values["otdet"],
|
|
248
|
+
file_values["ottrk"],
|
|
249
|
+
file_values["otrfpts"],
|
|
250
|
+
),
|
|
251
|
+
)
|
|
252
|
+
self.update_other_gui_parts()
|
|
253
|
+
|
|
254
|
+
def update_other_gui_parts(self):
|
|
255
|
+
# Activate/deactivate buttons in FrameTransform
|
|
256
|
+
if self.files_dict:
|
|
257
|
+
self.master.frame_transform.frame_options.button_choose_refpts["state"] = (
|
|
258
|
+
tk.NORMAL
|
|
259
|
+
)
|
|
260
|
+
self.master.frame_transform.frame_options.button_click_refpts["state"] = (
|
|
261
|
+
tk.NORMAL
|
|
262
|
+
)
|
|
263
|
+
else:
|
|
264
|
+
self.master.frame_transform.frame_options.button_choose_refpts["state"] = (
|
|
265
|
+
tk.DISABLED
|
|
266
|
+
)
|
|
267
|
+
self.master.frame_transform.frame_options.button_click_refpts["state"] = (
|
|
268
|
+
tk.DISABLED
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
def get_tree_files(self):
|
|
272
|
+
return [
|
|
273
|
+
Path(self.tree_files.item(item)["text"])
|
|
274
|
+
for item in self.tree_files.get_children()
|
|
275
|
+
]
|
|
276
|
+
|
|
277
|
+
def get_selected_files(self):
|
|
278
|
+
selected_files = [
|
|
279
|
+
self.tree_files.item(item)["text"] for item in self.tree_files.selection()
|
|
280
|
+
]
|
|
281
|
+
log.debug("Selected files:")
|
|
282
|
+
log.debug(selected_files)
|
|
283
|
+
return selected_files
|
|
284
|
+
|
|
285
|
+
def deselect_tree_files(self, events):
|
|
286
|
+
for item in self.tree_files.selection():
|
|
287
|
+
self.tree_files.selection_remove(item)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
class FrameFiles(tk.LabelFrame):
|
|
291
|
+
def __init__(self, filecategory, default_filetype, filetypes=None, **kwargs):
|
|
292
|
+
super().__init__(**kwargs)
|
|
293
|
+
# File type
|
|
294
|
+
self.default_filetype = default_filetype
|
|
295
|
+
self.filetype = self.default_filetype
|
|
296
|
+
if filetypes:
|
|
297
|
+
self.label_filetype = tk.Label(master=self, text="Input file type")
|
|
298
|
+
self.label_filetype.grid(row=0, column=0, sticky="w")
|
|
299
|
+
self.combo_filetype = ttk.Combobox(
|
|
300
|
+
master=self,
|
|
301
|
+
values=filetypes,
|
|
302
|
+
)
|
|
303
|
+
self.combo_filetype.grid(row=0, column=1, sticky="w")
|
|
304
|
+
self.combo_filetype.bind("<<ComboboxSelected>>", self.set_filetype)
|
|
305
|
+
self.combo_filetype.set(default_filetype)
|
|
306
|
+
# File list
|
|
307
|
+
self.file_list = [] # ?: Add test data?
|
|
308
|
+
self.filecategory = filecategory
|
|
309
|
+
self.filetypes = filetypes
|
|
310
|
+
# File buttons
|
|
311
|
+
self.button_add_folder = tk.Button(master=self, text="Add folder")
|
|
312
|
+
self.button_add_folder.bind("<ButtonRelease-1>", self.add_dirs)
|
|
313
|
+
self.button_add_folder.grid(row=1, column=0, sticky="ew")
|
|
314
|
+
self.button_add_file = tk.Button(master=self, text="Add files")
|
|
315
|
+
self.button_add_file.bind("<ButtonRelease-1>", self.add_files)
|
|
316
|
+
self.button_add_file.grid(row=1, column=1, sticky="ew")
|
|
317
|
+
self.button_rem_sel = tk.Button(master=self, text="Remove selected")
|
|
318
|
+
self.button_rem_sel.bind("<ButtonRelease-1>", self.remove_selected)
|
|
319
|
+
self.button_rem_sel.grid(row=1, column=2, sticky="ew")
|
|
320
|
+
self.button_rem_all = tk.Button(master=self, text="Remove all")
|
|
321
|
+
self.button_rem_all.bind("<ButtonRelease-1>", self.remove_all)
|
|
322
|
+
self.button_rem_all.grid(row=1, column=3, sticky="ew")
|
|
323
|
+
# File list
|
|
324
|
+
self.listbox_files = tk.Listbox(master=self, width=150, selectmode="extended")
|
|
325
|
+
self.listbox_files.yview()
|
|
326
|
+
self.listbox_files.grid(row=2, column=0, columnspan=4, sticky="ew")
|
|
327
|
+
|
|
328
|
+
def set_filetype(self, event):
|
|
329
|
+
self.filetype = self.combo_filetype.get()
|
|
330
|
+
|
|
331
|
+
def get_listbox_files(self):
|
|
332
|
+
return self.listbox_files.get(first=0, last=self.listbox_files.size() - 1)
|
|
333
|
+
|
|
334
|
+
def get_listbox_file_indices(self):
|
|
335
|
+
return self.listbox_files.get(first=0, last=self.listbox_files.size() - 1)
|
|
336
|
+
|
|
337
|
+
def add_files(self, event):
|
|
338
|
+
new_paths_str = list(
|
|
339
|
+
filedialog.askopenfilenames(
|
|
340
|
+
title=f"Select one or multiple {self.filecategory}",
|
|
341
|
+
filetypes=[
|
|
342
|
+
(self.filecategory, self.filetype),
|
|
343
|
+
("all files", "*.*"),
|
|
344
|
+
],
|
|
345
|
+
)
|
|
346
|
+
)
|
|
347
|
+
new_paths = [Path(path_str) for path_str in new_paths_str]
|
|
348
|
+
new_paths = get_files(new_paths, [self.filetype])
|
|
349
|
+
self.add_to_listbox(new_paths)
|
|
350
|
+
|
|
351
|
+
def add_dirs(self, event):
|
|
352
|
+
new_dir = filedialog.askdirectory(title="Select a folder")
|
|
353
|
+
new_paths = get_files([Path(new_dir)], [self.filetype])
|
|
354
|
+
self.add_to_listbox(new_paths)
|
|
355
|
+
|
|
356
|
+
def add_to_listbox(self, new_paths):
|
|
357
|
+
for new_path in new_paths:
|
|
358
|
+
if new_path not in self.get_listbox_files():
|
|
359
|
+
self.listbox_files.insert("end", new_path)
|
|
360
|
+
|
|
361
|
+
def remove_selected(self, event):
|
|
362
|
+
selection = self.listbox_files.curselection()
|
|
363
|
+
self.remove_from_listbox(selection)
|
|
364
|
+
|
|
365
|
+
def remove_all(self, event):
|
|
366
|
+
selection = range(self.listbox_files.size())
|
|
367
|
+
self.remove_from_listbox(selection)
|
|
368
|
+
|
|
369
|
+
def remove_from_listbox(self, selection):
|
|
370
|
+
for delta, selected_file in enumerate(selection):
|
|
371
|
+
file_to_remove = selected_file - delta
|
|
372
|
+
self.listbox_files.delete(first=file_to_remove)
|
|
373
|
+
|
|
374
|
+
def _debug(self, event):
|
|
375
|
+
log.debug(event)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
class FrameRun(tk.Frame):
|
|
379
|
+
def __init__(self, button_label="Run!", **kwargs):
|
|
380
|
+
super().__init__(**kwargs)
|
|
381
|
+
# Run
|
|
382
|
+
self.button_run = tk.Button(master=self, text=button_label)
|
|
383
|
+
self.button_run.pack(fill="both")
|
|
384
|
+
# Include in chained run
|
|
385
|
+
self.checkbutton_run_chained_var = tk.BooleanVar()
|
|
386
|
+
self.checkbutton_run_chained = tk.Checkbutton(
|
|
387
|
+
master=self,
|
|
388
|
+
text="Include in chained run",
|
|
389
|
+
variable=self.checkbutton_run_chained_var,
|
|
390
|
+
)
|
|
391
|
+
self.checkbutton_run_chained.pack()
|
|
392
|
+
# self.checkbutton_run_chained.select()
|
|
393
|
+
# # Progress bar # TODO
|
|
394
|
+
# self.progress = ttk.Progressbar(master=self)
|
|
395
|
+
# self.progress.grid(row=1, column=0, sticky="ew")
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
class FrameRunChained(tk.LabelFrame):
|
|
399
|
+
def __init__(self, button_label="Run chained!", **kwargs):
|
|
400
|
+
super().__init__(**kwargs)
|
|
401
|
+
# Run
|
|
402
|
+
self.button_run = tk.Button(master=self, text=button_label)
|
|
403
|
+
self.button_run.pack(**PAD, fill="x", expand=1)
|
|
404
|
+
self.button_run.bind("<ButtonRelease-1>", self.run)
|
|
405
|
+
# # Progress bar # TODO
|
|
406
|
+
# self.progress = ttk.Progressbar(master=self)
|
|
407
|
+
# self.progress.grid(row=1, column=0, sticky="ew")
|
|
408
|
+
|
|
409
|
+
def run(self, event):
|
|
410
|
+
if self.master.frame_convert.frame_run.checkbutton_run_chained_var.get():
|
|
411
|
+
self.master.frame_convert.frame_run.run(event)
|
|
412
|
+
if self.master.frame_detect.frame_run.checkbutton_run_chained_var.get():
|
|
413
|
+
self.master.frame_detect.frame_run.run(event)
|
|
414
|
+
if self.master.frame_track.frame_run.checkbutton_run_chained_var.get():
|
|
415
|
+
self.master.frame_track.frame_run.run(event)
|
|
416
|
+
if self.master.frame_transform.frame_run.checkbutton_run_chained_var.get():
|
|
417
|
+
self.master.frame_transform.frame_run.run(event)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OTVision gui module for track.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
|
+
|
|
26
|
+
from OTVision.config import CONFIG, PAD
|
|
27
|
+
from OTVision.helpers.files import get_files, replace_filetype
|
|
28
|
+
from OTVision.helpers.log import LOGGER_NAME
|
|
29
|
+
from OTVision.track.track import main as track
|
|
30
|
+
from OTVision.view.view_helpers import FrameRun
|
|
31
|
+
|
|
32
|
+
log = logging.getLogger(LOGGER_NAME)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class FrameTrack(tk.LabelFrame):
|
|
36
|
+
def __init__(self, **kwargs):
|
|
37
|
+
super().__init__(**kwargs)
|
|
38
|
+
self.frame_options = FrameTrackOptions(
|
|
39
|
+
master=self
|
|
40
|
+
) # Always name this "frame_options"
|
|
41
|
+
self.frame_options.pack(**PAD, fill="both", expand=1, anchor="n")
|
|
42
|
+
self.frame_run = FrameRunTracking(master=self)
|
|
43
|
+
self.frame_run.pack(**PAD, side="left", fill="both", expand=1, anchor="s")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class FrameTrackOptions(tk.Frame):
|
|
47
|
+
def __init__(self, **kwargs):
|
|
48
|
+
super().__init__(**kwargs)
|
|
49
|
+
# Sigma l
|
|
50
|
+
self.label_sigma_l = tk.Label(master=self, text="sigma l")
|
|
51
|
+
self.label_sigma_l.grid(row=0, column=0, sticky="sw")
|
|
52
|
+
self.scale_sigma_l = tk.Scale(
|
|
53
|
+
master=self, from_=0, to=1, resolution=0.01, orient="horizontal"
|
|
54
|
+
)
|
|
55
|
+
self.scale_sigma_l.grid(row=0, column=1, sticky="w")
|
|
56
|
+
self.scale_sigma_l.set(CONFIG["TRACK"]["IOU"]["SIGMA_L"])
|
|
57
|
+
# Sigma h
|
|
58
|
+
self.label_sigma_h = tk.Label(master=self, text="sigma h")
|
|
59
|
+
self.label_sigma_h.grid(row=1, column=0, sticky="sw")
|
|
60
|
+
self.scale_sigma_h = tk.Scale(
|
|
61
|
+
master=self, from_=0, to=1, resolution=0.01, orient="horizontal"
|
|
62
|
+
)
|
|
63
|
+
self.scale_sigma_h.grid(row=1, column=1, sticky="w")
|
|
64
|
+
self.scale_sigma_h.set(CONFIG["TRACK"]["IOU"]["SIGMA_H"])
|
|
65
|
+
# Sigma IOU
|
|
66
|
+
self.label_sigma_iou = tk.Label(master=self, text="sigma IOU")
|
|
67
|
+
self.label_sigma_iou.grid(row=2, column=0, sticky="sw")
|
|
68
|
+
self.scale_sigma_iou = tk.Scale(
|
|
69
|
+
master=self, from_=0, to=1, resolution=0.01, orient="horizontal"
|
|
70
|
+
)
|
|
71
|
+
self.scale_sigma_iou.grid(row=2, column=1, sticky="w")
|
|
72
|
+
self.scale_sigma_iou.set(CONFIG["TRACK"]["IOU"]["SIGMA_IOU"])
|
|
73
|
+
# t min
|
|
74
|
+
self.label_t_min = tk.Label(master=self, text="t min")
|
|
75
|
+
self.label_t_min.grid(row=3, column=0, sticky="sw")
|
|
76
|
+
self.scale_t_min = tk.Scale(
|
|
77
|
+
master=self, from_=0, to=20, resolution=1, orient="horizontal"
|
|
78
|
+
)
|
|
79
|
+
self.scale_t_min.grid(row=3, column=1, sticky="w")
|
|
80
|
+
self.scale_t_min.set(CONFIG["TRACK"]["IOU"]["T_MIN"])
|
|
81
|
+
# t miss max
|
|
82
|
+
self.label_t_miss_max = tk.Label(master=self, text="t miss max")
|
|
83
|
+
self.label_t_miss_max.grid(row=4, column=0, sticky="sw")
|
|
84
|
+
self.scale_t_miss_max = tk.Scale(
|
|
85
|
+
master=self, from_=0, to=100, resolution=1, orient="horizontal"
|
|
86
|
+
)
|
|
87
|
+
self.scale_t_miss_max.grid(row=4, column=1, sticky="w")
|
|
88
|
+
self.scale_t_miss_max.set(CONFIG["TRACK"]["IOU"]["T_MISS_MAX"])
|
|
89
|
+
# Overwrite
|
|
90
|
+
self.checkbutton_overwrite_var = tk.BooleanVar()
|
|
91
|
+
self.checkbutton_overwrite = tk.Checkbutton(
|
|
92
|
+
master=self, text="Overwrite", variable=self.checkbutton_overwrite_var
|
|
93
|
+
)
|
|
94
|
+
self.checkbutton_overwrite.grid(row=6, column=0, columnspan=2, sticky="w")
|
|
95
|
+
if CONFIG["TRACK"]["OVERWRITE"]:
|
|
96
|
+
self.checkbutton_overwrite.select()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class FrameRunTracking(FrameRun):
|
|
100
|
+
def __init__(self, **kwargs):
|
|
101
|
+
super().__init__(**kwargs)
|
|
102
|
+
self.button_run.bind("<ButtonRelease-1>", self.run)
|
|
103
|
+
if CONFIG["TRACK"]["RUN_CHAINED"]:
|
|
104
|
+
self.checkbutton_run_chained.select()
|
|
105
|
+
|
|
106
|
+
def run(self, event):
|
|
107
|
+
files = replace_filetype(
|
|
108
|
+
files=self.master.master.frame_files.get_tree_files(),
|
|
109
|
+
new_filetype=CONFIG["DEFAULT_FILETYPE"]["DETECT"],
|
|
110
|
+
)
|
|
111
|
+
files = get_files(
|
|
112
|
+
paths=files,
|
|
113
|
+
filetypes=[CONFIG["DEFAULT_FILETYPE"]["DETECT"]],
|
|
114
|
+
)
|
|
115
|
+
sigma_l = self.master.frame_options.scale_sigma_l.get()
|
|
116
|
+
sigma_h = self.master.frame_options.scale_sigma_h.get()
|
|
117
|
+
sigma_iou = self.master.frame_options.scale_sigma_iou.get()
|
|
118
|
+
t_min = self.master.frame_options.scale_t_min.get()
|
|
119
|
+
t_miss_max = self.master.frame_options.scale_t_miss_max.get()
|
|
120
|
+
overwrite = self.master.frame_options.checkbutton_overwrite_var.get()
|
|
121
|
+
log.info("Call track from GUI")
|
|
122
|
+
track(
|
|
123
|
+
paths=files,
|
|
124
|
+
sigma_l=sigma_l,
|
|
125
|
+
sigma_h=sigma_h,
|
|
126
|
+
sigma_iou=sigma_iou,
|
|
127
|
+
t_min=t_min,
|
|
128
|
+
t_miss_max=t_miss_max,
|
|
129
|
+
overwrite=overwrite,
|
|
130
|
+
)
|
|
131
|
+
self.master.master.frame_files.update_files_dict()
|