boris-behav-obs 8.12__py3-none-any.whl → 9.7.6__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.
Potentially problematic release.
This version of boris-behav-obs might be problematic. Click here for more details.
- boris/__init__.py +1 -1
- boris/__main__.py +1 -1
- boris/about.py +28 -39
- boris/add_modifier.py +122 -109
- boris/add_modifier_ui.py +239 -135
- boris/advanced_event_filtering.py +81 -45
- 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 +42 -49
- boris/config.py +141 -65
- boris/config_file.py +58 -67
- boris/connections.py +107 -61
- boris/converters.py +13 -37
- boris/converters_ui.py +187 -110
- boris/cooccurence.py +250 -0
- boris/core.py +2373 -1786
- boris/core_qrc.py +15895 -10743
- boris/core_ui.py +943 -798
- boris/db_functions.py +17 -42
- boris/dev.py +109 -8
- boris/dialog.py +482 -236
- boris/duration_widget.py +9 -14
- boris/edit_event.py +61 -31
- boris/edit_event_ui.py +208 -97
- boris/event_operations.py +408 -293
- boris/events_cursor.py +25 -17
- boris/events_snapshots.py +36 -82
- boris/exclusion_matrix.py +4 -9
- boris/export_events.py +184 -223
- boris/export_observation.py +74 -100
- boris/external_processes.py +123 -98
- boris/geometric_measurement.py +644 -290
- boris/gui_utilities.py +91 -14
- boris/image_overlay.py +4 -4
- boris/import_observations.py +190 -98
- boris/ipc_mpv.py +325 -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 +17 -6
- boris/modifier_coding_map_creator.py +1013 -0
- boris/modifiers_coding_map.py +7 -9
- boris/mpv.py +1 -0
- boris/mpv2.py +732 -705
- boris/observation.py +533 -221
- boris/observation_operations.py +1025 -390
- boris/observation_ui.py +572 -362
- boris/observations_list.py +71 -53
- boris/otx_parser.py +74 -68
- boris/param_panel.py +31 -16
- boris/param_panel_ui.py +254 -138
- boris/player_dock_widget.py +90 -60
- boris/plot_data_module.py +25 -33
- boris/plot_events.py +127 -90
- boris/plot_events_rt.py +17 -31
- boris/plot_spectrogram_rt.py +95 -30
- boris/plot_waveform_rt.py +32 -21
- 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 +306 -83
- boris/preferences_ui.py +684 -227
- boris/project.py +448 -293
- boris/project_functions.py +671 -238
- boris/project_import_export.py +213 -222
- boris/project_ui.py +674 -438
- boris/qrc_boris.py +6 -3
- boris/qrc_boris5.py +6 -3
- boris/select_modifiers.py +74 -48
- boris/select_observations.py +20 -198
- boris/select_subj_behav.py +67 -39
- boris/state_events.py +52 -35
- boris/subjects_pad.py +6 -9
- boris/synthetic_time_budget.py +45 -28
- boris/time_budget_functions.py +171 -171
- boris/time_budget_widget.py +84 -114
- boris/transitions.py +41 -47
- boris/utilities.py +627 -236
- boris/version.py +3 -3
- boris/video_equalizer.py +16 -14
- boris/video_equalizer_ui.py +199 -130
- boris/video_operations.py +95 -29
- boris/view_df.py +104 -0
- boris/view_df_ui.py +75 -0
- boris/write_event.py +538 -0
- boris_behav_obs-9.7.6.dist-info/METADATA +139 -0
- boris_behav_obs-9.7.6.dist-info/RECORD +109 -0
- {boris_behav_obs-8.12.dist-info → boris_behav_obs-9.7.6.dist-info}/WHEEL +1 -1
- boris_behav_obs-9.7.6.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 -36
- boris/core.ui +0 -1556
- boris/edit_event.ui +0 -233
- boris/icons/logo_eye.ico +0 -0
- boris/map_creator.py +0 -850
- boris/observation.ui +0 -814
- boris/param_panel.ui +0 -379
- boris/preferences.ui +0 -537
- boris/project.ui +0 -1069
- boris/project_server.py +0 -236
- boris/vlc.py +0 -10343
- boris/vlc_local.py +0 -90
- boris_behav_obs-8.12.dist-info/LICENSE.TXT +0 -674
- boris_behav_obs-8.12.dist-info/METADATA +0 -128
- boris_behav_obs-8.12.dist-info/RECORD +0 -108
- boris_behav_obs-8.12.dist-info/entry_points.txt +0 -3
- {boris → boris_behav_obs-9.7.6.dist-info/licenses}/LICENSE.TXT +0 -0
- {boris_behav_obs-8.12.dist-info → boris_behav_obs-9.7.6.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
|
|
|
@@ -21,10 +21,21 @@ This file is part of BORIS.
|
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
23
|
import logging
|
|
24
|
+
import io
|
|
25
|
+
import pandas as pd
|
|
26
|
+
import pathlib as pl
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
try:
|
|
29
|
+
import pyreadr
|
|
30
|
+
|
|
31
|
+
flag_pyreadr_loaded = True
|
|
32
|
+
except ModuleNotFoundError:
|
|
33
|
+
flag_pyreadr_loaded = False
|
|
34
|
+
|
|
35
|
+
|
|
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 (
|
|
28
39
|
QApplication,
|
|
29
40
|
QCheckBox,
|
|
30
41
|
QFileDialog,
|
|
@@ -32,31 +43,38 @@ from PyQt5.QtWidgets import (
|
|
|
32
43
|
QLabel,
|
|
33
44
|
QLineEdit,
|
|
34
45
|
QMessageBox,
|
|
35
|
-
|
|
46
|
+
QTableWidget,
|
|
47
|
+
QTableWidgetItem,
|
|
36
48
|
QPushButton,
|
|
37
49
|
QRadioButton,
|
|
38
50
|
QVBoxLayout,
|
|
39
|
-
QWidget,
|
|
40
51
|
QColorDialog,
|
|
41
52
|
QSpacerItem,
|
|
42
53
|
QSizePolicy,
|
|
54
|
+
QDialog,
|
|
43
55
|
)
|
|
44
56
|
|
|
57
|
+
from typing import List
|
|
58
|
+
|
|
45
59
|
from . import config as cfg
|
|
46
60
|
from . import dialog, menu_options
|
|
47
61
|
from . import utilities as util
|
|
48
62
|
|
|
49
63
|
|
|
50
|
-
class wgMeasurement(
|
|
64
|
+
class wgMeasurement(QDialog):
|
|
51
65
|
"""
|
|
52
66
|
widget for geometric measurements
|
|
53
67
|
"""
|
|
54
68
|
|
|
55
|
-
closeSignal =
|
|
56
|
-
send_event_signal =
|
|
69
|
+
closeSignal = Signal()
|
|
70
|
+
send_event_signal = Signal(QEvent)
|
|
71
|
+
reload_image_signal = Signal()
|
|
72
|
+
save_picture_signal = Signal(str)
|
|
57
73
|
mark_color: str = cfg.ACTIVE_MEASUREMENTS_COLOR
|
|
58
74
|
flag_saved = True # store if measurements are saved
|
|
59
75
|
draw_mem: dict = {}
|
|
76
|
+
mem_points: list = [] # memory of clicked points
|
|
77
|
+
mem_video: list = [] # memory of clicked points
|
|
60
78
|
|
|
61
79
|
def __init__(self):
|
|
62
80
|
super().__init__()
|
|
@@ -65,17 +83,22 @@ class wgMeasurement(QWidget):
|
|
|
65
83
|
|
|
66
84
|
vbox = QVBoxLayout(self)
|
|
67
85
|
|
|
68
|
-
self.
|
|
69
|
-
vbox.addWidget(self.
|
|
86
|
+
self.rb_point = QRadioButton("Point (left click)", clicked=self.rb_clicked)
|
|
87
|
+
vbox.addWidget(self.rb_point)
|
|
88
|
+
|
|
89
|
+
self.rb_polyline = QRadioButton("Polyline (left click for vertices, right click to finish)", clicked=self.rb_clicked)
|
|
90
|
+
vbox.addWidget(self.rb_polyline)
|
|
70
91
|
|
|
71
|
-
self.
|
|
72
|
-
|
|
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)
|
|
73
96
|
|
|
74
|
-
self.
|
|
75
|
-
vbox.addWidget(self.
|
|
97
|
+
self.rb_angle = QRadioButton("Angle (vertex: left click, segments: right click)", clicked=self.rb_clicked)
|
|
98
|
+
vbox.addWidget(self.rb_angle)
|
|
76
99
|
|
|
77
|
-
self.
|
|
78
|
-
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)
|
|
79
102
|
|
|
80
103
|
hbox = QHBoxLayout()
|
|
81
104
|
self.cbPersistentMeasurements = QCheckBox("Measurements are persistent")
|
|
@@ -87,33 +110,58 @@ class wgMeasurement(QWidget):
|
|
|
87
110
|
self.bt_color_chooser.setStyleSheet(f"QWidget {{background-color:{self.mark_color}}}")
|
|
88
111
|
hbox.addWidget(self.bt_color_chooser)
|
|
89
112
|
|
|
113
|
+
hbox.addItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
|
|
114
|
+
|
|
90
115
|
vbox.addLayout(hbox)
|
|
91
116
|
|
|
92
117
|
vbox.addWidget(QLabel("<b>Scale</b>"))
|
|
93
118
|
|
|
94
119
|
hbox1 = QHBoxLayout()
|
|
95
|
-
|
|
96
120
|
self.lbRef = QLabel("Reference")
|
|
97
121
|
hbox1.addWidget(self.lbRef)
|
|
98
|
-
|
|
99
122
|
self.lbPx = QLabel("Pixels")
|
|
100
123
|
hbox1.addWidget(self.lbPx)
|
|
101
|
-
|
|
102
124
|
vbox.addLayout(hbox1)
|
|
103
125
|
|
|
104
126
|
hbox2 = QHBoxLayout()
|
|
105
|
-
|
|
106
127
|
self.leRef = QLineEdit()
|
|
107
128
|
self.leRef.setText("1")
|
|
108
129
|
hbox2.addWidget(self.leRef)
|
|
109
|
-
|
|
110
130
|
self.lePx = QLineEdit()
|
|
111
131
|
self.lePx.setText("1")
|
|
112
132
|
hbox2.addWidget(self.lePx)
|
|
113
|
-
|
|
114
133
|
vbox.addLayout(hbox2)
|
|
115
134
|
|
|
116
|
-
self.pte =
|
|
135
|
+
self.pte = QTableWidget()
|
|
136
|
+
self.pte.verticalHeader().hide()
|
|
137
|
+
|
|
138
|
+
# header
|
|
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
|
+
|
|
117
165
|
vbox.addWidget(self.pte)
|
|
118
166
|
|
|
119
167
|
self.status_lb = QLabel()
|
|
@@ -121,20 +169,33 @@ class wgMeasurement(QWidget):
|
|
|
121
169
|
|
|
122
170
|
hbox3 = QHBoxLayout()
|
|
123
171
|
hbox3.addItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
|
|
172
|
+
self.pb_clear = QPushButton("Clear measurements", clicked=self.pbClear_clicked)
|
|
173
|
+
hbox3.addWidget(self.pb_clear)
|
|
124
174
|
|
|
125
|
-
self.
|
|
126
|
-
hbox3.addWidget(self.
|
|
175
|
+
self.pb_save_picture = QPushButton("Save current picture", clicked=self.pb_save_picture_clicked)
|
|
176
|
+
hbox3.addWidget(self.pb_save_picture)
|
|
127
177
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
self.pbClose = QPushButton("Close", clicked=self.pbClose_clicked)
|
|
132
|
-
hbox3.addWidget(self.pbClose)
|
|
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)
|
|
133
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)
|
|
134
186
|
vbox.addLayout(hbox3)
|
|
135
187
|
|
|
136
188
|
self.installEventFilter(self)
|
|
137
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
|
+
|
|
138
199
|
def eventFilter(self, receiver, event):
|
|
139
200
|
"""
|
|
140
201
|
send event (if keypress) to main window
|
|
@@ -145,6 +206,51 @@ class wgMeasurement(QWidget):
|
|
|
145
206
|
else:
|
|
146
207
|
return False
|
|
147
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
|
+
|
|
148
254
|
def choose_marks_color(self):
|
|
149
255
|
"""
|
|
150
256
|
show the color chooser dialog
|
|
@@ -152,6 +258,7 @@ class wgMeasurement(QWidget):
|
|
|
152
258
|
cd = QColorDialog()
|
|
153
259
|
cd.setWindowFlags(Qt.WindowStaysOnTopHint)
|
|
154
260
|
cd.setOptions(QColorDialog.ShowAlphaChannel | QColorDialog.DontUseNativeDialog)
|
|
261
|
+
cd.setCurrentColor(QColor(self.mark_color))
|
|
155
262
|
|
|
156
263
|
if cd.exec_():
|
|
157
264
|
new_color = cd.currentColor()
|
|
@@ -163,20 +270,24 @@ class wgMeasurement(QWidget):
|
|
|
163
270
|
Intercept the close event to check if measurements are saved
|
|
164
271
|
"""
|
|
165
272
|
|
|
273
|
+
logging.debug("close event")
|
|
274
|
+
|
|
166
275
|
if not self.flag_saved:
|
|
167
276
|
response = dialog.MessageDialog(
|
|
168
277
|
cfg.programName,
|
|
169
278
|
"The current measurements are not saved. Do you want to save the measurement results before closing?",
|
|
170
|
-
|
|
279
|
+
(cfg.YES, cfg.NO, cfg.CANCEL),
|
|
171
280
|
)
|
|
172
281
|
if response == cfg.YES:
|
|
173
|
-
self.
|
|
282
|
+
if self.pb_save_clicked():
|
|
283
|
+
event.ignore()
|
|
284
|
+
return
|
|
174
285
|
if response == cfg.CANCEL:
|
|
175
286
|
event.ignore()
|
|
176
287
|
return
|
|
177
288
|
|
|
178
|
-
self.flag_saved = True
|
|
179
|
-
self.draw_mem = {}
|
|
289
|
+
self.flag_saved: bool = True
|
|
290
|
+
self.draw_mem: dict = {}
|
|
180
291
|
self.closeSignal.emit()
|
|
181
292
|
|
|
182
293
|
def pbClear_clicked(self):
|
|
@@ -188,61 +299,130 @@ class wgMeasurement(QWidget):
|
|
|
188
299
|
response = dialog.MessageDialog(
|
|
189
300
|
cfg.programName,
|
|
190
301
|
"Confirm clearing",
|
|
191
|
-
|
|
302
|
+
(cfg.YES, cfg.CANCEL),
|
|
192
303
|
)
|
|
193
304
|
if response == cfg.CANCEL:
|
|
194
305
|
return
|
|
195
306
|
|
|
196
|
-
self.draw_mem = {}
|
|
307
|
+
self.draw_mem: dict = {}
|
|
308
|
+
self.mem_points: list = []
|
|
309
|
+
self.mem_video: list = []
|
|
310
|
+
|
|
197
311
|
self.pte.clear()
|
|
312
|
+
self.pte.setColumnCount(len(self.measurements_header))
|
|
313
|
+
self.pte.setRowCount(0)
|
|
314
|
+
self.pte.setHorizontalHeaderLabels(self.measurements_header)
|
|
198
315
|
self.flag_saved = True
|
|
199
316
|
|
|
317
|
+
self.reload_image_signal.emit()
|
|
318
|
+
|
|
200
319
|
def pbClose_clicked(self):
|
|
201
320
|
"""
|
|
202
321
|
Close button
|
|
203
322
|
"""
|
|
323
|
+
logging.debug("close function")
|
|
204
324
|
self.close()
|
|
205
325
|
|
|
206
|
-
def
|
|
326
|
+
def pb_save_clicked(self) -> bool:
|
|
207
327
|
"""
|
|
208
|
-
Save measurements results
|
|
328
|
+
Save measurements results
|
|
209
329
|
"""
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
330
|
+
|
|
331
|
+
file_formats = [cfg.TSV, cfg.CSV, cfg.ODS, cfg.XLSX, cfg.HTML, cfg.PANDAS_DF]
|
|
332
|
+
if flag_pyreadr_loaded:
|
|
333
|
+
file_formats.append(cfg.RDS)
|
|
334
|
+
|
|
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(""))
|
|
221
342
|
else:
|
|
222
|
-
|
|
343
|
+
default_file_name = ""
|
|
344
|
+
|
|
345
|
+
file_name, filter_ = QFileDialog().getSaveFileName(self, "Save geometric measurements", default_file_name, ";;".join(file_formats))
|
|
346
|
+
if not file_name:
|
|
347
|
+
return True
|
|
348
|
+
|
|
349
|
+
# add correct file extension if not present
|
|
350
|
+
if pl.Path(file_name).suffix != f".{cfg.FILE_NAME_SUFFIX[filter_]}":
|
|
351
|
+
file_name = str(pl.Path(file_name)) + "." + cfg.FILE_NAME_SUFFIX[filter_]
|
|
352
|
+
# check if file with new extension already exists
|
|
353
|
+
if pl.Path(file_name).is_file():
|
|
354
|
+
if (
|
|
355
|
+
dialog.MessageDialog(cfg.programName, f"The file {file_name} already exists.", (cfg.CANCEL, cfg.OVERWRITE))
|
|
356
|
+
== cfg.CANCEL
|
|
357
|
+
):
|
|
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"
|
|
366
|
+
|
|
367
|
+
plain_text = plain_text[:-1]
|
|
368
|
+
|
|
369
|
+
df = pd.read_csv(io.StringIO(plain_text), sep="\t")
|
|
370
|
+
|
|
371
|
+
try:
|
|
372
|
+
if filter_ == cfg.ODS:
|
|
373
|
+
df.to_excel(file_name, engine="odf", sheet_name="Geometric measurements", index=False, na_rep="NA")
|
|
374
|
+
self.flag_saved = True
|
|
375
|
+
if filter_ == cfg.XLSX:
|
|
376
|
+
df.to_excel(file_name, sheet_name="Geometric measurements", index=False, na_rep="NA")
|
|
377
|
+
self.flag_saved = True
|
|
378
|
+
if filter_ == cfg.HTML:
|
|
379
|
+
df.to_html(file_name, index=False, na_rep="NA")
|
|
380
|
+
self.flag_saved = True
|
|
381
|
+
if filter_ == cfg.CSV:
|
|
382
|
+
df.to_csv(file_name, index=False, sep=",", na_rep="NA")
|
|
383
|
+
self.flag_saved = True
|
|
384
|
+
if filter_ == cfg.TSV:
|
|
385
|
+
df.to_csv(file_name, index=False, sep="\t", na_rep="NA")
|
|
386
|
+
self.flag_saved = True
|
|
387
|
+
if filter_ == cfg.PANDAS_DF:
|
|
388
|
+
df.to_pickle(file_name)
|
|
389
|
+
if filter_ == cfg.RDS:
|
|
390
|
+
pyreadr.write_rds(file_name, df)
|
|
391
|
+
|
|
392
|
+
except Exception:
|
|
393
|
+
QMessageBox.warning(self, cfg.programName, "An error occured during saving the measurement results")
|
|
394
|
+
return True
|
|
395
|
+
return False # everything OK
|
|
223
396
|
|
|
224
397
|
|
|
225
|
-
def show_widget(self):
|
|
398
|
+
def show_widget(self) -> None:
|
|
226
399
|
"""
|
|
227
400
|
active the geometric measurement widget
|
|
228
401
|
"""
|
|
229
402
|
|
|
230
403
|
def close_measurement_widget():
|
|
404
|
+
"""
|
|
405
|
+
close the geometric measurement widget
|
|
406
|
+
"""
|
|
231
407
|
|
|
232
|
-
|
|
233
|
-
for n_player, dw in enumerate(self.dw_player):
|
|
234
|
-
dw.frame_viewer.clear()
|
|
235
|
-
dw.stack.setCurrentIndex(cfg.VIDEO_VIEWER)
|
|
236
|
-
dw.setWindowTitle(f"Player #{n_player + 1}")
|
|
237
|
-
self.measurement_w.close()
|
|
408
|
+
logging.debug("close_measurement_widget")
|
|
238
409
|
|
|
239
|
-
|
|
410
|
+
if self.observationId and self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.MEDIA:
|
|
411
|
+
for n_player, dw in enumerate(self.dw_player):
|
|
412
|
+
dw.frame_viewer.clear()
|
|
413
|
+
dw.stack.setCurrentIndex(cfg.VIDEO_VIEWER)
|
|
414
|
+
dw.setWindowTitle(f"Player #{n_player + 1}")
|
|
415
|
+
self.actionPlay.setEnabled(True)
|
|
240
416
|
|
|
241
|
-
self.
|
|
417
|
+
if self.observationId and self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.IMAGES:
|
|
418
|
+
for dw in self.dw_player:
|
|
419
|
+
self.extract_frame(dw)
|
|
242
420
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
421
|
+
self.geometric_measurements_mode = False
|
|
422
|
+
self.measurement_w.draw_mem = {}
|
|
423
|
+
|
|
424
|
+
self.measurement_w.close()
|
|
425
|
+
menu_options.update_menu(self)
|
|
246
426
|
|
|
247
427
|
self.geometric_measurements_mode = True
|
|
248
428
|
self.pause_video()
|
|
@@ -252,9 +432,13 @@ def show_widget(self):
|
|
|
252
432
|
self.actionPlay.setEnabled(False)
|
|
253
433
|
|
|
254
434
|
self.measurement_w = wgMeasurement()
|
|
255
|
-
self.measurement_w.setWindowFlags(Qt.WindowStaysOnTopHint)
|
|
435
|
+
# self.measurement_w.setWindowFlags(Qt.WindowStaysOnTopHint)
|
|
256
436
|
self.measurement_w.closeSignal.connect(close_measurement_widget)
|
|
257
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)
|
|
440
|
+
self.measurement_w.draw_mem = {}
|
|
441
|
+
|
|
258
442
|
self.measurement_w.show()
|
|
259
443
|
|
|
260
444
|
for dw in self.dw_player:
|
|
@@ -263,35 +447,62 @@ def show_widget(self):
|
|
|
263
447
|
self.extract_frame(dw)
|
|
264
448
|
|
|
265
449
|
|
|
266
|
-
def draw_point(self, x, y, color: str, n_player: int = 0):
|
|
450
|
+
def draw_point(self, x: int, y: int, color: str, n_player: int = 0) -> None:
|
|
267
451
|
"""
|
|
268
452
|
draw point on frame-by-frame image
|
|
269
453
|
"""
|
|
454
|
+
|
|
455
|
+
logging.debug("draw_point function")
|
|
456
|
+
|
|
270
457
|
RADIUS = 6
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
painter
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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)
|
|
279
472
|
self.dw_player[n_player].frame_viewer.update()
|
|
280
473
|
|
|
281
474
|
|
|
282
|
-
def draw_line(self, x1, y1, x2, y2, color: str, n_player=0):
|
|
475
|
+
def draw_line(self, x1: int, y1: int, x2: int, y2: int, color: str, n_player: int = 0) -> None:
|
|
283
476
|
"""
|
|
284
477
|
draw line on frame-by-frame image
|
|
285
478
|
"""
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
painter
|
|
289
|
-
|
|
290
|
-
|
|
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)
|
|
291
490
|
self.dw_player[n_player].frame_viewer.update()
|
|
292
491
|
|
|
293
492
|
|
|
294
|
-
def
|
|
493
|
+
def append_results(self, results: list) -> None:
|
|
494
|
+
"""
|
|
495
|
+
append results to measurements table
|
|
496
|
+
"""
|
|
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)
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def image_clicked(self, n_player: int, event) -> None:
|
|
295
506
|
"""
|
|
296
507
|
Geometric measurements on image
|
|
297
508
|
|
|
@@ -300,7 +511,7 @@ def image_clicked(self, n_player, event):
|
|
|
300
511
|
event (Qevent): event (mousepressed)
|
|
301
512
|
"""
|
|
302
513
|
|
|
303
|
-
logging.debug(
|
|
514
|
+
logging.debug("function image_clicked")
|
|
304
515
|
|
|
305
516
|
if not self.geometric_measurements_mode:
|
|
306
517
|
return
|
|
@@ -310,214 +521,341 @@ def image_clicked(self, n_player, event):
|
|
|
310
521
|
return
|
|
311
522
|
|
|
312
523
|
self.mem_player = n_player
|
|
313
|
-
if self.
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
# convert label coordinates in pixmap coordinates
|
|
321
|
-
x = int(
|
|
322
|
-
x
|
|
323
|
-
- (self.dw_player[n_player].frame_viewer.width() - self.dw_player[n_player].frame_viewer.pixmap().width())
|
|
324
|
-
/ 2
|
|
325
|
-
)
|
|
326
|
-
y = int(
|
|
327
|
-
y
|
|
328
|
-
- (self.dw_player[n_player].frame_viewer.height() - self.dw_player[n_player].frame_viewer.pixmap().height())
|
|
329
|
-
/ 2
|
|
330
|
-
)
|
|
524
|
+
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.MEDIA:
|
|
525
|
+
if self.dw_player[n_player].player.estimated_frame_number is not None:
|
|
526
|
+
current_frame = self.dw_player[n_player].player.estimated_frame_number
|
|
527
|
+
else:
|
|
528
|
+
current_frame = cfg.NA
|
|
529
|
+
elif self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.IMAGES:
|
|
530
|
+
current_frame = self.image_idx
|
|
331
531
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
(x / self.dw_player[n_player].frame_viewer.pixmap().width()) * self.dw_player[n_player].player.width
|
|
335
|
-
)
|
|
336
|
-
y_video = round(
|
|
337
|
-
(y / self.dw_player[n_player].frame_viewer.pixmap().height()) * self.dw_player[n_player].player.height
|
|
338
|
-
)
|
|
532
|
+
if not (hasattr(self, "measurement_w") and (self.measurement_w is not None) and (self.measurement_w.isVisible())):
|
|
533
|
+
return
|
|
339
534
|
|
|
340
|
-
|
|
341
|
-
0 <= x <= self.dw_player[n_player].frame_viewer.pixmap().width()
|
|
342
|
-
and 0 <= y <= self.dw_player[n_player].frame_viewer.pixmap().height()
|
|
343
|
-
):
|
|
344
|
-
self.measurement_w.status_lb.setText("<b>The click is outside the video area</b>")
|
|
345
|
-
return
|
|
535
|
+
x, y = event.pos().x(), event.pos().y()
|
|
346
536
|
|
|
347
|
-
|
|
537
|
+
logging.debug(f"clicked on {x} {y}")
|
|
348
538
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
draw_point(self, x, y, self.measurement_w.mark_color, n_player)
|
|
353
|
-
if current_frame not in self.measurement_w.draw_mem:
|
|
354
|
-
self.measurement_w.draw_mem[current_frame] = []
|
|
539
|
+
# convert label coordinates in pixmap coordinates
|
|
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)
|
|
355
542
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
543
|
+
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.MEDIA:
|
|
544
|
+
# convert pixmap coordinates in video coordinates
|
|
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)
|
|
547
|
+
elif self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.IMAGES:
|
|
548
|
+
original_width = QPixmap(self.images_list[self.image_idx]).size().width()
|
|
549
|
+
original_height = QPixmap(self.images_list[self.image_idx]).size().height()
|
|
550
|
+
x_video = round((pixmap_x / self.dw_player[n_player].frame_viewer.pixmap().width()) * original_width)
|
|
551
|
+
y_video = round((pixmap_y / self.dw_player[n_player].frame_viewer.pixmap().height()) * original_height)
|
|
552
|
+
|
|
553
|
+
if not (
|
|
554
|
+
0 <= pixmap_x <= self.dw_player[n_player].frame_viewer.pixmap().width()
|
|
555
|
+
and 0 <= pixmap_y <= self.dw_player[n_player].frame_viewer.pixmap().height()
|
|
556
|
+
):
|
|
557
|
+
self.measurement_w.status_lb.setText("<b>The click is outside the video area</b>")
|
|
558
|
+
return
|
|
359
559
|
|
|
360
|
-
|
|
361
|
-
(
|
|
362
|
-
f"Time: {self.getLaps():.3f}\tPlayer: {n_player + 1}\t"
|
|
363
|
-
f"Frame: {current_frame}\tPoint: {x_video},{y_video}"
|
|
364
|
-
)
|
|
365
|
-
)
|
|
366
|
-
self.measurement_w.flag_saved = False
|
|
560
|
+
self.measurement_w.status_lb.clear()
|
|
367
561
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
draw_point(self, x, y, self.measurement_w.mark_color, n_player)
|
|
372
|
-
self.memx, self.memy = x, y
|
|
373
|
-
self.memx_video, self.memy_video = x_video, y_video
|
|
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"]
|
|
374
565
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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))
|
|
378
571
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
+
|
|
581
|
+
# point
|
|
582
|
+
if self.measurement_w.rb_point.isChecked():
|
|
583
|
+
if event.button() == Qt.LeftButton:
|
|
584
|
+
draw_point(self, pixmap_x, pixmap_y, self.measurement_w.mark_color, n_player)
|
|
585
|
+
if current_frame not in self.measurement_w.draw_mem:
|
|
586
|
+
self.measurement_w.draw_mem[current_frame] = []
|
|
587
|
+
|
|
588
|
+
self.measurement_w.draw_mem[current_frame].append(
|
|
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
|
+
}
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
append_results(
|
|
598
|
+
self,
|
|
599
|
+
(
|
|
600
|
+
n_player + 1,
|
|
601
|
+
media_file_name,
|
|
602
|
+
f"{self.getLaps():.03f}",
|
|
603
|
+
current_frame,
|
|
604
|
+
cfg.POINT_OBJECT,
|
|
605
|
+
x_video,
|
|
606
|
+
y_video,
|
|
607
|
+
cfg.NA,
|
|
608
|
+
cfg.NA,
|
|
609
|
+
cfg.NA,
|
|
610
|
+
str([(x_video, y_video)]),
|
|
611
|
+
),
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
self.measurement_w.flag_saved = False
|
|
615
|
+
|
|
616
|
+
# angle
|
|
617
|
+
elif self.measurement_w.rb_angle.isChecked() or self.measurement_w.rb_oriented_angle.isChecked():
|
|
618
|
+
if event.button() == Qt.LeftButton:
|
|
619
|
+
draw_point(self, pixmap_x, pixmap_y, self.measurement_w.mark_color, n_player)
|
|
620
|
+
self.measurement_w.mem_points = [(pixmap_x, pixmap_y)]
|
|
621
|
+
self.measurement_w.mem_video = [(x_video, y_video)]
|
|
622
|
+
|
|
623
|
+
if event.button() == Qt.RightButton and len(self.measurement_w.mem_points):
|
|
624
|
+
draw_point(self, pixmap_x, pixmap_y, self.measurement_w.mark_color, n_player)
|
|
625
|
+
draw_line(
|
|
626
|
+
self,
|
|
627
|
+
self.measurement_w.mem_points[0][0],
|
|
628
|
+
self.measurement_w.mem_points[0][1],
|
|
629
|
+
pixmap_x,
|
|
630
|
+
pixmap_y,
|
|
631
|
+
self.measurement_w.mark_color,
|
|
632
|
+
n_player,
|
|
633
|
+
)
|
|
384
634
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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]
|
|
395
645
|
)
|
|
646
|
+
object_type = cfg.ORIENTED_ANGLE_OBJECT
|
|
396
647
|
|
|
397
|
-
|
|
648
|
+
append_results(
|
|
649
|
+
self,
|
|
398
650
|
(
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
651
|
+
n_player + 1,
|
|
652
|
+
media_file_name,
|
|
653
|
+
f"{self.getLaps():.03f}",
|
|
654
|
+
current_frame,
|
|
655
|
+
object_type,
|
|
656
|
+
cfg.NA,
|
|
657
|
+
cfg.NA,
|
|
658
|
+
cfg.NA,
|
|
659
|
+
cfg.NA,
|
|
660
|
+
round(
|
|
661
|
+
angle,
|
|
662
|
+
1,
|
|
663
|
+
),
|
|
664
|
+
str(self.measurement_w.mem_video),
|
|
665
|
+
),
|
|
402
666
|
)
|
|
667
|
+
|
|
403
668
|
self.measurement_w.flag_saved = False
|
|
404
|
-
|
|
669
|
+
if current_frame not in self.measurement_w.draw_mem:
|
|
670
|
+
self.measurement_w.draw_mem[current_frame] = []
|
|
405
671
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
672
|
+
self.measurement_w.draw_mem[current_frame].append(
|
|
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
|
+
}
|
|
679
|
+
)
|
|
411
680
|
|
|
412
|
-
|
|
413
|
-
|
|
681
|
+
self.measurement_w.mem_points, self.measurement_w.mem_video = [], []
|
|
682
|
+
|
|
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):
|
|
414
688
|
draw_line(
|
|
415
|
-
self,
|
|
689
|
+
self,
|
|
690
|
+
self.measurement_w.mem_points[-1][0],
|
|
691
|
+
self.measurement_w.mem_points[-1][1],
|
|
692
|
+
pixmap_x,
|
|
693
|
+
pixmap_y,
|
|
694
|
+
self.measurement_w.mark_color,
|
|
695
|
+
n_player,
|
|
416
696
|
)
|
|
697
|
+
self.measurement_w.mem_points.append((pixmap_x, pixmap_y))
|
|
698
|
+
self.measurement_w.mem_video.append((x_video, y_video))
|
|
699
|
+
|
|
700
|
+
if event.button() == Qt.RightButton and len(self.measurement_w.mem_points) >= 2:
|
|
701
|
+
# close polygon
|
|
702
|
+
draw_line(
|
|
703
|
+
self,
|
|
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],
|
|
708
|
+
self.measurement_w.mark_color,
|
|
709
|
+
n_player,
|
|
710
|
+
)
|
|
711
|
+
if current_frame not in self.measurement_w.draw_mem:
|
|
712
|
+
self.measurement_w.draw_mem[current_frame] = []
|
|
713
|
+
|
|
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
|
+
)
|
|
417
722
|
|
|
418
|
-
|
|
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
|
+
)
|
|
419
734
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
self.measurement_w.draw_mem[current_frame].append(
|
|
432
|
-
[n_player, "angle", self.measurement_w.mark_color, self.memPoints]
|
|
433
|
-
)
|
|
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
|
+
)
|
|
434
746
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
self.memPoints.append((x, y))
|
|
452
|
-
self.memPoints_video.append((x_video, y_video))
|
|
747
|
+
append_results(
|
|
748
|
+
self,
|
|
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
|
+
),
|
|
762
|
+
)
|
|
453
763
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
draw_line(
|
|
457
|
-
self, self.memPoints[-1][0], self.memPoints[-1][1], x, y, self.measurement_w.mark_color, n_player
|
|
458
|
-
)
|
|
459
|
-
self.memPoints.append((x, y))
|
|
460
|
-
self.memPoints_video.append((x_video, y_video))
|
|
764
|
+
self.measurement_w.flag_saved = False
|
|
765
|
+
self.measurement_w.mem_points, self.measurement_w.mem_video = [], []
|
|
461
766
|
|
|
462
|
-
|
|
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):
|
|
463
772
|
draw_line(
|
|
464
773
|
self,
|
|
465
|
-
self.
|
|
466
|
-
self.
|
|
467
|
-
|
|
468
|
-
|
|
774
|
+
self.measurement_w.mem_points[-1][0],
|
|
775
|
+
self.measurement_w.mem_points[-1][1],
|
|
776
|
+
pixmap_x,
|
|
777
|
+
pixmap_y,
|
|
469
778
|
self.measurement_w.mark_color,
|
|
470
779
|
n_player,
|
|
471
780
|
)
|
|
472
|
-
|
|
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:
|
|
785
|
+
if current_frame not in self.measurement_w.draw_mem:
|
|
786
|
+
self.measurement_w.draw_mem[current_frame] = []
|
|
787
|
+
|
|
788
|
+
self.measurement_w.draw_mem[current_frame].append(
|
|
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
|
+
}
|
|
795
|
+
)
|
|
473
796
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
self.measurement_w.
|
|
477
|
-
|
|
797
|
+
length = util.polyline_length(self.measurement_w.mem_video)
|
|
798
|
+
try:
|
|
799
|
+
length = length / float(self.measurement_w.lePx.text()) * float(self.measurement_w.leRef.text())
|
|
800
|
+
except Exception:
|
|
801
|
+
QMessageBox.critical(
|
|
802
|
+
None,
|
|
803
|
+
cfg.programName,
|
|
804
|
+
"Check reference and pixel values! Values must be numeric.",
|
|
805
|
+
QMessageBox.Ok | QMessageBox.Default,
|
|
806
|
+
QMessageBox.NoButton,
|
|
478
807
|
)
|
|
479
808
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
)
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
)
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
f"Frame: {current_frame}\tArea: {round(area, 1)}"
|
|
499
|
-
)
|
|
500
|
-
)
|
|
501
|
-
self.measurement_w.flag_saved = False
|
|
502
|
-
self.memPoints, self.memPoints_video = [], []
|
|
809
|
+
append_results(
|
|
810
|
+
self,
|
|
811
|
+
(
|
|
812
|
+
n_player + 1,
|
|
813
|
+
media_file_name,
|
|
814
|
+
f"{self.getLaps():.03f}",
|
|
815
|
+
current_frame,
|
|
816
|
+
cfg.POLYLINE_OBJECT,
|
|
817
|
+
cfg.NA,
|
|
818
|
+
cfg.NA,
|
|
819
|
+
round(length, 1),
|
|
820
|
+
cfg.NA,
|
|
821
|
+
cfg.NA,
|
|
822
|
+
str(self.measurement_w.mem_video),
|
|
823
|
+
),
|
|
824
|
+
)
|
|
825
|
+
self.measurement_w.mem_points, self.measurement_w.mem_video = [], []
|
|
826
|
+
self.measurement_w.flag_saved = False
|
|
503
827
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
else: # no measurements
|
|
508
|
-
QMessageBox.warning(
|
|
509
|
-
self,
|
|
510
|
-
cfg.programName,
|
|
511
|
-
"The Focus area function is not yet available in frame-by-frame mode.",
|
|
512
|
-
QMessageBox.Ok | QMessageBox.Default,
|
|
513
|
-
QMessageBox.NoButton,
|
|
514
|
-
)
|
|
828
|
+
else:
|
|
829
|
+
self.measurement_w.status_lb.setText("<b>Choose a measurement type!</b>")
|
|
515
830
|
|
|
516
831
|
|
|
517
832
|
def redraw_measurements(self):
|
|
518
833
|
"""
|
|
519
834
|
redraw measurements from previous frames
|
|
520
835
|
"""
|
|
836
|
+
|
|
837
|
+
def scale_coord(coord_list: list) -> List[int]:
|
|
838
|
+
"""
|
|
839
|
+
scale coordinates from original media resolution to pixmap
|
|
840
|
+
"""
|
|
841
|
+
|
|
842
|
+
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.IMAGES:
|
|
843
|
+
# pixmap_size = QPixmap(self.images_list[self.image_idx]).size()
|
|
844
|
+
original_width, original_height = self.current_image_size
|
|
845
|
+
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.MEDIA:
|
|
846
|
+
original_width = dw.player.width
|
|
847
|
+
original_height = dw.player.height
|
|
848
|
+
|
|
849
|
+
pixmap_coord_list: List[int] = []
|
|
850
|
+
for idx, coord in enumerate(coord_list):
|
|
851
|
+
if idx % 2 == 0:
|
|
852
|
+
coord_pixmap = round(coord / original_width * dw.frame_viewer.pixmap().width())
|
|
853
|
+
else:
|
|
854
|
+
coord_pixmap = round(coord / original_height * dw.frame_viewer.pixmap().height())
|
|
855
|
+
pixmap_coord_list.append(coord_pixmap)
|
|
856
|
+
|
|
857
|
+
return pixmap_coord_list
|
|
858
|
+
|
|
521
859
|
logging.debug("Redraw measurement marks")
|
|
522
860
|
|
|
523
861
|
if not (hasattr(self, "measurement_w") and self.measurement_w is not None and self.measurement_w.isVisible()):
|
|
@@ -528,56 +866,72 @@ def redraw_measurements(self):
|
|
|
528
866
|
return
|
|
529
867
|
|
|
530
868
|
for idx, dw in enumerate(self.dw_player):
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
869
|
+
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.MEDIA:
|
|
870
|
+
if dw.player.estimated_frame_number is not None:
|
|
871
|
+
current_frame = dw.player.estimated_frame_number
|
|
872
|
+
else:
|
|
873
|
+
current_frame = cfg.NA
|
|
874
|
+
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.IMAGES:
|
|
875
|
+
current_frame = self.image_idx
|
|
536
876
|
|
|
537
877
|
for frame in self.measurement_w.draw_mem:
|
|
538
|
-
|
|
539
878
|
for element in self.measurement_w.draw_mem[frame]:
|
|
540
|
-
|
|
541
879
|
if frame == current_frame:
|
|
542
|
-
|
|
880
|
+
elements_color = element["color"]
|
|
543
881
|
else:
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
if element[
|
|
547
|
-
if element[
|
|
548
|
-
x, y = element[
|
|
549
|
-
draw_point(self, x, y,
|
|
550
|
-
|
|
551
|
-
if element[
|
|
552
|
-
x1, y1
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
draw_point(self,
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
draw_line(self, x1, y1,
|
|
563
|
-
|
|
564
|
-
draw_point(self,
|
|
565
|
-
draw_point(self,
|
|
566
|
-
|
|
567
|
-
|
|
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:
|
|
568
907
|
polygon = QPolygon()
|
|
569
|
-
for
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
painter
|
|
575
|
-
|
|
908
|
+
for x, y in element["coordinates"]:
|
|
909
|
+
x, y = scale_coord([x, y])
|
|
910
|
+
polygon.append(QPoint(x, y))
|
|
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
|
+
|
|
576
923
|
dw.frame_viewer.update()
|
|
577
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)
|
|
578
930
|
|
|
579
|
-
|
|
931
|
+
draw_line(self, x1, y1, x2, y2, elements_color, n_player=idx)
|
|
580
932
|
|
|
933
|
+
|
|
934
|
+
if __name__ == "__main__":
|
|
581
935
|
import sys
|
|
582
936
|
|
|
583
937
|
app = QApplication(sys.argv)
|