boris-behav-obs 8.16.5__py3-none-any.whl → 9.7.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. boris/__init__.py +1 -1
  2. boris/__main__.py +1 -1
  3. boris/about.py +28 -40
  4. boris/add_modifier.py +88 -80
  5. boris/add_modifier_ui.py +266 -144
  6. boris/advanced_event_filtering.py +23 -29
  7. boris/analysis_plugins/__init__.py +0 -0
  8. boris/analysis_plugins/_export_to_feral.py +225 -0
  9. boris/analysis_plugins/_latency.py +59 -0
  10. boris/analysis_plugins/irr_cohen_kappa.py +109 -0
  11. boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
  12. boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
  13. boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
  14. boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
  15. boris/analysis_plugins/number_of_occurences.py +22 -0
  16. boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
  17. boris/analysis_plugins/time_budget.py +61 -0
  18. boris/behav_coding_map_creator.py +235 -236
  19. boris/behavior_binary_table.py +33 -50
  20. boris/behaviors_coding_map.py +17 -18
  21. boris/boris_cli.py +6 -25
  22. boris/cmd_arguments.py +12 -1
  23. boris/coding_pad.py +19 -36
  24. boris/config.py +109 -50
  25. boris/config_file.py +58 -67
  26. boris/connections.py +105 -58
  27. boris/converters.py +13 -37
  28. boris/converters_ui.py +187 -110
  29. boris/cooccurence.py +250 -0
  30. boris/core.py +2174 -1303
  31. boris/core_qrc.py +15892 -10829
  32. boris/core_ui.py +941 -806
  33. boris/db_functions.py +17 -42
  34. boris/dev.py +27 -7
  35. boris/dialog.py +461 -242
  36. boris/duration_widget.py +9 -14
  37. boris/edit_event.py +61 -31
  38. boris/edit_event_ui.py +208 -97
  39. boris/event_operations.py +405 -281
  40. boris/events_cursor.py +25 -17
  41. boris/events_snapshots.py +36 -82
  42. boris/exclusion_matrix.py +4 -9
  43. boris/export_events.py +180 -203
  44. boris/export_observation.py +60 -73
  45. boris/external_processes.py +123 -98
  46. boris/geometric_measurement.py +427 -218
  47. boris/gui_utilities.py +91 -14
  48. boris/image_overlay.py +4 -4
  49. boris/import_observations.py +190 -98
  50. boris/ipc_mpv.py +325 -0
  51. boris/irr.py +20 -57
  52. boris/latency.py +31 -24
  53. boris/measurement_widget.py +14 -18
  54. boris/media_file.py +17 -19
  55. boris/menu_options.py +16 -6
  56. boris/modifier_coding_map_creator.py +1013 -0
  57. boris/modifiers_coding_map.py +7 -9
  58. boris/mpv2.py +128 -35
  59. boris/observation.py +501 -211
  60. boris/observation_operations.py +1037 -393
  61. boris/observation_ui.py +573 -363
  62. boris/observations_list.py +51 -58
  63. boris/otx_parser.py +74 -68
  64. boris/param_panel.py +45 -59
  65. boris/param_panel_ui.py +254 -138
  66. boris/player_dock_widget.py +91 -56
  67. boris/plot_data_module.py +20 -53
  68. boris/plot_events.py +56 -153
  69. boris/plot_events_rt.py +16 -30
  70. boris/plot_spectrogram_rt.py +83 -56
  71. boris/plot_waveform_rt.py +27 -49
  72. boris/plugins.py +468 -0
  73. boris/portion/__init__.py +18 -8
  74. boris/portion/const.py +35 -18
  75. boris/portion/dict.py +5 -5
  76. boris/portion/func.py +2 -2
  77. boris/portion/interval.py +21 -41
  78. boris/portion/io.py +41 -32
  79. boris/preferences.py +307 -123
  80. boris/preferences_ui.py +686 -227
  81. boris/project.py +294 -271
  82. boris/project_functions.py +626 -537
  83. boris/project_import_export.py +204 -213
  84. boris/project_ui.py +673 -441
  85. boris/qrc_boris.py +6 -3
  86. boris/qrc_boris5.py +6 -3
  87. boris/select_modifiers.py +62 -90
  88. boris/select_observations.py +19 -197
  89. boris/select_subj_behav.py +67 -39
  90. boris/state_events.py +51 -33
  91. boris/subjects_pad.py +7 -9
  92. boris/synthetic_time_budget.py +42 -26
  93. boris/time_budget_functions.py +169 -169
  94. boris/time_budget_widget.py +77 -89
  95. boris/transitions.py +41 -41
  96. boris/utilities.py +594 -226
  97. boris/version.py +3 -3
  98. boris/video_equalizer.py +16 -14
  99. boris/video_equalizer_ui.py +199 -130
  100. boris/video_operations.py +86 -28
  101. boris/view_df.py +104 -0
  102. boris/view_df_ui.py +75 -0
  103. boris/write_event.py +240 -136
  104. boris_behav_obs-9.7.12.dist-info/METADATA +139 -0
  105. boris_behav_obs-9.7.12.dist-info/RECORD +110 -0
  106. {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.12.dist-info}/WHEEL +1 -1
  107. boris_behav_obs-9.7.12.dist-info/entry_points.txt +2 -0
  108. boris/README.TXT +0 -22
  109. boris/add_modifier.ui +0 -323
  110. boris/converters.ui +0 -289
  111. boris/core.qrc +0 -37
  112. boris/core.ui +0 -1571
  113. boris/edit_event.ui +0 -233
  114. boris/icons/logo_eye.ico +0 -0
  115. boris/map_creator.py +0 -982
  116. boris/observation.ui +0 -814
  117. boris/param_panel.ui +0 -379
  118. boris/preferences.ui +0 -537
  119. boris/project.ui +0 -1074
  120. boris/vlc_local.py +0 -90
  121. boris_behav_obs-8.16.5.dist-info/LICENSE.TXT +0 -674
  122. boris_behav_obs-8.16.5.dist-info/METADATA +0 -134
  123. boris_behav_obs-8.16.5.dist-info/RECORD +0 -107
  124. boris_behav_obs-8.16.5.dist-info/entry_points.txt +0 -2
  125. {boris → boris_behav_obs-9.7.12.dist-info/licenses}/LICENSE.TXT +0 -0
  126. {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.12.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2023 Olivier Friard
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 PyQt5.QtCore import QPoint, Qt, pyqtSignal, QEvent
37
- from PyQt5.QtGui import QColor, QPainter, QPolygon, QPixmap
38
- from PyQt5.QtWidgets import (
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
- QPlainTextEdit,
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 Union, Optional, List, Tuple, Dict
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(QWidget):
64
+ class wgMeasurement(QDialog):
64
65
  """
65
66
  widget for geometric measurements
66
67
  """
67
68
 
68
- closeSignal = pyqtSignal()
69
- send_event_signal = pyqtSignal(QEvent)
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.rbPoint = QRadioButton("Point (left click)")
82
- vbox.addWidget(self.rbPoint)
86
+ self.rb_point = QRadioButton("Point (left click)", clicked=self.rb_clicked)
87
+ vbox.addWidget(self.rb_point)
83
88
 
84
- self.rbDistance = QRadioButton("Distance (start: left click, end: right click)")
85
- vbox.addWidget(self.rbDistance)
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.rbArea = QRadioButton("Area (left click for area vertices, right click to close area)")
88
- vbox.addWidget(self.rbArea)
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.rbAngle = QRadioButton("Angle (vertex: left click, segments: right click)")
91
- vbox.addWidget(self.rbAngle)
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 = QPlainTextEdit()
124
- vbox.addWidget(self.pte)
125
- self.pte.setReadOnly(True)
126
- self.pte.setLineWrapMode(QPlainTextEdit.NoWrap)
135
+ self.pte = QTableWidget()
136
+ self.pte.verticalHeader().hide()
137
+
127
138
  # header
128
- self.pte.setPlainText("Player\tTime\tFrame index\ttype of measurement\tx\ty\tdistance\tarea\tangle")
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.pbClear = QPushButton("Clear measurements", clicked=self.pbClear_clicked)
136
- hbox3.addWidget(self.pbClear)
137
- self.pbSave = QPushButton("Save results", clicked=self.pbSave_clicked)
138
- hbox3.addWidget(self.pbSave)
139
- self.pbClose = QPushButton("Close", clicked=self.pbClose_clicked)
140
- hbox3.addWidget(self.pbClose)
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.pbSave_clicked()
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 pbSave_clicked(self):
326
+ def pb_save_clicked(self) -> bool:
218
327
  """
219
- Save measurements results in plain text file
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
- file_name, filter_ = QFileDialog().getSaveFileName(
227
- self, "Save geometric measurements", "", ";;".join(file_formats)
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
- df = pd.read_csv(io.StringIO(self.pte.toPlainText()), sep="\t")
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
- painter = QPainter()
328
- painter.begin(self.dw_player[n_player].frame_viewer.pixmap())
329
- painter.setPen(QColor(color))
330
- painter.drawEllipse(QPoint(x, y), RADIUS, RADIUS)
331
- # cross inside circle
332
- painter.drawLine(x - RADIUS, y, x + RADIUS, y)
333
- painter.drawLine(x, y - RADIUS, x, y + RADIUS)
334
- painter.end()
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
- painter = QPainter()
343
- painter.begin(self.dw_player[n_player].frame_viewer.pixmap())
344
- painter.setPen(QColor(color))
345
- painter.drawLine(x1, y1, x2, y2)
346
- painter.end()
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: List):
493
+ def append_results(self, results: list) -> None:
351
494
  """
352
- append results to plain text widget
495
+ append results to measurements table
353
496
  """
354
- self.measurement_w.pte.appendPlainText("\t".join([str(x) for x in results]))
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 + 1
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
- x - (self.dw_player[n_player].frame_viewer.width() - self.dw_player[n_player].frame_viewer.pixmap().width()) / 2
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
- (pixmap_x / self.dw_player[n_player].frame_viewer.pixmap().width()) * self.dw_player[n_player].player.width
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.rbPoint.isChecked():
432
- if event.button() == 1: # left click
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
- [n_player, "point", self.measurement_w.mark_color, x_video, y_video]
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
- "Point",
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
- # distance
459
- elif self.measurement_w.rbDistance.isChecked():
460
- if event.button() == 1: # left
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.memPoints = [(pixmap_x, pixmap_y)]
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() == 2 and len(self.memPoints):
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.memPoints[0][0],
518
- self.memPoints[0][1],
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.memPoints.append((pixmap_x, pixmap_y))
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
- "Angle",
655
+ object_type,
536
656
  cfg.NA,
537
657
  cfg.NA,
538
658
  cfg.NA,
539
659
  cfg.NA,
540
- round(util.angle(self.memPoints[0], self.memPoints[1], self.memPoints[2]), 1),
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
- [n_player, "angle", self.measurement_w.mark_color, self.mem_video]
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.memPoints, self.mem_video = [], []
681
+ self.measurement_w.mem_points, self.measurement_w.mem_video = [], []
553
682
 
554
- # Area
555
- elif self.measurement_w.rbArea.isChecked():
556
- if event.button() == 1: # left
557
- draw_point(self, pixmap_x, pixmap_y, self.measurement_w.mark_color)
558
- if len(self.memPoints):
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.memPoints[-1][0],
562
- self.memPoints[-1][1],
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.memPoints.append((pixmap_x, pixmap_y))
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() == 2 and len(self.memPoints) >= 2:
572
- draw_point(self, pixmap_x, pixmap_y, self.measurement_w.mark_color, n_player)
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.memPoints[-1][0],
576
- self.memPoints[-1][1],
577
- pixmap_x,
578
- pixmap_y,
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.memPoints.append((pixmap_x, pixmap_y))
583
- self.mem_video.append((x_video, y_video))
711
+ if current_frame not in self.measurement_w.draw_mem:
712
+ self.measurement_w.draw_mem[current_frame] = []
584
713
 
585
- # close polygon
586
- draw_line(
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
- self.memPoints[-1][0],
589
- self.memPoints[-1][1],
590
- self.memPoints[0][0],
591
- self.memPoints[0][1],
592
- self.measurement_w.mark_color,
593
- n_player,
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
- [n_player, "polygon", self.measurement_w.mark_color, self.mem_video]
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
- area = area / (float(self.measurement_w.lePx.text()) ** 2) * float(self.measurement_w.leRef.text()) ** 2
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
- "Area",
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 + 1
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
- elementsColor = element[2] # color
880
+ elements_color = element["color"]
686
881
  else:
687
- elementsColor = cfg.PASSIVE_MEASUREMENTS_COLOR
688
-
689
- if element[0] == idx:
690
- if element[1] == "point":
691
- x, y = scale_coord(element[3:])
692
-
693
- draw_point(self, int(x), int(y), elementsColor, n_player=idx)
694
-
695
- if element[1] == "line":
696
- x1, y1, x2, y2 = scale_coord(element[3:])
697
- draw_line(self, x1, y1, x2, y2, elementsColor, n_player=idx)
698
- draw_point(self, x1, y1, elementsColor, n_player=idx)
699
- draw_point(self, x2, y2, elementsColor, n_player=idx)
700
-
701
- if element[1] == "angle":
702
- x1, y1 = scale_coord(element[3][0])
703
- x2, y2 = scale_coord(element[3][1])
704
- x3, y3 = scale_coord(element[3][2])
705
- draw_line(self, x1, y1, x2, y2, elementsColor, n_player=idx)
706
- draw_line(self, x1, y1, x3, y3, elementsColor, n_player=idx)
707
- draw_point(self, x1, y1, elementsColor, n_player=idx)
708
- draw_point(self, x2, y2, elementsColor, n_player=idx)
709
- draw_point(self, x3, y3, elementsColor, n_player=idx)
710
-
711
- if element[1] == "polygon":
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
- painter = QPainter()
718
- painter.begin(self.dw_player[idx].frame_viewer.pixmap())
719
- painter.setPen(QColor(elementsColor))
720
- painter.drawPolygon(polygon)
721
- painter.end()
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