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,462 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OTVision tool to select reference points for transformation
|
|
3
|
+
from pixel to world coordinates
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# Copyright (C) 2022 OpenTrafficCam Contributors
|
|
7
|
+
# <https://github.com/OpenTrafficCam
|
|
8
|
+
# <team@opentrafficcam.org>
|
|
9
|
+
#
|
|
10
|
+
# This program is free software: you can redistribute it and/or modify
|
|
11
|
+
# it under the terms of the GNU General Public License as published by
|
|
12
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
13
|
+
# (at your option) any later version.
|
|
14
|
+
#
|
|
15
|
+
# This program is distributed in the hope that it will be useful,
|
|
16
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
17
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
18
|
+
# GNU General Public License for more details.
|
|
19
|
+
#
|
|
20
|
+
# You should have received a copy of the GNU General Public License
|
|
21
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import logging
|
|
26
|
+
import tkinter as tk
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from random import randrange
|
|
29
|
+
from tkinter import ttk
|
|
30
|
+
from tkinter.simpledialog import Dialog
|
|
31
|
+
from typing import Any, Union
|
|
32
|
+
|
|
33
|
+
import cv2
|
|
34
|
+
|
|
35
|
+
from OTVision.helpers.files import is_image, is_video
|
|
36
|
+
from OTVision.helpers.log import LOGGER_NAME
|
|
37
|
+
|
|
38
|
+
log = logging.getLogger(LOGGER_NAME)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ReferencePointsPicker:
|
|
42
|
+
"""Class to pick reference points in pixel coordinates for transform subpackage.
|
|
43
|
+
|
|
44
|
+
Instructions for using the gui:
|
|
45
|
+
Hold left mouse button Find spot for reference point with magnifier
|
|
46
|
+
Release left mouse button Mark reference point
|
|
47
|
+
CTRL + Z Unmark last reference point
|
|
48
|
+
CTRL + Y Remark last reference point
|
|
49
|
+
CTRL + S Save image with markers
|
|
50
|
+
CTRL + N Show next frame (disabled if image was loaded)
|
|
51
|
+
CTRL + R Show random frame (disabled if image was loaded)
|
|
52
|
+
ESC Close window and return reference points
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
file: Path,
|
|
58
|
+
title: str = "Reference Points Picker",
|
|
59
|
+
popup_root: Union[tk.Tk, None] = None,
|
|
60
|
+
):
|
|
61
|
+
# Attributes
|
|
62
|
+
self.title = title
|
|
63
|
+
self.left_button_down = False
|
|
64
|
+
self.refpts: dict = {}
|
|
65
|
+
self.historic_refpts: dict = {}
|
|
66
|
+
self.file = file
|
|
67
|
+
self.refpts_file = file.with_suffix(".otrfpts")
|
|
68
|
+
self.image = None
|
|
69
|
+
self.video = None
|
|
70
|
+
self.popup_root = popup_root
|
|
71
|
+
|
|
72
|
+
# Initial method calls
|
|
73
|
+
self.update_base_image()
|
|
74
|
+
self.show()
|
|
75
|
+
|
|
76
|
+
# ----------- Handle OpenCV gui -----------
|
|
77
|
+
|
|
78
|
+
def update_base_image(self, random_frame: bool = False):
|
|
79
|
+
log.debug("update base image")
|
|
80
|
+
if is_image(self.file):
|
|
81
|
+
self.get_image()
|
|
82
|
+
self.video = None
|
|
83
|
+
elif is_video(self.file):
|
|
84
|
+
self.get_frame_frome_video(random_frame=random_frame)
|
|
85
|
+
else:
|
|
86
|
+
raise ValueError("The file path provided has to be an image or a video")
|
|
87
|
+
self.draw_refpts()
|
|
88
|
+
|
|
89
|
+
def get_image(self):
|
|
90
|
+
"""Set base_image with image from a file
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
ImageWontOpenError: If image wont open
|
|
94
|
+
"""
|
|
95
|
+
try:
|
|
96
|
+
self.base_image = cv2.imread(self.image_file)
|
|
97
|
+
except Exception as e:
|
|
98
|
+
raise ImageWontOpenError(
|
|
99
|
+
f"Error opening this image file: {self.image_file}"
|
|
100
|
+
) from e
|
|
101
|
+
|
|
102
|
+
def get_frame_frome_video(self, random_frame: bool):
|
|
103
|
+
"""Set base image with next or random frame from a video
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
random_frame (bool): If true gets a random frame from the video.
|
|
107
|
+
If false gets the next frame of the video.
|
|
108
|
+
|
|
109
|
+
Raises:
|
|
110
|
+
VideoWontOpenError: If video wont open
|
|
111
|
+
FrameNotAvailableError: If frame cannt be read
|
|
112
|
+
"""
|
|
113
|
+
if not self.video:
|
|
114
|
+
self.video = cv2.VideoCapture(str(self.file))
|
|
115
|
+
if not self.video.isOpened():
|
|
116
|
+
raise VideoWontOpenError(f"Error opening this video file: {self.file}")
|
|
117
|
+
total_frames = int(self.video.get(cv2.CAP_PROP_FRAME_COUNT))
|
|
118
|
+
if random_frame:
|
|
119
|
+
frame_nr = randrange(0, total_frames)
|
|
120
|
+
self.video.set(cv2.CAP_PROP_POS_FRAMES, frame_nr)
|
|
121
|
+
else:
|
|
122
|
+
frame_nr = self.video.get(cv2.CAP_PROP_POS_FRAMES)
|
|
123
|
+
if frame_nr >= total_frames:
|
|
124
|
+
self.video.set(cv2.CAP_PROP_POS_FRAMES, 0)
|
|
125
|
+
ret, self.base_image = self.video.read()
|
|
126
|
+
if not ret:
|
|
127
|
+
raise FrameNotAvailableError("Video Frame cannot be read correctly")
|
|
128
|
+
|
|
129
|
+
def update_image(self):
|
|
130
|
+
"""Show the current image"""
|
|
131
|
+
if not self.left_button_down:
|
|
132
|
+
log.debug("update image")
|
|
133
|
+
cv2.imshow(self.title, self.image)
|
|
134
|
+
|
|
135
|
+
def show(self):
|
|
136
|
+
"""Enter OpenCV event loop"""
|
|
137
|
+
cv2.imshow(self.title, self.image)
|
|
138
|
+
cv2.setMouseCallback(self.title, self.handle_mouse_events)
|
|
139
|
+
|
|
140
|
+
while True:
|
|
141
|
+
# wait for a key press to close the window (0 = indefinite loop)
|
|
142
|
+
key = cv2.waitKey(-1) & 0xFF # BUG: #150 on mac
|
|
143
|
+
|
|
144
|
+
window_visible = (
|
|
145
|
+
cv2.getWindowProperty(self.title, cv2.WND_PROP_VISIBLE) >= 1
|
|
146
|
+
)
|
|
147
|
+
if key == 27 or not window_visible:
|
|
148
|
+
break # Exit loop and collapse OpenCV window
|
|
149
|
+
else:
|
|
150
|
+
self.handle_keystrokes(key)
|
|
151
|
+
|
|
152
|
+
cv2.destroyAllWindows()
|
|
153
|
+
|
|
154
|
+
def handle_mouse_events(
|
|
155
|
+
self,
|
|
156
|
+
event: int,
|
|
157
|
+
x_px: int,
|
|
158
|
+
y_px: int,
|
|
159
|
+
flags, # TODO: Correct type hint
|
|
160
|
+
params: Any, # TODO: Correct type hint
|
|
161
|
+
):
|
|
162
|
+
"""Read the current mouse position with a left click and write it
|
|
163
|
+
to the end of the array refpkte and increases the counter by one"""
|
|
164
|
+
if event == cv2.EVENT_LBUTTONUP:
|
|
165
|
+
self.left_button_down = False
|
|
166
|
+
self.add_refpt(x_px, y_px)
|
|
167
|
+
elif event == cv2.EVENT_LBUTTONDOWN:
|
|
168
|
+
self.left_button_down = True
|
|
169
|
+
elif event == cv2.EVENT_MOUSEMOVE:
|
|
170
|
+
if self.left_button_down:
|
|
171
|
+
self.draw_magnifier(x_px, y_px)
|
|
172
|
+
elif event == cv2.EVENT_MOUSEWHEEL:
|
|
173
|
+
if self.left_button_down:
|
|
174
|
+
self.zoom_magnifier(x_px, y_px)
|
|
175
|
+
|
|
176
|
+
def handle_keystrokes(self, key: int):
|
|
177
|
+
if key == 26: # ctrl + z
|
|
178
|
+
self.undo_last_refpt()
|
|
179
|
+
elif key == 25: # ctrl + y
|
|
180
|
+
self.redo_last_refpt()
|
|
181
|
+
elif key == 14: # ctrl + n
|
|
182
|
+
self.update_base_image(random_frame=False)
|
|
183
|
+
elif key == 18: # ctrl + r
|
|
184
|
+
self.update_base_image(random_frame=True)
|
|
185
|
+
elif key == 23: # ctrl+w
|
|
186
|
+
self._write_refpts()
|
|
187
|
+
|
|
188
|
+
# ----------- Handle connections -----------
|
|
189
|
+
|
|
190
|
+
def add_refpt(self, x_px: int, y_px: int):
|
|
191
|
+
"""Add a reference point
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
x_px (int): x coordinate [px] (horizontal distance from the left)
|
|
195
|
+
y_px (int): y coordinate [px] (vertical distance from the top)
|
|
196
|
+
"""
|
|
197
|
+
log.debug("add refpt")
|
|
198
|
+
new_refpt_px = {"x_px": x_px, "y_px": y_px}
|
|
199
|
+
self.draw_refpts(temp_refpt=new_refpt_px)
|
|
200
|
+
if new_refpt_utm := self.get_refpt_utm_from_popup():
|
|
201
|
+
new_refpt = {**new_refpt_px, **new_refpt_utm}
|
|
202
|
+
self.refpts = self.append_refpt(refpts=self.refpts, new_refpt=new_refpt)
|
|
203
|
+
self._log_refpts()
|
|
204
|
+
self.draw_refpts()
|
|
205
|
+
|
|
206
|
+
def undo_last_refpt(self):
|
|
207
|
+
"""Remove the last reference point"""
|
|
208
|
+
if self.refpts:
|
|
209
|
+
log.debug("undo last refpt")
|
|
210
|
+
self.refpts, undone_refpt = self.pop_refpt(refpts=self.refpts)
|
|
211
|
+
log.debug(self.refpts)
|
|
212
|
+
log.debug(undone_refpt)
|
|
213
|
+
self.historic_refpts = self.append_refpt(
|
|
214
|
+
refpts=self.historic_refpts, new_refpt=undone_refpt
|
|
215
|
+
)
|
|
216
|
+
self.draw_refpts()
|
|
217
|
+
self._log_refpts()
|
|
218
|
+
else:
|
|
219
|
+
log.debug("refpts empty, cannot undo last refpt")
|
|
220
|
+
|
|
221
|
+
def redo_last_refpt(self):
|
|
222
|
+
"""Again add the last removed reference point"""
|
|
223
|
+
if self.historic_refpts:
|
|
224
|
+
log.debug("redo last refpt")
|
|
225
|
+
self.historic_refpts, redone_refpt = self.pop_refpt(
|
|
226
|
+
refpts=self.historic_refpts
|
|
227
|
+
)
|
|
228
|
+
self.append_refpt(refpts=self.refpts, new_refpt=redone_refpt)
|
|
229
|
+
self.draw_refpts()
|
|
230
|
+
self._log_refpts()
|
|
231
|
+
else:
|
|
232
|
+
log.debug("no historc refpts, cannot redo last refpt")
|
|
233
|
+
|
|
234
|
+
def get_refpt_utm_from_popup(self) -> dict:
|
|
235
|
+
"""Open a popup to enter utm lat and lon coordinates of the reference point
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
dict: dict of utm coordinates including hemisphere and utm zone
|
|
239
|
+
"""
|
|
240
|
+
# TODO: Refactor to smaller methods
|
|
241
|
+
if not self.popup_root:
|
|
242
|
+
self.popup_root = tk.Tk()
|
|
243
|
+
self.popup_root.overrideredirect(True)
|
|
244
|
+
self.popup_root.withdraw()
|
|
245
|
+
refpt_utm_correct = False
|
|
246
|
+
try_again = False
|
|
247
|
+
while not refpt_utm_correct:
|
|
248
|
+
# Get utm part of refpt from pupup
|
|
249
|
+
new_refpt_utm = DialogUTMCoordinates(
|
|
250
|
+
parent=self.popup_root, try_again=try_again
|
|
251
|
+
).coords_utm
|
|
252
|
+
# Check utm part of refpt
|
|
253
|
+
log.debug(new_refpt_utm)
|
|
254
|
+
if new_refpt_utm: # BUG: ValueError: could not convert string to float: ''
|
|
255
|
+
# BUG: Cancel cancels whole refpt
|
|
256
|
+
# (even pixel coordinates, otherwise stuck in infinite loop)
|
|
257
|
+
hemisphere_correct = isinstance(
|
|
258
|
+
new_refpt_utm["hemisphere"], str
|
|
259
|
+
) and new_refpt_utm["hemisphere"] in ["N", "S"]
|
|
260
|
+
zone_correct = isinstance(new_refpt_utm["zone_utm"], int) and (
|
|
261
|
+
1 <= new_refpt_utm["zone_utm"] <= 60
|
|
262
|
+
)
|
|
263
|
+
lon_utm_correct = isinstance(
|
|
264
|
+
new_refpt_utm["lon_utm"], (float, int)
|
|
265
|
+
) and (100000 <= new_refpt_utm["lon_utm"] <= 900000)
|
|
266
|
+
lat_utm_correct = isinstance(
|
|
267
|
+
new_refpt_utm["lat_utm"], (float, int)
|
|
268
|
+
) and (0 <= new_refpt_utm["lat_utm"] <= 10000000)
|
|
269
|
+
if (
|
|
270
|
+
hemisphere_correct
|
|
271
|
+
and zone_correct
|
|
272
|
+
and lon_utm_correct
|
|
273
|
+
and lat_utm_correct
|
|
274
|
+
):
|
|
275
|
+
refpt_utm_correct = True
|
|
276
|
+
else:
|
|
277
|
+
try_again = True
|
|
278
|
+
else:
|
|
279
|
+
break
|
|
280
|
+
return new_refpt_utm
|
|
281
|
+
|
|
282
|
+
# ----------- Edit refpts dict -----------
|
|
283
|
+
|
|
284
|
+
def append_refpt(self, refpts: dict, new_refpt: dict) -> dict:
|
|
285
|
+
"""Append a reference point to a dict of reference points.
|
|
286
|
+
Used for valid reference points as well as for deleted reference points.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
refpts (dict): old reference points
|
|
290
|
+
new_refpt (dict): new reference point
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
dict: updated reference points
|
|
294
|
+
"""
|
|
295
|
+
log.debug("append refpts")
|
|
296
|
+
new_idx = len(refpts) + 1
|
|
297
|
+
refpts[new_idx] = new_refpt
|
|
298
|
+
return refpts
|
|
299
|
+
|
|
300
|
+
def pop_refpt(self, refpts: dict) -> tuple[dict, dict]:
|
|
301
|
+
"""Remove reference point from a dict of reference points.
|
|
302
|
+
Used for valid reference points as well as for deleted reference points.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
refpts (dict): old reference points
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
dict: new reference points
|
|
309
|
+
dict: removed reference point
|
|
310
|
+
"""
|
|
311
|
+
log.debug("pop refpts")
|
|
312
|
+
popped_refpt = refpts.popitem()[1]
|
|
313
|
+
return refpts, popped_refpt
|
|
314
|
+
|
|
315
|
+
# ----------- Draw on image -----------
|
|
316
|
+
|
|
317
|
+
def draw_magnifier(self, x_px, y_px):
|
|
318
|
+
log.debug("#TODO: draw magnifier")
|
|
319
|
+
|
|
320
|
+
def zoom_magnifier(self, x_px, y_px):
|
|
321
|
+
log.debug(" # TODO: zoom magnifier")
|
|
322
|
+
|
|
323
|
+
def draw_refpts(self, temp_refpt: Union[dict, None] = None):
|
|
324
|
+
"""Draw all the reference points an self.image
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
temp_refpt (dict, optional): possible reference point from left button down.
|
|
328
|
+
Defaults to None.
|
|
329
|
+
"""
|
|
330
|
+
FONT = cv2.FONT_ITALIC
|
|
331
|
+
FONT_SIZE_REL = 0.02
|
|
332
|
+
MARKER_SIZE_REL = 0.02
|
|
333
|
+
FONT_SIZE_PX = round(self.base_image.shape[0] * FONT_SIZE_REL)
|
|
334
|
+
MARKER_SIZE_PX = round(self.base_image.shape[0] * MARKER_SIZE_REL)
|
|
335
|
+
log.debug("draw refpts")
|
|
336
|
+
self.image = self.base_image.copy()
|
|
337
|
+
refpts = self.refpts.copy()
|
|
338
|
+
if temp_refpt:
|
|
339
|
+
refpts = self.append_refpt(refpts=refpts, new_refpt=temp_refpt)
|
|
340
|
+
for idx, refpt in refpts.items():
|
|
341
|
+
x_px = refpt["x_px"]
|
|
342
|
+
y_px = refpt["y_px"]
|
|
343
|
+
marker_bottom = (x_px, y_px + MARKER_SIZE_PX)
|
|
344
|
+
marker_top = (x_px, y_px - MARKER_SIZE_PX)
|
|
345
|
+
marker_left = (x_px - MARKER_SIZE_PX, y_px)
|
|
346
|
+
marker_right = (x_px + MARKER_SIZE_PX, y_px)
|
|
347
|
+
cv2.line(self.image, marker_bottom, marker_top, (0, 0, 255), 1)
|
|
348
|
+
cv2.line(self.image, marker_left, marker_right, (0, 0, 255), 1)
|
|
349
|
+
cv2.putText(
|
|
350
|
+
self.image,
|
|
351
|
+
str(idx),
|
|
352
|
+
marker_top,
|
|
353
|
+
FONT,
|
|
354
|
+
cv2.getFontScaleFromHeight(FONT, FONT_SIZE_PX),
|
|
355
|
+
(0, 0, 255),
|
|
356
|
+
1,
|
|
357
|
+
cv2.LINE_AA,
|
|
358
|
+
)
|
|
359
|
+
self.update_image()
|
|
360
|
+
|
|
361
|
+
# ----------- Complete job -----------
|
|
362
|
+
|
|
363
|
+
def _write_refpts(self):
|
|
364
|
+
"""Write reference points to a json file"""
|
|
365
|
+
log.debug("write refpts")
|
|
366
|
+
# Only necessary if the reference points picker is used as standalone tool
|
|
367
|
+
with open(self.refpts_file, "w") as f:
|
|
368
|
+
json.dump(self.refpts, f, indent=4)
|
|
369
|
+
|
|
370
|
+
def write_image(self):
|
|
371
|
+
"""This is done via on-board resources of OpenCV"""
|
|
372
|
+
# Alternative: Own implementation using cv2.imwrite()
|
|
373
|
+
# Problem: Ctrl+S shortcut is occupied by this
|
|
374
|
+
pass
|
|
375
|
+
|
|
376
|
+
# ----------- Helper functions -----------
|
|
377
|
+
|
|
378
|
+
def _log_refpts(self):
|
|
379
|
+
"""Helper for logging"""
|
|
380
|
+
log.debug("-------------------------")
|
|
381
|
+
log.debug("refpts:")
|
|
382
|
+
log.debug(self.refpts)
|
|
383
|
+
log.debug("-------------------------")
|
|
384
|
+
log.debug("historic refpts:")
|
|
385
|
+
log.debug(self.historic_refpts)
|
|
386
|
+
log.debug("-------------------------")
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
class DialogUTMCoordinates(Dialog):
|
|
390
|
+
"""Modificatoin of tkinter.simpledialog.Dialog to get utm coordinates and zone and
|
|
391
|
+
hemisphere of sreference point
|
|
392
|
+
"""
|
|
393
|
+
|
|
394
|
+
def __init__(self, try_again=False, **kwargs):
|
|
395
|
+
self.coords_utm = None
|
|
396
|
+
self.try_again = try_again
|
|
397
|
+
super().__init__(**kwargs)
|
|
398
|
+
|
|
399
|
+
def body(
|
|
400
|
+
self, master: tk.Frame
|
|
401
|
+
): # sourcery skip: assign-if-exp, swap-if-expression
|
|
402
|
+
# Labels
|
|
403
|
+
if not self.try_again:
|
|
404
|
+
text_provide = "Provide reference point\nin UTM coordinates!"
|
|
405
|
+
else:
|
|
406
|
+
text_provide = "No valid UTM!\nProvide reference point\nin UTM coordinates!"
|
|
407
|
+
tk.Label(master, text=text_provide).grid(row=0, columnspan=3)
|
|
408
|
+
tk.Label(master, text="UTM zone:").grid(row=1, sticky="e")
|
|
409
|
+
tk.Label(master, text="Longitude (E):").grid(row=2, sticky="e")
|
|
410
|
+
tk.Label(master, text="Latitude (N):").grid(row=3, sticky="e")
|
|
411
|
+
|
|
412
|
+
# Zone
|
|
413
|
+
zones: list = list(range(1, 61))
|
|
414
|
+
self.combo_zone = ttk.Combobox(
|
|
415
|
+
master=master, values=zones, width=3, state="readonly"
|
|
416
|
+
)
|
|
417
|
+
self.combo_zone.set(32)
|
|
418
|
+
self.combo_zone.grid(row=1, column=1, sticky="ew")
|
|
419
|
+
|
|
420
|
+
# Hemisphere
|
|
421
|
+
hemispheres = ["N", "S"]
|
|
422
|
+
self.combo_hemisphere = ttk.Combobox(
|
|
423
|
+
master=master, values=hemispheres, width=2, state="readonly"
|
|
424
|
+
)
|
|
425
|
+
self.combo_hemisphere.set("N")
|
|
426
|
+
self.combo_hemisphere.grid(row=1, column=2, sticky="ew")
|
|
427
|
+
|
|
428
|
+
self.input_lon = tk.Entry(master, width=5)
|
|
429
|
+
self.input_lat = tk.Entry(master, width=5)
|
|
430
|
+
self.input_lon.grid(row=2, column=1, columnspan=2, sticky="ew")
|
|
431
|
+
self.input_lat.grid(row=3, column=1, columnspan=2, sticky="ew")
|
|
432
|
+
|
|
433
|
+
return self.combo_zone # initial focus
|
|
434
|
+
|
|
435
|
+
def apply(self):
|
|
436
|
+
self.zone = int(self.combo_zone.get())
|
|
437
|
+
self.hemisphere = self.combo_hemisphere.get()
|
|
438
|
+
self.lon = float(self.input_lon.get())
|
|
439
|
+
self.lat = float(self.input_lat.get())
|
|
440
|
+
self.coords_utm = {
|
|
441
|
+
"hemisphere": self.hemisphere,
|
|
442
|
+
"zone_utm": self.zone,
|
|
443
|
+
"lon_utm": self.lon,
|
|
444
|
+
"lat_utm": self.lat,
|
|
445
|
+
}
|
|
446
|
+
log.debug(self.coords_utm)
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
class NoPathError(Exception):
|
|
450
|
+
pass
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
class ImageWontOpenError(Exception):
|
|
454
|
+
pass
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
class VideoWontOpenError(Exception):
|
|
458
|
+
pass
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
class FrameNotAvailableError(Exception):
|
|
462
|
+
pass
|