boris-behav-obs 8.16.6__py3-none-any.whl → 9.7.1__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.
- boris/__init__.py +1 -1
- boris/__main__.py +1 -1
- boris/about.py +24 -40
- boris/add_modifier.py +88 -80
- boris/add_modifier_ui.py +235 -131
- boris/advanced_event_filtering.py +23 -29
- boris/analysis_plugins/__init__.py +0 -0
- boris/analysis_plugins/_latency.py +59 -0
- boris/analysis_plugins/irr_cohen_kappa.py +109 -0
- boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
- boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
- boris/analysis_plugins/number_of_occurences.py +22 -0
- boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
- boris/analysis_plugins/time_budget.py +61 -0
- boris/behav_coding_map_creator.py +228 -229
- boris/behavior_binary_table.py +33 -50
- boris/behaviors_coding_map.py +17 -18
- boris/boris_cli.py +6 -25
- boris/cmd_arguments.py +12 -1
- boris/coding_pad.py +16 -34
- boris/config.py +101 -49
- boris/config_file.py +55 -64
- boris/connections.py +105 -58
- boris/converters.py +13 -37
- boris/converters_ui.py +187 -110
- boris/cooccurence.py +250 -0
- boris/core.py +2108 -1275
- boris/core_qrc.py +15892 -10829
- boris/core_ui.py +941 -806
- boris/db_functions.py +17 -42
- boris/dev.py +134 -0
- boris/dialog.py +461 -242
- boris/duration_widget.py +9 -14
- boris/edit_event.py +61 -31
- boris/edit_event_ui.py +208 -97
- boris/event_operations.py +405 -281
- boris/events_cursor.py +25 -17
- boris/events_snapshots.py +36 -82
- boris/exclusion_matrix.py +4 -9
- boris/export_events.py +180 -203
- boris/export_observation.py +60 -73
- boris/external_processes.py +123 -98
- boris/geometric_measurement.py +427 -218
- boris/gui_utilities.py +91 -14
- boris/image_overlay.py +4 -4
- boris/import_observations.py +190 -98
- boris/ipc_mpv.py +304 -0
- boris/irr.py +20 -57
- boris/latency.py +31 -24
- boris/measurement_widget.py +14 -18
- boris/media_file.py +17 -19
- boris/menu_options.py +16 -6
- boris/modifier_coding_map_creator.py +1013 -0
- boris/modifiers_coding_map.py +7 -9
- boris/mpv2.py +127 -36
- boris/observation.py +493 -210
- boris/observation_operations.py +1010 -391
- boris/observation_ui.py +573 -363
- boris/observations_list.py +51 -58
- boris/otx_parser.py +74 -68
- boris/param_panel.py +45 -59
- boris/param_panel_ui.py +254 -138
- boris/player_dock_widget.py +91 -56
- boris/plot_data_module.py +18 -53
- boris/plot_events.py +56 -153
- boris/plot_events_rt.py +16 -30
- boris/plot_spectrogram_rt.py +80 -56
- boris/plot_waveform_rt.py +23 -48
- boris/plugins.py +431 -0
- boris/portion/__init__.py +18 -8
- boris/portion/const.py +35 -18
- boris/portion/dict.py +5 -5
- boris/portion/func.py +2 -2
- boris/portion/interval.py +21 -41
- boris/portion/io.py +41 -32
- boris/preferences.py +298 -123
- boris/preferences_ui.py +664 -225
- boris/project.py +293 -270
- boris/project_functions.py +610 -537
- boris/project_import_export.py +204 -213
- boris/project_ui.py +673 -441
- boris/qrc_boris.py +6 -3
- boris/qrc_boris5.py +6 -3
- boris/select_modifiers.py +62 -90
- boris/select_observations.py +19 -197
- boris/select_subj_behav.py +67 -39
- boris/state_events.py +51 -33
- boris/subjects_pad.py +6 -8
- boris/synthetic_time_budget.py +25 -17
- boris/time_budget_functions.py +169 -169
- boris/time_budget_widget.py +71 -86
- boris/transitions.py +41 -41
- boris/utilities.py +562 -222
- boris/version.py +3 -3
- boris/video_equalizer.py +16 -14
- boris/video_equalizer_ui.py +199 -130
- boris/video_operations.py +78 -28
- boris/view_df.py +104 -0
- boris/view_df_ui.py +75 -0
- boris/write_event.py +240 -136
- boris_behav_obs-9.7.1.dist-info/METADATA +140 -0
- boris_behav_obs-9.7.1.dist-info/RECORD +109 -0
- {boris_behav_obs-8.16.6.dist-info → boris_behav_obs-9.7.1.dist-info}/WHEEL +1 -1
- boris_behav_obs-9.7.1.dist-info/entry_points.txt +2 -0
- boris/README.TXT +0 -22
- boris/add_modifier.ui +0 -323
- boris/converters.ui +0 -289
- boris/core.qrc +0 -37
- boris/core.ui +0 -1571
- boris/edit_event.ui +0 -233
- boris/icons/logo_eye.ico +0 -0
- boris/map_creator.py +0 -982
- boris/observation.ui +0 -814
- boris/param_panel.ui +0 -379
- boris/preferences.ui +0 -537
- boris/project.ui +0 -1074
- boris/vlc_local.py +0 -90
- boris_behav_obs-8.16.6.dist-info/LICENSE.TXT +0 -674
- boris_behav_obs-8.16.6.dist-info/METADATA +0 -134
- boris_behav_obs-8.16.6.dist-info/RECORD +0 -106
- boris_behav_obs-8.16.6.dist-info/entry_points.txt +0 -2
- {boris → boris_behav_obs-9.7.1.dist-info/licenses}/LICENSE.TXT +0 -0
- {boris_behav_obs-8.16.6.dist-info → boris_behav_obs-9.7.1.dist-info}/top_level.txt +0 -0
boris/geometric_measurement.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
BORIS
|
|
3
3
|
Behavioral Observation Research Interactive Software
|
|
4
|
-
Copyright 2012-
|
|
4
|
+
Copyright 2012-2025 Olivier Friard
|
|
5
5
|
|
|
6
6
|
This file is part of BORIS.
|
|
7
7
|
|
|
@@ -33,9 +33,9 @@ except ModuleNotFoundError:
|
|
|
33
33
|
flag_pyreadr_loaded = False
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
from
|
|
37
|
-
from
|
|
38
|
-
from
|
|
36
|
+
from PySide6.QtCore import QPoint, Qt, Signal, QEvent
|
|
37
|
+
from PySide6.QtGui import QColor, QPainter, QPolygon, QPixmap, QAction, QPen
|
|
38
|
+
from PySide6.QtWidgets import (
|
|
39
39
|
QApplication,
|
|
40
40
|
QCheckBox,
|
|
41
41
|
QFileDialog,
|
|
@@ -43,33 +43,38 @@ from PyQt5.QtWidgets import (
|
|
|
43
43
|
QLabel,
|
|
44
44
|
QLineEdit,
|
|
45
45
|
QMessageBox,
|
|
46
|
-
|
|
46
|
+
QTableWidget,
|
|
47
|
+
QTableWidgetItem,
|
|
47
48
|
QPushButton,
|
|
48
49
|
QRadioButton,
|
|
49
50
|
QVBoxLayout,
|
|
50
|
-
QWidget,
|
|
51
51
|
QColorDialog,
|
|
52
52
|
QSpacerItem,
|
|
53
53
|
QSizePolicy,
|
|
54
|
+
QDialog,
|
|
54
55
|
)
|
|
55
56
|
|
|
56
|
-
from typing import
|
|
57
|
+
from typing import List
|
|
57
58
|
|
|
58
59
|
from . import config as cfg
|
|
59
60
|
from . import dialog, menu_options
|
|
60
61
|
from . import utilities as util
|
|
61
62
|
|
|
62
63
|
|
|
63
|
-
class wgMeasurement(
|
|
64
|
+
class wgMeasurement(QDialog):
|
|
64
65
|
"""
|
|
65
66
|
widget for geometric measurements
|
|
66
67
|
"""
|
|
67
68
|
|
|
68
|
-
closeSignal =
|
|
69
|
-
send_event_signal =
|
|
69
|
+
closeSignal = Signal()
|
|
70
|
+
send_event_signal = Signal(QEvent)
|
|
71
|
+
reload_image_signal = Signal()
|
|
72
|
+
save_picture_signal = Signal(str)
|
|
70
73
|
mark_color: str = cfg.ACTIVE_MEASUREMENTS_COLOR
|
|
71
74
|
flag_saved = True # store if measurements are saved
|
|
72
75
|
draw_mem: dict = {}
|
|
76
|
+
mem_points: list = [] # memory of clicked points
|
|
77
|
+
mem_video: list = [] # memory of clicked points
|
|
73
78
|
|
|
74
79
|
def __init__(self):
|
|
75
80
|
super().__init__()
|
|
@@ -78,17 +83,22 @@ class wgMeasurement(QWidget):
|
|
|
78
83
|
|
|
79
84
|
vbox = QVBoxLayout(self)
|
|
80
85
|
|
|
81
|
-
self.
|
|
82
|
-
vbox.addWidget(self.
|
|
86
|
+
self.rb_point = QRadioButton("Point (left click)", clicked=self.rb_clicked)
|
|
87
|
+
vbox.addWidget(self.rb_point)
|
|
83
88
|
|
|
84
|
-
self.
|
|
85
|
-
vbox.addWidget(self.
|
|
89
|
+
self.rb_polyline = QRadioButton("Polyline (left click for vertices, right click to finish)", clicked=self.rb_clicked)
|
|
90
|
+
vbox.addWidget(self.rb_polyline)
|
|
86
91
|
|
|
87
|
-
self.
|
|
88
|
-
|
|
92
|
+
self.rb_polygon = QRadioButton(
|
|
93
|
+
"Polygon (left click for Polygon vertices, right click to close the polygon)", clicked=self.rb_clicked
|
|
94
|
+
)
|
|
95
|
+
vbox.addWidget(self.rb_polygon)
|
|
96
|
+
|
|
97
|
+
self.rb_angle = QRadioButton("Angle (vertex: left click, segments: right click)", clicked=self.rb_clicked)
|
|
98
|
+
vbox.addWidget(self.rb_angle)
|
|
89
99
|
|
|
90
|
-
self.
|
|
91
|
-
vbox.addWidget(self.
|
|
100
|
+
self.rb_oriented_angle = QRadioButton("Oriented angle (vertex: left click, segments: right click)", clicked=self.rb_clicked)
|
|
101
|
+
vbox.addWidget(self.rb_oriented_angle)
|
|
92
102
|
|
|
93
103
|
hbox = QHBoxLayout()
|
|
94
104
|
self.cbPersistentMeasurements = QCheckBox("Measurements are persistent")
|
|
@@ -100,6 +110,8 @@ class wgMeasurement(QWidget):
|
|
|
100
110
|
self.bt_color_chooser.setStyleSheet(f"QWidget {{background-color:{self.mark_color}}}")
|
|
101
111
|
hbox.addWidget(self.bt_color_chooser)
|
|
102
112
|
|
|
113
|
+
hbox.addItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
|
|
114
|
+
|
|
103
115
|
vbox.addLayout(hbox)
|
|
104
116
|
|
|
105
117
|
vbox.addWidget(QLabel("<b>Scale</b>"))
|
|
@@ -120,28 +132,70 @@ class wgMeasurement(QWidget):
|
|
|
120
132
|
hbox2.addWidget(self.lePx)
|
|
121
133
|
vbox.addLayout(hbox2)
|
|
122
134
|
|
|
123
|
-
self.pte =
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
self.pte.setLineWrapMode(QPlainTextEdit.NoWrap)
|
|
135
|
+
self.pte = QTableWidget()
|
|
136
|
+
self.pte.verticalHeader().hide()
|
|
137
|
+
|
|
127
138
|
# header
|
|
128
|
-
self.
|
|
139
|
+
self.measurements_header = [
|
|
140
|
+
"Player",
|
|
141
|
+
"media file name",
|
|
142
|
+
"Time",
|
|
143
|
+
"Frame index",
|
|
144
|
+
"Type of measurement",
|
|
145
|
+
"x",
|
|
146
|
+
"y",
|
|
147
|
+
"Distance",
|
|
148
|
+
"Area",
|
|
149
|
+
"Angle",
|
|
150
|
+
"Coordinates",
|
|
151
|
+
]
|
|
152
|
+
self.pte.setColumnCount(len(self.measurements_header))
|
|
153
|
+
self.pte.setHorizontalHeaderLabels(self.measurements_header)
|
|
154
|
+
|
|
155
|
+
self.pte.setSelectionBehavior(QTableWidget.SelectRows)
|
|
156
|
+
self.pte.setSelectionMode(QTableWidget.MultiSelection)
|
|
157
|
+
|
|
158
|
+
self.pte.setContextMenuPolicy(Qt.ActionsContextMenu)
|
|
159
|
+
|
|
160
|
+
self.action = QAction("Delete measurement")
|
|
161
|
+
self.action.triggered.connect(self.delete_measurement)
|
|
162
|
+
|
|
163
|
+
self.pte.addAction(self.action)
|
|
164
|
+
|
|
165
|
+
vbox.addWidget(self.pte)
|
|
129
166
|
|
|
130
167
|
self.status_lb = QLabel()
|
|
131
168
|
vbox.addWidget(self.status_lb)
|
|
132
169
|
|
|
133
170
|
hbox3 = QHBoxLayout()
|
|
134
171
|
hbox3.addItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
|
|
135
|
-
self.
|
|
136
|
-
hbox3.addWidget(self.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
172
|
+
self.pb_clear = QPushButton("Clear measurements", clicked=self.pbClear_clicked)
|
|
173
|
+
hbox3.addWidget(self.pb_clear)
|
|
174
|
+
|
|
175
|
+
self.pb_save_picture = QPushButton("Save current picture", clicked=self.pb_save_picture_clicked)
|
|
176
|
+
hbox3.addWidget(self.pb_save_picture)
|
|
177
|
+
|
|
178
|
+
# disabled for now
|
|
179
|
+
self.pb_save_all_pictures = QPushButton("Save all pictures", clicked=self.pb_save_all_pictures_clicked)
|
|
180
|
+
hbox3.addWidget(self.pb_save_all_pictures)
|
|
181
|
+
|
|
182
|
+
self.pb_save = QPushButton("Save results", clicked=self.pb_save_clicked)
|
|
183
|
+
hbox3.addWidget(self.pb_save)
|
|
184
|
+
self.pb_close = QPushButton(cfg.CLOSE, clicked=self.pbClose_clicked)
|
|
185
|
+
hbox3.addWidget(self.pb_close)
|
|
141
186
|
vbox.addLayout(hbox3)
|
|
142
187
|
|
|
143
188
|
self.installEventFilter(self)
|
|
144
189
|
|
|
190
|
+
def rb_clicked(self):
|
|
191
|
+
"""
|
|
192
|
+
radiobutton clicked, all points in memory are cleared
|
|
193
|
+
"""
|
|
194
|
+
self.mem_points = []
|
|
195
|
+
self.mem_video = []
|
|
196
|
+
|
|
197
|
+
self.reload_image_signal.emit()
|
|
198
|
+
|
|
145
199
|
def eventFilter(self, receiver, event):
|
|
146
200
|
"""
|
|
147
201
|
send event (if keypress) to main window
|
|
@@ -152,6 +206,51 @@ class wgMeasurement(QWidget):
|
|
|
152
206
|
else:
|
|
153
207
|
return False
|
|
154
208
|
|
|
209
|
+
def pb_save_picture_clicked(self):
|
|
210
|
+
"""
|
|
211
|
+
ask to save the current frame
|
|
212
|
+
"""
|
|
213
|
+
self.save_picture_signal.emit("current")
|
|
214
|
+
|
|
215
|
+
def pb_save_all_pictures_clicked(self):
|
|
216
|
+
"""
|
|
217
|
+
ask to save all frames with measurements
|
|
218
|
+
"""
|
|
219
|
+
self.save_picture_signal.emit("all")
|
|
220
|
+
|
|
221
|
+
def delete_measurement(self):
|
|
222
|
+
"""
|
|
223
|
+
delete the selected measurement(s)
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
if not self.pte.selectedItems():
|
|
227
|
+
return
|
|
228
|
+
|
|
229
|
+
rows_to_delete: list = []
|
|
230
|
+
for item in self.pte.selectedItems():
|
|
231
|
+
if item.row() not in rows_to_delete:
|
|
232
|
+
rows_to_delete.append(item.row())
|
|
233
|
+
|
|
234
|
+
elements_to_delete = []
|
|
235
|
+
for row in sorted(rows_to_delete, reverse=True):
|
|
236
|
+
player = int(self.pte.item(row, 0).text())
|
|
237
|
+
frame_idx = int(self.pte.item(row, 2).text())
|
|
238
|
+
obj_type = self.pte.item(row, 3).text()
|
|
239
|
+
coord = eval(self.pte.item(row, 9).text())
|
|
240
|
+
|
|
241
|
+
if frame_idx in self.draw_mem:
|
|
242
|
+
for idx, element in enumerate(self.draw_mem[frame_idx]):
|
|
243
|
+
if (element["player"] == player - 1) and (element["object_type"] == obj_type) and (element["coordinates"] == coord):
|
|
244
|
+
elements_to_delete.append((frame_idx, idx))
|
|
245
|
+
|
|
246
|
+
self.pte.removeRow(row)
|
|
247
|
+
self.pte.flag_saved = False
|
|
248
|
+
|
|
249
|
+
for frame_idx, idx in sorted(elements_to_delete, reverse=True):
|
|
250
|
+
self.draw_mem[frame_idx].pop(idx)
|
|
251
|
+
|
|
252
|
+
self.reload_image_signal.emit()
|
|
253
|
+
|
|
155
254
|
def choose_marks_color(self):
|
|
156
255
|
"""
|
|
157
256
|
show the color chooser dialog
|
|
@@ -180,13 +279,15 @@ class wgMeasurement(QWidget):
|
|
|
180
279
|
(cfg.YES, cfg.NO, cfg.CANCEL),
|
|
181
280
|
)
|
|
182
281
|
if response == cfg.YES:
|
|
183
|
-
self.
|
|
282
|
+
if self.pb_save_clicked():
|
|
283
|
+
event.ignore()
|
|
284
|
+
return
|
|
184
285
|
if response == cfg.CANCEL:
|
|
185
286
|
event.ignore()
|
|
186
287
|
return
|
|
187
288
|
|
|
188
|
-
self.flag_saved = True
|
|
189
|
-
self.draw_mem = {}
|
|
289
|
+
self.flag_saved: bool = True
|
|
290
|
+
self.draw_mem: dict = {}
|
|
190
291
|
self.closeSignal.emit()
|
|
191
292
|
|
|
192
293
|
def pbClear_clicked(self):
|
|
@@ -203,10 +304,18 @@ class wgMeasurement(QWidget):
|
|
|
203
304
|
if response == cfg.CANCEL:
|
|
204
305
|
return
|
|
205
306
|
|
|
206
|
-
self.draw_mem = {}
|
|
307
|
+
self.draw_mem: dict = {}
|
|
308
|
+
self.mem_points: list = []
|
|
309
|
+
self.mem_video: list = []
|
|
310
|
+
|
|
207
311
|
self.pte.clear()
|
|
312
|
+
self.pte.setColumnCount(len(self.measurements_header))
|
|
313
|
+
self.pte.setRowCount(0)
|
|
314
|
+
self.pte.setHorizontalHeaderLabels(self.measurements_header)
|
|
208
315
|
self.flag_saved = True
|
|
209
316
|
|
|
317
|
+
self.reload_image_signal.emit()
|
|
318
|
+
|
|
210
319
|
def pbClose_clicked(self):
|
|
211
320
|
"""
|
|
212
321
|
Close button
|
|
@@ -214,20 +323,28 @@ class wgMeasurement(QWidget):
|
|
|
214
323
|
logging.debug("close function")
|
|
215
324
|
self.close()
|
|
216
325
|
|
|
217
|
-
def
|
|
326
|
+
def pb_save_clicked(self) -> bool:
|
|
218
327
|
"""
|
|
219
|
-
Save measurements results
|
|
328
|
+
Save measurements results
|
|
220
329
|
"""
|
|
221
330
|
|
|
222
331
|
file_formats = [cfg.TSV, cfg.CSV, cfg.ODS, cfg.XLSX, cfg.HTML, cfg.PANDAS_DF]
|
|
223
332
|
if flag_pyreadr_loaded:
|
|
224
333
|
file_formats.append(cfg.RDS)
|
|
225
334
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
)
|
|
335
|
+
# default file name
|
|
336
|
+
media_file_list: list = []
|
|
337
|
+
for row in range(self.pte.rowCount()):
|
|
338
|
+
media_file_list.append(self.pte.item(row, 1).text())
|
|
339
|
+
|
|
340
|
+
if len(set(media_file_list)) == 1:
|
|
341
|
+
default_file_name = str(pl.Path(media_file_list[0]).with_suffix(""))
|
|
342
|
+
else:
|
|
343
|
+
default_file_name = ""
|
|
344
|
+
|
|
345
|
+
file_name, filter_ = QFileDialog().getSaveFileName(self, "Save geometric measurements", default_file_name, ";;".join(file_formats))
|
|
229
346
|
if not file_name:
|
|
230
|
-
return
|
|
347
|
+
return True
|
|
231
348
|
|
|
232
349
|
# add correct file extension if not present
|
|
233
350
|
if pl.Path(file_name).suffix != f".{cfg.FILE_NAME_SUFFIX[filter_]}":
|
|
@@ -235,14 +352,21 @@ class wgMeasurement(QWidget):
|
|
|
235
352
|
# check if file with new extension already exists
|
|
236
353
|
if pl.Path(file_name).is_file():
|
|
237
354
|
if (
|
|
238
|
-
dialog.MessageDialog(
|
|
239
|
-
cfg.programName, f"The file {file_name} already exists.", (cfg.CANCEL, cfg.OVERWRITE)
|
|
240
|
-
)
|
|
355
|
+
dialog.MessageDialog(cfg.programName, f"The file {file_name} already exists.", (cfg.CANCEL, cfg.OVERWRITE))
|
|
241
356
|
== cfg.CANCEL
|
|
242
357
|
):
|
|
243
|
-
return
|
|
358
|
+
return True
|
|
359
|
+
|
|
360
|
+
plain_text: str = "\t".join(self.measurements_header) + "\n"
|
|
361
|
+
for row in range(self.pte.rowCount()):
|
|
362
|
+
row_content: list = []
|
|
363
|
+
for col in range(self.pte.columnCount()):
|
|
364
|
+
row_content.append(self.pte.item(row, col).text())
|
|
365
|
+
plain_text += "\t".join(row_content) + "\n"
|
|
244
366
|
|
|
245
|
-
|
|
367
|
+
plain_text = plain_text[:-1]
|
|
368
|
+
|
|
369
|
+
df = pd.read_csv(io.StringIO(plain_text), sep="\t")
|
|
246
370
|
|
|
247
371
|
try:
|
|
248
372
|
if filter_ == cfg.ODS:
|
|
@@ -267,9 +391,11 @@ class wgMeasurement(QWidget):
|
|
|
267
391
|
|
|
268
392
|
except Exception:
|
|
269
393
|
QMessageBox.warning(self, cfg.programName, "An error occured during saving the measurement results")
|
|
394
|
+
return True
|
|
395
|
+
return False # everything OK
|
|
270
396
|
|
|
271
397
|
|
|
272
|
-
def show_widget(self):
|
|
398
|
+
def show_widget(self) -> None:
|
|
273
399
|
"""
|
|
274
400
|
active the geometric measurement widget
|
|
275
401
|
"""
|
|
@@ -306,9 +432,11 @@ def show_widget(self):
|
|
|
306
432
|
self.actionPlay.setEnabled(False)
|
|
307
433
|
|
|
308
434
|
self.measurement_w = wgMeasurement()
|
|
309
|
-
self.measurement_w.setWindowFlags(Qt.WindowStaysOnTopHint)
|
|
435
|
+
# self.measurement_w.setWindowFlags(Qt.WindowStaysOnTopHint)
|
|
310
436
|
self.measurement_w.closeSignal.connect(close_measurement_widget)
|
|
311
437
|
self.measurement_w.send_event_signal.connect(self.signal_from_widget)
|
|
438
|
+
self.measurement_w.reload_image_signal.connect(self.reload_frame)
|
|
439
|
+
self.measurement_w.save_picture_signal.connect(self.save_picture_with_measurements)
|
|
312
440
|
self.measurement_w.draw_mem = {}
|
|
313
441
|
|
|
314
442
|
self.measurement_w.show()
|
|
@@ -323,15 +451,24 @@ def draw_point(self, x: int, y: int, color: str, n_player: int = 0) -> None:
|
|
|
323
451
|
"""
|
|
324
452
|
draw point on frame-by-frame image
|
|
325
453
|
"""
|
|
454
|
+
|
|
455
|
+
logging.debug("draw_point function")
|
|
456
|
+
|
|
326
457
|
RADIUS = 6
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
painter
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
458
|
+
|
|
459
|
+
pixmap_copy = self.dw_player[n_player].frame_viewer.pixmap().copy()
|
|
460
|
+
|
|
461
|
+
painter = QPainter(pixmap_copy)
|
|
462
|
+
try:
|
|
463
|
+
painter.setPen(QPen(QColor(color), 1))
|
|
464
|
+
painter.drawEllipse(QPoint(x, y), RADIUS, RADIUS)
|
|
465
|
+
# cross inside circle
|
|
466
|
+
painter.drawLine(x - RADIUS, y, x + RADIUS, y)
|
|
467
|
+
painter.drawLine(x, y - RADIUS, x, y + RADIUS)
|
|
468
|
+
finally:
|
|
469
|
+
painter.end()
|
|
470
|
+
|
|
471
|
+
self.dw_player[n_player].frame_viewer.setPixmap(pixmap_copy)
|
|
335
472
|
self.dw_player[n_player].frame_viewer.update()
|
|
336
473
|
|
|
337
474
|
|
|
@@ -339,19 +476,30 @@ def draw_line(self, x1: int, y1: int, x2: int, y2: int, color: str, n_player: in
|
|
|
339
476
|
"""
|
|
340
477
|
draw line on frame-by-frame image
|
|
341
478
|
"""
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
painter
|
|
345
|
-
|
|
346
|
-
|
|
479
|
+
|
|
480
|
+
pixmap_copy = self.dw_player[n_player].frame_viewer.pixmap().copy()
|
|
481
|
+
painter = QPainter(pixmap_copy)
|
|
482
|
+
|
|
483
|
+
try:
|
|
484
|
+
painter.setPen(QColor(color))
|
|
485
|
+
painter.drawLine(x1, y1, x2, y2)
|
|
486
|
+
finally:
|
|
487
|
+
painter.end()
|
|
488
|
+
|
|
489
|
+
self.dw_player[n_player].frame_viewer.setPixmap(pixmap_copy)
|
|
347
490
|
self.dw_player[n_player].frame_viewer.update()
|
|
348
491
|
|
|
349
492
|
|
|
350
|
-
def append_results(self, results:
|
|
493
|
+
def append_results(self, results: list) -> None:
|
|
351
494
|
"""
|
|
352
|
-
append results to
|
|
495
|
+
append results to measurements table
|
|
353
496
|
"""
|
|
354
|
-
self.measurement_w.pte.
|
|
497
|
+
self.measurement_w.pte.setRowCount(self.measurement_w.pte.rowCount() + 1)
|
|
498
|
+
for idx, x in enumerate(results):
|
|
499
|
+
item = QTableWidgetItem()
|
|
500
|
+
item.setText(str(x))
|
|
501
|
+
item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
|
502
|
+
self.measurement_w.pte.setItem(self.measurement_w.pte.rowCount() - 1, idx, item)
|
|
355
503
|
|
|
356
504
|
|
|
357
505
|
def image_clicked(self, n_player: int, event) -> None:
|
|
@@ -375,7 +523,7 @@ def image_clicked(self, n_player: int, event) -> None:
|
|
|
375
523
|
self.mem_player = n_player
|
|
376
524
|
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.MEDIA:
|
|
377
525
|
if self.dw_player[n_player].player.estimated_frame_number is not None:
|
|
378
|
-
current_frame = self.dw_player[n_player].player.estimated_frame_number
|
|
526
|
+
current_frame = self.dw_player[n_player].player.estimated_frame_number
|
|
379
527
|
else:
|
|
380
528
|
current_frame = cfg.NA
|
|
381
529
|
elif self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.IMAGES:
|
|
@@ -383,35 +531,19 @@ def image_clicked(self, n_player: int, event) -> None:
|
|
|
383
531
|
|
|
384
532
|
if not (hasattr(self, "measurement_w") and (self.measurement_w is not None) and (self.measurement_w.isVisible())):
|
|
385
533
|
return
|
|
386
|
-
"""
|
|
387
|
-
QMessageBox.warning(
|
|
388
|
-
self,
|
|
389
|
-
cfg.programName,
|
|
390
|
-
"The Focus area function is not yet available in frame-by-frame mode.",
|
|
391
|
-
QMessageBox.Ok | QMessageBox.Default,
|
|
392
|
-
QMessageBox.NoButton,
|
|
393
|
-
)"""
|
|
394
534
|
|
|
395
535
|
x, y = event.pos().x(), event.pos().y()
|
|
396
536
|
|
|
537
|
+
logging.debug(f"clicked on {x} {y}")
|
|
538
|
+
|
|
397
539
|
# convert label coordinates in pixmap coordinates
|
|
398
|
-
pixmap_x = int(
|
|
399
|
-
|
|
400
|
-
)
|
|
401
|
-
pixmap_y = int(
|
|
402
|
-
y
|
|
403
|
-
- (self.dw_player[n_player].frame_viewer.height() - self.dw_player[n_player].frame_viewer.pixmap().height()) / 2
|
|
404
|
-
)
|
|
540
|
+
pixmap_x = int(x - (self.dw_player[n_player].frame_viewer.width() - self.dw_player[n_player].frame_viewer.pixmap().width()) / 2)
|
|
541
|
+
pixmap_y = int(y - (self.dw_player[n_player].frame_viewer.height() - self.dw_player[n_player].frame_viewer.pixmap().height()) / 2)
|
|
405
542
|
|
|
406
543
|
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.MEDIA:
|
|
407
544
|
# convert pixmap coordinates in video coordinates
|
|
408
|
-
x_video = round(
|
|
409
|
-
|
|
410
|
-
)
|
|
411
|
-
y_video = round(
|
|
412
|
-
(pixmap_y / self.dw_player[n_player].frame_viewer.pixmap().height())
|
|
413
|
-
* self.dw_player[n_player].player.height
|
|
414
|
-
)
|
|
545
|
+
x_video = round((pixmap_x / self.dw_player[n_player].frame_viewer.pixmap().width()) * self.dw_player[n_player].player.width)
|
|
546
|
+
y_video = round((pixmap_y / self.dw_player[n_player].frame_viewer.pixmap().height()) * self.dw_player[n_player].player.height)
|
|
415
547
|
elif self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.IMAGES:
|
|
416
548
|
original_width = QPixmap(self.images_list[self.image_idx]).size().width()
|
|
417
549
|
original_height = QPixmap(self.images_list[self.image_idx]).size().height()
|
|
@@ -427,117 +559,109 @@ def image_clicked(self, n_player: int, event) -> None:
|
|
|
427
559
|
|
|
428
560
|
self.measurement_w.status_lb.clear()
|
|
429
561
|
|
|
562
|
+
# media file name
|
|
563
|
+
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.MEDIA:
|
|
564
|
+
fn = self.dw_player[n_player - 1].player.playlist[self.dw_player[n_player - 1].player.playlist_pos]["filename"]
|
|
565
|
+
|
|
566
|
+
# check if media file path contained in media list
|
|
567
|
+
if [True for x in self.pj[cfg.OBSERVATIONS][self.observationId]["file"].values() if fn in x]:
|
|
568
|
+
media_file_name = self.dw_player[n_player - 1].player.playlist[self.dw_player[n_player - 1].player.playlist_pos]["filename"]
|
|
569
|
+
else:
|
|
570
|
+
media_file_name = str(pl.Path(fn).relative_to(pl.Path(self.projectFileName).parent))
|
|
571
|
+
|
|
572
|
+
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.IMAGES:
|
|
573
|
+
# check if pictures dir path is relative
|
|
574
|
+
if str(pl.Path(self.images_list[current_frame]).parent) not in self.pj[cfg.OBSERVATIONS][self.observationId].get(
|
|
575
|
+
cfg.DIRECTORIES_LIST, []
|
|
576
|
+
):
|
|
577
|
+
media_file_name = str(pl.Path(self.images_list[current_frame]).relative_to(pl.Path(self.projectFileName).parent))
|
|
578
|
+
else:
|
|
579
|
+
media_file_name = self.images_list[current_frame]
|
|
580
|
+
|
|
430
581
|
# point
|
|
431
|
-
if self.measurement_w.
|
|
432
|
-
if event.button() ==
|
|
582
|
+
if self.measurement_w.rb_point.isChecked():
|
|
583
|
+
if event.button() == Qt.LeftButton:
|
|
433
584
|
draw_point(self, pixmap_x, pixmap_y, self.measurement_w.mark_color, n_player)
|
|
434
585
|
if current_frame not in self.measurement_w.draw_mem:
|
|
435
586
|
self.measurement_w.draw_mem[current_frame] = []
|
|
436
587
|
|
|
437
588
|
self.measurement_w.draw_mem[current_frame].append(
|
|
438
|
-
|
|
589
|
+
{
|
|
590
|
+
"player": n_player,
|
|
591
|
+
"object_type": cfg.POINT_OBJECT,
|
|
592
|
+
"color": self.measurement_w.mark_color,
|
|
593
|
+
"coordinates": [(x_video, y_video)],
|
|
594
|
+
}
|
|
439
595
|
)
|
|
440
596
|
|
|
441
597
|
append_results(
|
|
442
598
|
self,
|
|
443
599
|
(
|
|
444
600
|
n_player + 1,
|
|
601
|
+
media_file_name,
|
|
445
602
|
f"{self.getLaps():.03f}",
|
|
446
603
|
current_frame,
|
|
447
|
-
|
|
604
|
+
cfg.POINT_OBJECT,
|
|
448
605
|
x_video,
|
|
449
606
|
y_video,
|
|
450
607
|
cfg.NA,
|
|
451
608
|
cfg.NA,
|
|
452
609
|
cfg.NA,
|
|
610
|
+
str([(x_video, y_video)]),
|
|
453
611
|
),
|
|
454
612
|
)
|
|
455
613
|
|
|
456
614
|
self.measurement_w.flag_saved = False
|
|
457
615
|
|
|
458
|
-
#
|
|
459
|
-
elif self.measurement_w.
|
|
460
|
-
if event.button() ==
|
|
461
|
-
draw_point(self, pixmap_x, pixmap_y, self.measurement_w.mark_color, n_player)
|
|
462
|
-
self.memx, self.memy = round(pixmap_x), round(pixmap_y)
|
|
463
|
-
self.memx_video, self.memy_video = round(x_video), round(y_video)
|
|
464
|
-
|
|
465
|
-
if event.button() == 2 and self.memx != -1 and self.memy != -1:
|
|
466
|
-
draw_point(self, pixmap_x, pixmap_y, self.measurement_w.mark_color, n_player)
|
|
467
|
-
draw_line(self, self.memx, self.memy, pixmap_x, pixmap_y, self.measurement_w.mark_color, n_player)
|
|
468
|
-
|
|
469
|
-
if current_frame not in self.measurement_w.draw_mem:
|
|
470
|
-
self.measurement_w.draw_mem[current_frame] = []
|
|
471
|
-
|
|
472
|
-
self.measurement_w.draw_mem[current_frame].append(
|
|
473
|
-
[n_player, "line", self.measurement_w.mark_color, self.memx_video, self.memy_video, x_video, y_video]
|
|
474
|
-
)
|
|
475
|
-
|
|
476
|
-
distance = ((x_video - self.memx_video) ** 2 + (y_video - self.memy_video) ** 2) ** 0.5
|
|
477
|
-
try:
|
|
478
|
-
distance = distance / float(self.measurement_w.lePx.text()) * float(self.measurement_w.leRef.text())
|
|
479
|
-
except Exception:
|
|
480
|
-
QMessageBox.critical(
|
|
481
|
-
None,
|
|
482
|
-
cfg.programName,
|
|
483
|
-
"Check reference and pixel values! Values must be numeric.",
|
|
484
|
-
QMessageBox.Ok | QMessageBox.Default,
|
|
485
|
-
QMessageBox.NoButton,
|
|
486
|
-
)
|
|
487
|
-
|
|
488
|
-
append_results(
|
|
489
|
-
self,
|
|
490
|
-
(
|
|
491
|
-
n_player + 1,
|
|
492
|
-
f"{self.getLaps():.03f}",
|
|
493
|
-
current_frame,
|
|
494
|
-
"Distance",
|
|
495
|
-
cfg.NA,
|
|
496
|
-
cfg.NA,
|
|
497
|
-
round(distance, 3),
|
|
498
|
-
cfg.NA,
|
|
499
|
-
cfg.NA,
|
|
500
|
-
),
|
|
501
|
-
)
|
|
502
|
-
|
|
503
|
-
self.measurement_w.flag_saved = False
|
|
504
|
-
self.memx, self.memy = -1, -1
|
|
505
|
-
|
|
506
|
-
# angle 1st clic -> vertex
|
|
507
|
-
elif self.measurement_w.rbAngle.isChecked():
|
|
508
|
-
if event.button() == 1: # left for vertex
|
|
616
|
+
# angle
|
|
617
|
+
elif self.measurement_w.rb_angle.isChecked() or self.measurement_w.rb_oriented_angle.isChecked():
|
|
618
|
+
if event.button() == Qt.LeftButton:
|
|
509
619
|
draw_point(self, pixmap_x, pixmap_y, self.measurement_w.mark_color, n_player)
|
|
510
|
-
self.
|
|
511
|
-
self.mem_video = [(x_video, y_video)]
|
|
620
|
+
self.measurement_w.mem_points = [(pixmap_x, pixmap_y)]
|
|
621
|
+
self.measurement_w.mem_video = [(x_video, y_video)]
|
|
512
622
|
|
|
513
|
-
if event.button() ==
|
|
623
|
+
if event.button() == Qt.RightButton and len(self.measurement_w.mem_points):
|
|
514
624
|
draw_point(self, pixmap_x, pixmap_y, self.measurement_w.mark_color, n_player)
|
|
515
625
|
draw_line(
|
|
516
626
|
self,
|
|
517
|
-
self.
|
|
518
|
-
self.
|
|
627
|
+
self.measurement_w.mem_points[0][0],
|
|
628
|
+
self.measurement_w.mem_points[0][1],
|
|
519
629
|
pixmap_x,
|
|
520
630
|
pixmap_y,
|
|
521
631
|
self.measurement_w.mark_color,
|
|
522
632
|
n_player,
|
|
523
633
|
)
|
|
524
634
|
|
|
525
|
-
self.
|
|
526
|
-
self.mem_video.append((x_video, y_video))
|
|
635
|
+
self.measurement_w.mem_points.append((pixmap_x, pixmap_y))
|
|
636
|
+
self.measurement_w.mem_video.append((x_video, y_video))
|
|
637
|
+
|
|
638
|
+
if len(self.measurement_w.mem_points) == 3:
|
|
639
|
+
if self.measurement_w.rb_angle.isChecked():
|
|
640
|
+
angle = util.angle(self.measurement_w.mem_points[0], self.measurement_w.mem_points[1], self.measurement_w.mem_points[2])
|
|
641
|
+
object_type = cfg.ANGLE_OBJECT
|
|
642
|
+
else: # oriented angle
|
|
643
|
+
angle = util.oriented_angle(
|
|
644
|
+
self.measurement_w.mem_points[0], self.measurement_w.mem_points[1], self.measurement_w.mem_points[2]
|
|
645
|
+
)
|
|
646
|
+
object_type = cfg.ORIENTED_ANGLE_OBJECT
|
|
527
647
|
|
|
528
|
-
if len(self.memPoints) == 3:
|
|
529
648
|
append_results(
|
|
530
649
|
self,
|
|
531
650
|
(
|
|
532
651
|
n_player + 1,
|
|
652
|
+
media_file_name,
|
|
533
653
|
f"{self.getLaps():.03f}",
|
|
534
654
|
current_frame,
|
|
535
|
-
|
|
655
|
+
object_type,
|
|
536
656
|
cfg.NA,
|
|
537
657
|
cfg.NA,
|
|
538
658
|
cfg.NA,
|
|
539
659
|
cfg.NA,
|
|
540
|
-
round(
|
|
660
|
+
round(
|
|
661
|
+
angle,
|
|
662
|
+
1,
|
|
663
|
+
),
|
|
664
|
+
str(self.measurement_w.mem_video),
|
|
541
665
|
),
|
|
542
666
|
)
|
|
543
667
|
|
|
@@ -546,63 +670,133 @@ def image_clicked(self, n_player: int, event) -> None:
|
|
|
546
670
|
self.measurement_w.draw_mem[current_frame] = []
|
|
547
671
|
|
|
548
672
|
self.measurement_w.draw_mem[current_frame].append(
|
|
549
|
-
|
|
673
|
+
{
|
|
674
|
+
"player": n_player,
|
|
675
|
+
"object_type": object_type,
|
|
676
|
+
"color": self.measurement_w.mark_color,
|
|
677
|
+
"coordinates": self.measurement_w.mem_video,
|
|
678
|
+
}
|
|
550
679
|
)
|
|
551
680
|
|
|
552
|
-
self.
|
|
681
|
+
self.measurement_w.mem_points, self.measurement_w.mem_video = [], []
|
|
553
682
|
|
|
554
|
-
#
|
|
555
|
-
elif self.measurement_w.
|
|
556
|
-
if event.button() ==
|
|
557
|
-
draw_point(self, pixmap_x, pixmap_y, self.measurement_w.mark_color)
|
|
558
|
-
if len(self.
|
|
683
|
+
# polygon
|
|
684
|
+
elif self.measurement_w.rb_polygon.isChecked():
|
|
685
|
+
if event.button() == Qt.LeftButton:
|
|
686
|
+
draw_point(self, pixmap_x, pixmap_y, self.measurement_w.mark_color, n_player)
|
|
687
|
+
if len(self.measurement_w.mem_points):
|
|
559
688
|
draw_line(
|
|
560
689
|
self,
|
|
561
|
-
self.
|
|
562
|
-
self.
|
|
690
|
+
self.measurement_w.mem_points[-1][0],
|
|
691
|
+
self.measurement_w.mem_points[-1][1],
|
|
563
692
|
pixmap_x,
|
|
564
693
|
pixmap_y,
|
|
565
694
|
self.measurement_w.mark_color,
|
|
566
695
|
n_player,
|
|
567
696
|
)
|
|
568
|
-
self.
|
|
569
|
-
self.mem_video.append((x_video, y_video))
|
|
697
|
+
self.measurement_w.mem_points.append((pixmap_x, pixmap_y))
|
|
698
|
+
self.measurement_w.mem_video.append((x_video, y_video))
|
|
570
699
|
|
|
571
|
-
if event.button() ==
|
|
572
|
-
|
|
700
|
+
if event.button() == Qt.RightButton and len(self.measurement_w.mem_points) >= 2:
|
|
701
|
+
# close polygon
|
|
573
702
|
draw_line(
|
|
574
703
|
self,
|
|
575
|
-
self.
|
|
576
|
-
self.
|
|
577
|
-
|
|
578
|
-
|
|
704
|
+
self.measurement_w.mem_points[-1][0],
|
|
705
|
+
self.measurement_w.mem_points[-1][1],
|
|
706
|
+
self.measurement_w.mem_points[0][0],
|
|
707
|
+
self.measurement_w.mem_points[0][1],
|
|
579
708
|
self.measurement_w.mark_color,
|
|
580
709
|
n_player,
|
|
581
710
|
)
|
|
582
|
-
self.
|
|
583
|
-
|
|
711
|
+
if current_frame not in self.measurement_w.draw_mem:
|
|
712
|
+
self.measurement_w.draw_mem[current_frame] = []
|
|
584
713
|
|
|
585
|
-
|
|
586
|
-
|
|
714
|
+
self.measurement_w.draw_mem[current_frame].append(
|
|
715
|
+
{
|
|
716
|
+
"player": n_player,
|
|
717
|
+
"object_type": cfg.POLYGON_OBJECT,
|
|
718
|
+
"color": self.measurement_w.mark_color,
|
|
719
|
+
"coordinates": self.measurement_w.mem_video,
|
|
720
|
+
}
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
area = util.polygon_area(self.measurement_w.mem_video)
|
|
724
|
+
try:
|
|
725
|
+
area = area / (float(self.measurement_w.lePx.text()) ** 2) * float(self.measurement_w.leRef.text()) ** 2
|
|
726
|
+
except Exception:
|
|
727
|
+
QMessageBox.critical(
|
|
728
|
+
None,
|
|
729
|
+
cfg.programName,
|
|
730
|
+
"Check reference and pixel values! Values must be numeric.",
|
|
731
|
+
QMessageBox.Ok | QMessageBox.Default,
|
|
732
|
+
QMessageBox.NoButton,
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
length = util.polyline_length(self.measurement_w.mem_video)
|
|
736
|
+
try:
|
|
737
|
+
length = length / float(self.measurement_w.lePx.text()) * float(self.measurement_w.leRef.text())
|
|
738
|
+
except Exception:
|
|
739
|
+
QMessageBox.critical(
|
|
740
|
+
None,
|
|
741
|
+
cfg.programName,
|
|
742
|
+
"Check reference and pixel values! Values must be numeric.",
|
|
743
|
+
QMessageBox.Ok | QMessageBox.Default,
|
|
744
|
+
QMessageBox.NoButton,
|
|
745
|
+
)
|
|
746
|
+
|
|
747
|
+
append_results(
|
|
587
748
|
self,
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
749
|
+
(
|
|
750
|
+
n_player + 1,
|
|
751
|
+
media_file_name,
|
|
752
|
+
f"{self.getLaps():.03f}",
|
|
753
|
+
current_frame,
|
|
754
|
+
cfg.POLYGON_OBJECT,
|
|
755
|
+
cfg.NA,
|
|
756
|
+
cfg.NA,
|
|
757
|
+
round(length, 1),
|
|
758
|
+
round(area, 1),
|
|
759
|
+
cfg.NA,
|
|
760
|
+
str(self.measurement_w.mem_video),
|
|
761
|
+
),
|
|
594
762
|
)
|
|
595
|
-
area = util.polygon_area(self.mem_video)
|
|
596
763
|
|
|
764
|
+
self.measurement_w.flag_saved = False
|
|
765
|
+
self.measurement_w.mem_points, self.measurement_w.mem_video = [], []
|
|
766
|
+
|
|
767
|
+
# polyline
|
|
768
|
+
elif self.measurement_w.rb_polyline.isChecked():
|
|
769
|
+
if event.button() == Qt.LeftButton:
|
|
770
|
+
draw_point(self, pixmap_x, pixmap_y, self.measurement_w.mark_color, n_player)
|
|
771
|
+
if len(self.measurement_w.mem_points):
|
|
772
|
+
draw_line(
|
|
773
|
+
self,
|
|
774
|
+
self.measurement_w.mem_points[-1][0],
|
|
775
|
+
self.measurement_w.mem_points[-1][1],
|
|
776
|
+
pixmap_x,
|
|
777
|
+
pixmap_y,
|
|
778
|
+
self.measurement_w.mark_color,
|
|
779
|
+
n_player,
|
|
780
|
+
)
|
|
781
|
+
self.measurement_w.mem_points.append((pixmap_x, pixmap_y))
|
|
782
|
+
self.measurement_w.mem_video.append((x_video, y_video))
|
|
783
|
+
|
|
784
|
+
if event.button() == Qt.RightButton:
|
|
597
785
|
if current_frame not in self.measurement_w.draw_mem:
|
|
598
786
|
self.measurement_w.draw_mem[current_frame] = []
|
|
599
787
|
|
|
600
788
|
self.measurement_w.draw_mem[current_frame].append(
|
|
601
|
-
|
|
789
|
+
{
|
|
790
|
+
"player": n_player,
|
|
791
|
+
"object_type": cfg.POLYLINE_OBJECT,
|
|
792
|
+
"color": self.measurement_w.mark_color,
|
|
793
|
+
"coordinates": self.measurement_w.mem_video,
|
|
794
|
+
}
|
|
602
795
|
)
|
|
603
796
|
|
|
797
|
+
length = util.polyline_length(self.measurement_w.mem_video)
|
|
604
798
|
try:
|
|
605
|
-
|
|
799
|
+
length = length / float(self.measurement_w.lePx.text()) * float(self.measurement_w.leRef.text())
|
|
606
800
|
except Exception:
|
|
607
801
|
QMessageBox.critical(
|
|
608
802
|
None,
|
|
@@ -616,19 +810,20 @@ def image_clicked(self, n_player: int, event) -> None:
|
|
|
616
810
|
self,
|
|
617
811
|
(
|
|
618
812
|
n_player + 1,
|
|
813
|
+
media_file_name,
|
|
619
814
|
f"{self.getLaps():.03f}",
|
|
620
815
|
current_frame,
|
|
621
|
-
|
|
816
|
+
cfg.POLYLINE_OBJECT,
|
|
622
817
|
cfg.NA,
|
|
623
818
|
cfg.NA,
|
|
819
|
+
round(length, 1),
|
|
624
820
|
cfg.NA,
|
|
625
|
-
round(area, 3),
|
|
626
821
|
cfg.NA,
|
|
822
|
+
str(self.measurement_w.mem_video),
|
|
627
823
|
),
|
|
628
824
|
)
|
|
629
|
-
|
|
825
|
+
self.measurement_w.mem_points, self.measurement_w.mem_video = [], []
|
|
630
826
|
self.measurement_w.flag_saved = False
|
|
631
|
-
self.memPoints, self.mem_video = [], []
|
|
632
827
|
|
|
633
828
|
else:
|
|
634
829
|
self.measurement_w.status_lb.setText("<b>Choose a measurement type!</b>")
|
|
@@ -673,7 +868,7 @@ def redraw_measurements(self):
|
|
|
673
868
|
for idx, dw in enumerate(self.dw_player):
|
|
674
869
|
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.MEDIA:
|
|
675
870
|
if dw.player.estimated_frame_number is not None:
|
|
676
|
-
current_frame = dw.player.estimated_frame_number
|
|
871
|
+
current_frame = dw.player.estimated_frame_number
|
|
677
872
|
else:
|
|
678
873
|
current_frame = cfg.NA
|
|
679
874
|
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.IMAGES:
|
|
@@ -682,45 +877,59 @@ def redraw_measurements(self):
|
|
|
682
877
|
for frame in self.measurement_w.draw_mem:
|
|
683
878
|
for element in self.measurement_w.draw_mem[frame]:
|
|
684
879
|
if frame == current_frame:
|
|
685
|
-
|
|
880
|
+
elements_color = element["color"]
|
|
686
881
|
else:
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
if element[
|
|
690
|
-
if element[
|
|
691
|
-
x, y = scale_coord(element[
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
draw_line(self, x1, y1, x2, y2,
|
|
698
|
-
draw_point(self, x1, y1,
|
|
699
|
-
draw_point(self, x2, y2,
|
|
700
|
-
|
|
701
|
-
if element[
|
|
702
|
-
x1, y1 = scale_coord(element[
|
|
703
|
-
x2, y2 = scale_coord(element[
|
|
704
|
-
x3, y3 = scale_coord(element[
|
|
705
|
-
draw_line(self, x1, y1, x2, y2,
|
|
706
|
-
draw_line(self, x1, y1, x3, y3,
|
|
707
|
-
draw_point(self, x1, y1,
|
|
708
|
-
draw_point(self, x2, y2,
|
|
709
|
-
draw_point(self, x3, y3,
|
|
710
|
-
|
|
711
|
-
if element[
|
|
882
|
+
elements_color = cfg.PASSIVE_MEASUREMENTS_COLOR
|
|
883
|
+
|
|
884
|
+
if element["player"] == idx:
|
|
885
|
+
if element["object_type"] == cfg.POINT_OBJECT:
|
|
886
|
+
x, y = scale_coord(element["coordinates"][0])
|
|
887
|
+
draw_point(self, int(x), int(y), elements_color, n_player=idx)
|
|
888
|
+
|
|
889
|
+
if element["object_type"] == cfg.SEGMENT_OBJECT:
|
|
890
|
+
x1, y1 = scale_coord(element["coordinates"][0])
|
|
891
|
+
x2, y2 = scale_coord(element["coordinates"][1])
|
|
892
|
+
draw_line(self, x1, y1, x2, y2, elements_color, n_player=idx)
|
|
893
|
+
draw_point(self, x1, y1, elements_color, n_player=idx)
|
|
894
|
+
draw_point(self, x2, y2, elements_color, n_player=idx)
|
|
895
|
+
|
|
896
|
+
if element["object_type"] in (cfg.ANGLE_OBJECT, cfg.ORIENTED_ANGLE_OBJECT):
|
|
897
|
+
x1, y1 = scale_coord(element["coordinates"][0])
|
|
898
|
+
x2, y2 = scale_coord(element["coordinates"][1])
|
|
899
|
+
x3, y3 = scale_coord(element["coordinates"][2])
|
|
900
|
+
draw_line(self, x1, y1, x2, y2, elements_color, n_player=idx)
|
|
901
|
+
draw_line(self, x1, y1, x3, y3, elements_color, n_player=idx)
|
|
902
|
+
draw_point(self, x1, y1, elements_color, n_player=idx)
|
|
903
|
+
draw_point(self, x2, y2, elements_color, n_player=idx)
|
|
904
|
+
draw_point(self, x3, y3, elements_color, n_player=idx)
|
|
905
|
+
|
|
906
|
+
if element["object_type"] == cfg.POLYGON_OBJECT:
|
|
712
907
|
polygon = QPolygon()
|
|
713
|
-
|
|
714
|
-
for x, y in element[3]:
|
|
908
|
+
for x, y in element["coordinates"]:
|
|
715
909
|
x, y = scale_coord([x, y])
|
|
716
910
|
polygon.append(QPoint(x, y))
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
painter
|
|
720
|
-
|
|
721
|
-
|
|
911
|
+
|
|
912
|
+
pixmap_copy = self.dw_player[idx].frame_viewer.pixmap().copy()
|
|
913
|
+
painter = QPainter(pixmap_copy)
|
|
914
|
+
|
|
915
|
+
try:
|
|
916
|
+
painter.setPen(QColor(elements_color))
|
|
917
|
+
painter.drawPolygon(polygon)
|
|
918
|
+
finally:
|
|
919
|
+
painter.end()
|
|
920
|
+
|
|
921
|
+
self.dw_player[idx].frame_viewer.setPixmap(pixmap_copy)
|
|
922
|
+
|
|
722
923
|
dw.frame_viewer.update()
|
|
723
924
|
|
|
925
|
+
if element["object_type"] == cfg.POLYLINE_OBJECT:
|
|
926
|
+
for idx1, p1 in enumerate(element["coordinates"][:-1]):
|
|
927
|
+
x1, y1 = scale_coord(p1)
|
|
928
|
+
p2 = element["coordinates"][idx1 + 1]
|
|
929
|
+
x2, y2 = scale_coord(p2)
|
|
930
|
+
|
|
931
|
+
draw_line(self, x1, y1, x2, y2, elements_color, n_player=idx)
|
|
932
|
+
|
|
724
933
|
|
|
725
934
|
if __name__ == "__main__":
|
|
726
935
|
import sys
|