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.

Files changed (128) hide show
  1. boris/__init__.py +1 -1
  2. boris/__main__.py +1 -1
  3. boris/about.py +28 -39
  4. boris/add_modifier.py +122 -109
  5. boris/add_modifier_ui.py +239 -135
  6. boris/advanced_event_filtering.py +81 -45
  7. boris/analysis_plugins/__init__.py +0 -0
  8. boris/analysis_plugins/_latency.py +59 -0
  9. boris/analysis_plugins/irr_cohen_kappa.py +109 -0
  10. boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
  11. boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
  12. boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
  13. boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
  14. boris/analysis_plugins/number_of_occurences.py +22 -0
  15. boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
  16. boris/analysis_plugins/time_budget.py +61 -0
  17. boris/behav_coding_map_creator.py +228 -229
  18. boris/behavior_binary_table.py +33 -50
  19. boris/behaviors_coding_map.py +17 -18
  20. boris/boris_cli.py +6 -25
  21. boris/cmd_arguments.py +12 -1
  22. boris/coding_pad.py +42 -49
  23. boris/config.py +141 -65
  24. boris/config_file.py +58 -67
  25. boris/connections.py +107 -61
  26. boris/converters.py +13 -37
  27. boris/converters_ui.py +187 -110
  28. boris/cooccurence.py +250 -0
  29. boris/core.py +2373 -1786
  30. boris/core_qrc.py +15895 -10743
  31. boris/core_ui.py +943 -798
  32. boris/db_functions.py +17 -42
  33. boris/dev.py +109 -8
  34. boris/dialog.py +482 -236
  35. boris/duration_widget.py +9 -14
  36. boris/edit_event.py +61 -31
  37. boris/edit_event_ui.py +208 -97
  38. boris/event_operations.py +408 -293
  39. boris/events_cursor.py +25 -17
  40. boris/events_snapshots.py +36 -82
  41. boris/exclusion_matrix.py +4 -9
  42. boris/export_events.py +184 -223
  43. boris/export_observation.py +74 -100
  44. boris/external_processes.py +123 -98
  45. boris/geometric_measurement.py +644 -290
  46. boris/gui_utilities.py +91 -14
  47. boris/image_overlay.py +4 -4
  48. boris/import_observations.py +190 -98
  49. boris/ipc_mpv.py +325 -0
  50. boris/irr.py +20 -57
  51. boris/latency.py +31 -24
  52. boris/measurement_widget.py +14 -18
  53. boris/media_file.py +17 -19
  54. boris/menu_options.py +17 -6
  55. boris/modifier_coding_map_creator.py +1013 -0
  56. boris/modifiers_coding_map.py +7 -9
  57. boris/mpv.py +1 -0
  58. boris/mpv2.py +732 -705
  59. boris/observation.py +533 -221
  60. boris/observation_operations.py +1025 -390
  61. boris/observation_ui.py +572 -362
  62. boris/observations_list.py +71 -53
  63. boris/otx_parser.py +74 -68
  64. boris/param_panel.py +31 -16
  65. boris/param_panel_ui.py +254 -138
  66. boris/player_dock_widget.py +90 -60
  67. boris/plot_data_module.py +25 -33
  68. boris/plot_events.py +127 -90
  69. boris/plot_events_rt.py +17 -31
  70. boris/plot_spectrogram_rt.py +95 -30
  71. boris/plot_waveform_rt.py +32 -21
  72. boris/plugins.py +431 -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 +306 -83
  80. boris/preferences_ui.py +684 -227
  81. boris/project.py +448 -293
  82. boris/project_functions.py +671 -238
  83. boris/project_import_export.py +213 -222
  84. boris/project_ui.py +674 -438
  85. boris/qrc_boris.py +6 -3
  86. boris/qrc_boris5.py +6 -3
  87. boris/select_modifiers.py +74 -48
  88. boris/select_observations.py +20 -198
  89. boris/select_subj_behav.py +67 -39
  90. boris/state_events.py +52 -35
  91. boris/subjects_pad.py +6 -9
  92. boris/synthetic_time_budget.py +45 -28
  93. boris/time_budget_functions.py +171 -171
  94. boris/time_budget_widget.py +84 -114
  95. boris/transitions.py +41 -47
  96. boris/utilities.py +627 -236
  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 +95 -29
  101. boris/view_df.py +104 -0
  102. boris/view_df_ui.py +75 -0
  103. boris/write_event.py +538 -0
  104. boris_behav_obs-9.7.6.dist-info/METADATA +139 -0
  105. boris_behav_obs-9.7.6.dist-info/RECORD +109 -0
  106. {boris_behav_obs-8.12.dist-info → boris_behav_obs-9.7.6.dist-info}/WHEEL +1 -1
  107. boris_behav_obs-9.7.6.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 -36
  112. boris/core.ui +0 -1556
  113. boris/edit_event.ui +0 -233
  114. boris/icons/logo_eye.ico +0 -0
  115. boris/map_creator.py +0 -850
  116. boris/observation.ui +0 -814
  117. boris/param_panel.ui +0 -379
  118. boris/preferences.ui +0 -537
  119. boris/project.ui +0 -1069
  120. boris/project_server.py +0 -236
  121. boris/vlc.py +0 -10343
  122. boris/vlc_local.py +0 -90
  123. boris_behav_obs-8.12.dist-info/LICENSE.TXT +0 -674
  124. boris_behav_obs-8.12.dist-info/METADATA +0 -128
  125. boris_behav_obs-8.12.dist-info/RECORD +0 -108
  126. boris_behav_obs-8.12.dist-info/entry_points.txt +0 -3
  127. {boris → boris_behav_obs-9.7.6.dist-info/licenses}/LICENSE.TXT +0 -0
  128. {boris_behav_obs-8.12.dist-info → boris_behav_obs-9.7.6.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
 
@@ -23,15 +23,12 @@ This file is part of BORIS.
23
23
  import binascii
24
24
  import io
25
25
  import json
26
- import os
27
- from decimal import ROUND_DOWN
28
- from decimal import Decimal as dec
29
- from decimal import getcontext
30
-
31
- from PyQt5.QtCore import QBuffer, QByteArray, QIODevice, QLineF, QPoint, Qt, pyqtSignal
32
- from PyQt5.QtGui import QBrush, QColor, QIcon, QMouseEvent, QPen, QPixmap, QPolygonF
33
- from PyQt5.QtWidgets import (
34
- QAction,
26
+ from pathlib import Path
27
+ import gui_utilities
28
+
29
+ from PySide6.QtCore import QBuffer, QByteArray, QIODevice, QLineF, QPoint, Qt, Signal
30
+ from PySide6.QtGui import QBrush, QColor, QIcon, QMouseEvent, QPen, QPixmap, QPolygonF, QAction
31
+ from PySide6.QtWidgets import (
35
32
  QApplication,
36
33
  QColorDialog,
37
34
  QFileDialog,
@@ -71,15 +68,14 @@ selectedBrush.setColor(QColor(255, 255, 0, 255))
71
68
 
72
69
 
73
70
  class BehaviorsMapCreatorWindow(QMainWindow):
74
-
75
- signal_add_to_project = pyqtSignal(dict)
71
+ signal_add_to_project = Signal(dict)
76
72
 
77
73
  class View(QGraphicsView):
78
74
  """
79
75
  class for handling mousepress event in QGraphicsView
80
76
  """
81
77
 
82
- mousePress = pyqtSignal(QMouseEvent)
78
+ mousePress = Signal(QMouseEvent)
83
79
 
84
80
  def mousePressEvent(self, event):
85
81
  self.mousePress.emit(event)
@@ -99,7 +95,6 @@ class BehaviorsMapCreatorWindow(QMainWindow):
99
95
  areaColor = QColor("lime")
100
96
 
101
97
  def __init__(self, arg):
102
-
103
98
  self.codes_list = arg
104
99
 
105
100
  super(BehaviorsMapCreatorWindow, self).__init__()
@@ -120,14 +115,14 @@ class BehaviorsMapCreatorWindow(QMainWindow):
120
115
  self.openMapAction.setStatusTip("Open a behaviors coding map")
121
116
  self.openMapAction.triggered.connect(self.openMap)
122
117
 
123
- self.saveMapAction = QAction(QIcon(), "&Save the current behaviors coding map", self)
118
+ self.saveMapAction = QAction(QIcon(), "&Save the behavior coding map", self)
124
119
  self.saveMapAction.setShortcut("Ctrl+S")
125
- self.saveMapAction.setStatusTip("Save the current behaviors coding map")
120
+ self.saveMapAction.setStatusTip("Save the behavior coding map")
126
121
  self.saveMapAction.setEnabled(False)
127
122
  self.saveMapAction.triggered.connect(self.saveMap_clicked)
128
123
 
129
- self.saveAsMapAction = QAction(QIcon(), "Save the current behaviors coding map as ...", self)
130
- self.saveAsMapAction.setStatusTip("Save the current behaviors coding map as ...")
124
+ self.saveAsMapAction = QAction(QIcon(), "Save the behavior coding map as ...", self)
125
+ self.saveAsMapAction.setStatusTip("Save the behavior coding map as ...")
131
126
  self.saveAsMapAction.setEnabled(False)
132
127
  self.saveAsMapAction.triggered.connect(self.saveAsMap_clicked)
133
128
 
@@ -137,13 +132,18 @@ class BehaviorsMapCreatorWindow(QMainWindow):
137
132
  self.mapNameAction.setEnabled(False)
138
133
  self.mapNameAction.triggered.connect(self.mapName_clicked)
139
134
 
135
+ self.resizeAction = QAction(QIcon(), "Resize the image", self)
136
+ self.resizeAction.setStatusTip("Resize the image")
137
+ self.resizeAction.setEnabled(False)
138
+ self.resizeAction.triggered.connect(self.resize_clicked)
139
+
140
140
  self.addToProject = QAction(QIcon(), "Add coding map to project", self)
141
141
  self.addToProject.setStatusTip("Add coding map to project")
142
142
  self.addToProject.setEnabled(False)
143
143
  self.addToProject.triggered.connect(self.add_to_project)
144
144
 
145
145
  self.exitAction = QAction(QIcon(), "&Close", self)
146
- self.exitAction.setStatusTip("Close")
146
+ self.exitAction.setStatusTip(cfg.CLOSE)
147
147
  self.exitAction.triggered.connect(self.close)
148
148
 
149
149
  menubar = self.menuBar()
@@ -155,16 +155,13 @@ class BehaviorsMapCreatorWindow(QMainWindow):
155
155
  fileMenu.addSeparator()
156
156
  fileMenu.addAction(self.mapNameAction)
157
157
  fileMenu.addSeparator()
158
+ fileMenu.addAction(self.resizeAction)
159
+ fileMenu.addSeparator()
158
160
  fileMenu.addAction(self.addToProject)
159
161
  fileMenu.addSeparator()
160
162
  fileMenu.addAction(self.exitAction)
161
163
 
162
164
  splitter1 = QSplitter(Qt.Vertical)
163
- """
164
- splitter1.addWidget(splitter1)
165
- splitter1.addWidget(bottom)
166
- """
167
- """vlayout_list = QVBoxLayout()"""
168
165
 
169
166
  self.view = self.View(self)
170
167
  self.view.mousePress.connect(self.viewMousePressEvent)
@@ -174,7 +171,6 @@ class BehaviorsMapCreatorWindow(QMainWindow):
174
171
  vlayout_list.addWidget(QLabel("Defined area"))
175
172
 
176
173
  self.area_list = QListWidget(self)
177
- # self.area_list.setMaximumHeight(120)
178
174
  self.area_list.itemClicked.connect(self.area_list_item_click)
179
175
  vlayout_list.addWidget(self.area_list)
180
176
  w = QWidget()
@@ -183,10 +179,6 @@ class BehaviorsMapCreatorWindow(QMainWindow):
183
179
  splitter1.setSizes([300, 100])
184
180
  splitter1.setStretchFactor(2, 8)
185
181
 
186
- self.btLoad = QPushButton("Load bitmap", self)
187
- self.btLoad.clicked.connect(self.loadBitmap)
188
- self.btLoad.setVisible(False)
189
-
190
182
  hlayout_cmd = QHBoxLayout()
191
183
 
192
184
  self.btNewArea = QPushButton("New behavior area", self)
@@ -204,6 +196,8 @@ class BehaviorsMapCreatorWindow(QMainWindow):
204
196
  self.btCancelAreaCreation.setVisible(False)
205
197
  hlayout_cmd.addWidget(self.btCancelAreaCreation)
206
198
 
199
+ hlayout_cmd.addItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
200
+
207
201
  self.btDeleteArea = QPushButton("Delete selected behavior area", self)
208
202
  self.btDeleteArea.clicked.connect(self.deleteArea)
209
203
  self.btDeleteArea.setVisible(False)
@@ -253,16 +247,9 @@ class BehaviorsMapCreatorWindow(QMainWindow):
253
247
  frame.setLayout(vlayout_frame)
254
248
 
255
249
  vlayout = QVBoxLayout()
256
- """
257
- vlayout.addWidget(self.view)
258
- vlayout.addWidget(QLabel("Defined area"))
259
- vlayout.addWidget(self.area_list)
260
- """
261
- vlayout.addWidget(splitter1)
262
- """vlayout.addLayout(vlayout_view_list)"""
263
250
 
251
+ vlayout.addWidget(splitter1)
264
252
  vlayout.addWidget(frame)
265
- vlayout.addWidget(self.btLoad)
266
253
 
267
254
  main_widget = QWidget(self)
268
255
  main_widget.setLayout(vlayout)
@@ -277,7 +264,6 @@ class BehaviorsMapCreatorWindow(QMainWindow):
277
264
 
278
265
  map_dict = self.make_coding_map_dict()
279
266
  if map_dict["areas"] == {}:
280
-
281
267
  QMessageBox.critical(
282
268
  self,
283
269
  cfg.programName,
@@ -310,19 +296,21 @@ class BehaviorsMapCreatorWindow(QMainWindow):
310
296
 
311
297
  self.selectedPolygon.setPen(QPen(QColor(255, 0, 0, 255), 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
312
298
 
313
- self.lb.setVisible(True)
314
299
  self.leAreaCode.setText(ac)
315
- self.leAreaCode.setVisible(True)
316
- self.btEditAreaCode.setVisible(True)
317
300
 
318
- self.btDeleteArea.setVisible(True)
301
+ for widget in (
302
+ self.lb,
303
+ self.leAreaCode,
304
+ self.btEditAreaCode,
305
+ self.btDeleteArea,
306
+ self.btColor,
307
+ self.slAlpha,
308
+ ):
309
+ widget.setVisible(True)
319
310
 
320
311
  self.areaColor = self.selectedPolygon.brush().color()
321
312
  self.btColor.setStyleSheet(f"QWidget {{background-color:{self.selectedPolygon.brush().color().name()}}}")
322
- self.btColor.setVisible(True)
323
-
324
- self.slAlpha.setValue(int(self.selectedPolygon.brush().color().alpha() / 255 * 100))
325
- self.slAlpha.setVisible(True)
313
+ self.slAlpha.setValue(int(self.areaColor.alpha() / 255 * 100))
326
314
 
327
315
  def edit_area_code(self):
328
316
  """
@@ -334,9 +322,7 @@ class BehaviorsMapCreatorWindow(QMainWindow):
334
322
  else:
335
323
  code_index = 0
336
324
 
337
- item, ok = QInputDialog.getItem(
338
- self, "Select a behavior", "Available behaviors", self.codes_list, code_index, False
339
- )
325
+ item, ok = QInputDialog.getItem(self, "Select a behavior", "Available behaviors", self.codes_list, code_index, False)
340
326
  self.leAreaCode.setText(item)
341
327
 
342
328
  if self.selectedPolygon:
@@ -376,9 +362,7 @@ class BehaviorsMapCreatorWindow(QMainWindow):
376
362
  cd.setWindowFlags(Qt.WindowStaysOnTopHint)
377
363
  cd.setOptions(QColorDialog.DontUseNativeDialog)
378
364
 
379
- # col = cd.getColor()
380
- # if col.isValid():
381
- if cd.exec_():
365
+ if cd.exec():
382
366
  self.areaColor = cd.currentColor()
383
367
  self.btColor.setStyleSheet(f"QWidget {{background-color:{self.areaColor.name()}}}")
384
368
  self.areaColor.setAlpha(int(self.slAlpha.value() / 100 * 255))
@@ -397,9 +381,7 @@ class BehaviorsMapCreatorWindow(QMainWindow):
397
381
  self.closedPolygon.setBrush(self.areaColor)
398
382
 
399
383
  def closeEvent(self, event):
400
-
401
384
  if self.flag_map_changed:
402
-
403
385
  response = dialog.MessageDialog(
404
386
  "BORIS - Behaviors map creator",
405
387
  "What to do about the current unsaved behaviors coding map?",
@@ -439,52 +421,38 @@ class BehaviorsMapCreatorWindow(QMainWindow):
439
421
 
440
422
  test = self.view.mapToScene(event.pos()).toPoint() # coordinates of clicked point
441
423
 
442
- if (
443
- test.x() < 0
444
- or test.y() < 0
445
- or test.x() > self.pixmap.size().width()
446
- or test.y() > self.pixmap.size().height()
447
- ):
424
+ if test.x() < 0 or test.y() < 0 or test.x() > self.pixmap.size().width() or test.y() > self.pixmap.size().height():
448
425
  return
449
426
 
450
427
  if not self.flagNewArea: # test clicked point for areas
451
-
452
428
  # reset selected polygon to default pen
453
429
  if self.selectedPolygon:
454
430
  self.selectedPolygon.setPen(QPen(designColor, penWidth, penStyle, Qt.RoundCap, Qt.RoundJoin))
455
431
  self.selectedPolygon = None
456
432
  self.selectedPolygonMemBrush = None
457
433
 
434
+ idx = 0
458
435
  for areaCode, pg in self.polygonsList2:
459
-
460
436
  if pg.contains(test):
461
-
462
437
  self.selectedPolygon = pg
463
-
464
438
  self.selectedPolygonMemBrush = self.selectedPolygon.brush()
465
-
466
- self.selectedPolygon.setPen(
467
- QPen(QColor(255, 0, 0, 255), 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
468
- )
469
-
470
- self.lb.setVisible(True)
439
+ self.selectedPolygon.setPen(QPen(QColor(255, 0, 0, 255), 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
471
440
  self.leAreaCode.setText(areaCode)
472
- self.leAreaCode.setVisible(True)
473
- # self.leAreaCode.setEnabled(False)
474
- self.btEditAreaCode.setVisible(True)
475
441
 
476
- self.btDeleteArea.setVisible(True)
442
+ for widget in (self.lb, self.leAreaCode, self.btEditAreaCode, self.btColor, self.slAlpha, self.btDeleteArea):
443
+ widget.setVisible(True)
477
444
 
478
445
  self.areaColor = self.selectedPolygon.brush().color()
479
- self.btColor.setStyleSheet(
480
- f"QWidget {{background-color:{self.selectedPolygon.brush().color().name()}}}"
481
- )
482
- self.btColor.setVisible(True)
446
+ self.btColor.setStyleSheet(f"QWidget {{background-color:{self.selectedPolygon.brush().color().name()}}}")
483
447
 
484
- self.slAlpha.setValue(int(self.selectedPolygon.brush().color().alpha() / 255 * 100))
485
- self.slAlpha.setVisible(True)
448
+ self.slAlpha.setValue(int(self.areaColor.alpha() / 255 * 100))
449
+
450
+ # select area in list widget
451
+ item = self.area_list.item(idx)
452
+ self.area_list.setCurrentItem(item)
486
453
 
487
454
  break
455
+ idx += 1
488
456
 
489
457
  if not self.selectedPolygon:
490
458
  self.leAreaCode.setVisible(False)
@@ -497,7 +465,6 @@ class BehaviorsMapCreatorWindow(QMainWindow):
497
465
 
498
466
  # delete last line item
499
467
  if (event.buttons() & Qt.RightButton) and not self.closedPolygon:
500
-
501
468
  if self.view.points:
502
469
  self.view.points = self.view.points[0:-1]
503
470
 
@@ -511,18 +478,28 @@ class BehaviorsMapCreatorWindow(QMainWindow):
511
478
  self.view.scene().removeItem(self.view.elList[-1])
512
479
  self.view.elList = self.view.elList[0:-1]
513
480
 
481
+ # middle button automatically close the polygon
482
+ if (event.buttons() & Qt.MiddleButton) and not self.closedPolygon:
483
+ line = QGraphicsLineItem(QLineF(self.view._start, QPoint(self.view.points[0][0], self.view.points[0][1])))
484
+ line.setPen(QPen(designColor, penWidth, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
485
+
486
+ self.view.scene().addItem(line)
487
+ self.view.elList.append(line)
488
+
489
+ self.statusBar().showMessage("Area completed")
490
+
491
+ # create polygon
492
+ add_polygon()
493
+ return
494
+
514
495
  # add line item
515
496
  if event.buttons() == Qt.LeftButton and not self.closedPolygon:
516
-
517
497
  if self.view._start:
518
-
519
498
  end = test
520
499
 
521
500
  # test is polygon is crossed
522
501
  if len(self.view.points) >= 3:
523
-
524
502
  for idx, _ in enumerate(self.view.points[:-2]):
525
-
526
503
  if util.intersection(
527
504
  self.view.points[idx],
528
505
  self.view.points[idx + 1],
@@ -534,10 +511,7 @@ class BehaviorsMapCreatorWindow(QMainWindow):
534
511
 
535
512
  # test if polygon closed (dist min 10 px)
536
513
  if abs(end.x() - self.view.points[0][0]) < 10 and abs(end.y() - self.view.points[0][1]) < 10:
537
-
538
- line = QGraphicsLineItem(
539
- QLineF(self.view._start, QPoint(self.view.points[0][0], self.view.points[0][1]))
540
- )
514
+ line = QGraphicsLineItem(QLineF(self.view._start, QPoint(self.view.points[0][0], self.view.points[0][1])))
541
515
  line.setPen(QPen(designColor, penWidth, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
542
516
 
543
517
  self.view.scene().addItem(line)
@@ -559,7 +533,6 @@ class BehaviorsMapCreatorWindow(QMainWindow):
559
533
  self.view._start = test
560
534
 
561
535
  else: # first point
562
-
563
536
  self.view._start = test
564
537
 
565
538
  ellipse = QGraphicsEllipseItem(self.view._start.x(), self.view._start.y(), 3, 3)
@@ -577,15 +550,12 @@ class BehaviorsMapCreatorWindow(QMainWindow):
577
550
 
578
551
  # automatically close the polygon
579
552
  if event.buttons() == Qt.MiddleButton and not self.closedPolygon:
580
-
581
553
  # add first point as last point of polygon
582
554
 
583
555
  # test is polygon is crossed
584
556
 
585
557
  if len(self.view.points) >= 3:
586
-
587
558
  for idx, _ in enumerate(self.view.points[1:-2]):
588
-
589
559
  if util.intersection(
590
560
  self.view.points[idx],
591
561
  self.view.points[idx + 1],
@@ -638,7 +608,6 @@ class BehaviorsMapCreatorWindow(QMainWindow):
638
608
  """
639
609
 
640
610
  if self.flag_map_changed:
641
-
642
611
  response = dialog.MessageDialog(
643
612
  cfg.programName + " - Behaviors coding map creator",
644
613
  "What to do about the current unsaved coding map?",
@@ -655,9 +624,9 @@ class BehaviorsMapCreatorWindow(QMainWindow):
655
624
  self.cancelMap()
656
625
 
657
626
  while True:
658
- map_name, ok = QInputDialog.getText(
659
- self, "Behaviors coding map name", "Enter a name for the new coding map"
660
- )
627
+ map_name, ok = QInputDialog.getText(self, "Behaviors coding map name", "Enter a name for the new coding map")
628
+ if not ok:
629
+ return
661
630
  if map_name.upper() in self.bcm_list:
662
631
  QMessageBox.critical(
663
632
  self,
@@ -671,18 +640,10 @@ class BehaviorsMapCreatorWindow(QMainWindow):
671
640
  if ok and map_name and map_name.upper() not in self.bcm_list:
672
641
  self.mapName = map_name
673
642
  break
674
- if not ok:
675
- return
676
- """
677
- if not self.mapName:
678
- QMessageBox.critical(self, "", "You must define a name for the new coding map")
679
- return
680
- """
681
643
 
682
644
  self.setWindowTitle(f"{cfg.programName} - Behaviors coding map creator tool - {self.mapName}")
683
645
 
684
- self.btLoad.setVisible(True)
685
- self.statusBar().showMessage('Click "Load bitmap" button to select and load a bitmap into the viewer')
646
+ self.loadBitmap()
686
647
 
687
648
  def openMap(self):
688
649
  """
@@ -701,79 +662,75 @@ class BehaviorsMapCreatorWindow(QMainWindow):
701
662
  if (response == "Save" and not self.saveMap_clicked()) or (response == "Cancel"):
702
663
  return
703
664
 
704
- fn = QFileDialog(self).getOpenFileName(
665
+ fileName, _ = QFileDialog(self).getOpenFileName(
705
666
  self, "Open a behaviors coding map", "", "Behaviors coding map (*.behav_coding_map);;All files (*)"
706
667
  )
707
- fileName = fn[0] if type(fn) is tuple else fn
708
668
 
709
- if fileName:
710
-
711
- try:
712
- self.codingMap = json.loads(open(fileName, "r").read())
713
- except Exception:
714
- QMessageBox.critical(self, cfg.programName, f"The file {fileName} is not a behaviors coding map.")
715
- return
716
-
717
- if (
718
- "coding_map_type" not in self.codingMap
719
- or self.codingMap["coding_map_type"] != "BORIS behaviors coding map"
720
- ):
721
- QMessageBox.critical(self, cfg.programName, f"The file {fileName} is not a BORIS behaviors coding map.")
669
+ if not fileName:
670
+ return
671
+ try:
672
+ self.codingMap = json.loads(open(fileName, "r").read())
673
+ except Exception:
674
+ QMessageBox.critical(self, cfg.programName, f"The file {fileName} is not a behaviors coding map.")
675
+ return
722
676
 
723
- self.cancelMap()
677
+ if "coding_map_type" not in self.codingMap or self.codingMap["coding_map_type"] != "BORIS behaviors coding map":
678
+ QMessageBox.critical(self, cfg.programName, f"The file {fileName} is not a BORIS behaviors coding map.")
679
+ return
724
680
 
725
- self.mapName = self.codingMap["name"]
681
+ self.cancelMap()
726
682
 
727
- self.setWindowTitle(f"{cfg.programName} - Behaviors coding map creator - {self.mapName}")
683
+ self.mapName = self.codingMap["name"]
728
684
 
729
- self.bitmapFileName = True
685
+ self.setWindowTitle(f"{cfg.programName} - Behaviors coding map creator - {self.mapName}")
730
686
 
731
- self.fileName = fileName
687
+ self.bitmapFileName = True
732
688
 
733
- bitmapContent = binascii.a2b_base64(self.codingMap["bitmap"])
689
+ self.fileName = fileName
734
690
 
735
- self.pixmap.loadFromData(bitmapContent)
691
+ bitmapContent = binascii.a2b_base64(self.codingMap["bitmap"])
736
692
 
737
- self.view.setSceneRect(0, 0, self.pixmap.size().width(), self.pixmap.size().height())
738
- self.view.setMinimumHeight(self.pixmap.size().height())
739
- # self.view.setMaximumHeight(self.pixmap.size().height())
740
- pixItem = QGraphicsPixmapItem(self.pixmap)
741
- pixItem.setPos(0, 0)
742
- self.view.scene().addItem(pixItem)
693
+ self.pixmap.loadFromData(bitmapContent)
743
694
 
744
- for key in self.codingMap["areas"]:
745
- areaCode = self.codingMap["areas"][key]["code"]
746
- points = self.codingMap["areas"][key]["geometry"]
695
+ self.view.setSceneRect(0, 0, self.pixmap.size().width(), self.pixmap.size().height())
696
+ self.view.setMinimumHeight(self.pixmap.size().height())
697
+ # self.view.setMaximumHeight(self.pixmap.size().height())
698
+ pixItem = QGraphicsPixmapItem(self.pixmap)
699
+ pixItem.setPos(0, 0)
700
+ self.view.scene().addItem(pixItem)
747
701
 
748
- newPolygon = QPolygonF()
749
- for p in points:
750
- newPolygon.append(QPoint(p[0], p[1]))
702
+ for key in self.codingMap["areas"]:
703
+ areaCode = self.codingMap["areas"][key]["code"]
704
+ points = self.codingMap["areas"][key]["geometry"]
751
705
 
752
- # draw polygon
753
- """polygon = QGraphicsPolygonItem(None, None) if QT_VERSION_STR[0] == "4" else QGraphicsPolygonItem()"""
754
- polygon = QGraphicsPolygonItem()
755
- polygon.setPolygon(newPolygon)
756
- clr = QColor()
757
- clr.setRgba(self.codingMap["areas"][key]["color"])
758
- polygon.setPen(QPen(clr, penWidth, penStyle, Qt.RoundCap, Qt.RoundJoin))
759
- polygon.setBrush(QBrush(clr, Qt.SolidPattern))
706
+ newPolygon = QPolygonF()
707
+ for p in points:
708
+ newPolygon.append(QPoint(p[0], p[1]))
760
709
 
761
- self.view.scene().addItem(polygon)
710
+ # draw polygon
711
+ polygon = QGraphicsPolygonItem()
712
+ polygon.setPolygon(newPolygon)
713
+ clr = QColor()
714
+ clr.setRgba(self.codingMap["areas"][key]["color"])
715
+ polygon.setPen(QPen(clr, penWidth, penStyle, Qt.RoundCap, Qt.RoundJoin))
716
+ polygon.setBrush(QBrush(clr, Qt.SolidPattern))
762
717
 
763
- self.polygonsList2.append([areaCode, polygon])
718
+ self.view.scene().addItem(polygon)
764
719
 
765
- self.btNewArea.setVisible(True)
766
- self.btLoad.setVisible(False)
720
+ self.polygonsList2.append([areaCode, polygon])
767
721
 
768
- self.saveMapAction.setEnabled(True)
769
- self.saveAsMapAction.setEnabled(True)
770
- self.addToProject.setEnabled(True)
771
- self.mapNameAction.setEnabled(True)
722
+ self.btNewArea.setVisible(True)
772
723
 
773
- self.update_area_list()
724
+ for action in (
725
+ self.saveMapAction,
726
+ self.saveAsMapAction,
727
+ self.addToProject,
728
+ self.mapNameAction,
729
+ self.resizeAction,
730
+ ):
731
+ action.setEnabled(True)
774
732
 
775
- else:
776
- self.statusBar().showMessage("No file", 5000)
733
+ self.update_area_list()
777
734
 
778
735
  def make_coding_map_dict(self) -> dict:
779
736
  """
@@ -827,29 +784,26 @@ class BehaviorsMapCreatorWindow(QMainWindow):
827
784
  return False
828
785
 
829
786
  def saveAsMap_clicked(self):
830
-
831
787
  filters = "Behaviors coding map (*.behav_coding_map);;All files (*)"
832
788
 
833
- fn = QFileDialog(self).getSaveFileName(self, "Save behaviors coding map as", "", filters)
834
- self.fileName = fn[0] if type(fn) is tuple else fn
789
+ self.fileName, _ = QFileDialog.getSaveFileName(self, "Save behaviors coding map as", "", filters)
835
790
 
836
- if self.fileName:
837
- if os.path.splitext(self.fileName)[1] != ".behav_coding_map":
838
- self.fileName += ".behav_coding_map"
839
- self.saveMap()
791
+ if not self.fileName:
792
+ return
793
+ if Path(self.fileName).suffix != ".behav_coding_map":
794
+ self.fileName += ".behav_coding_map"
795
+ self.saveMap()
840
796
 
841
797
  def saveMap_clicked(self):
842
-
843
798
  if not self.fileName:
844
- fn = QFileDialog().getSaveFileName(
799
+ self.fileName, _ = QFileDialog().getSaveFileName(
845
800
  self,
846
801
  "Save modifiers map",
847
802
  self.mapName + ".behav_coding_map",
848
803
  "Behaviors coding map (*.behav_coding_map);;All files (*)",
849
804
  )
850
- self.fileName = fn[0] if type(fn) is tuple else fn
851
805
 
852
- if self.fileName and os.path.splitext(self.fileName)[1] != ".behav_coding_map":
806
+ if self.fileName and Path(self.fileName).suffix != ".behav_coding_map":
853
807
  self.fileName += ".behav_coding_map"
854
808
 
855
809
  if self.fileName:
@@ -859,7 +813,7 @@ class BehaviorsMapCreatorWindow(QMainWindow):
859
813
 
860
814
  def newArea(self):
861
815
  if not self.bitmapFileName:
862
- QMessageBox.critical(self, cfg.programName, "A bitmap must be loaded before to define areas")
816
+ QMessageBox.critical(self, cfg.programName, "An image must be loaded before to define areas")
863
817
  return
864
818
 
865
819
  if self.selectedPolygon:
@@ -880,19 +834,15 @@ class BehaviorsMapCreatorWindow(QMainWindow):
880
834
  self.btDeleteArea.setVisible(False)
881
835
 
882
836
  self.statusBar().showMessage(
883
- (
884
- "Click on bitmap to set the vertices of the area with the mouse "
885
- "(right click will cancel the last point)"
886
- )
837
+ ("Click on bitmap to set the vertices of the area with the mouse (right click will cancel the last point)")
887
838
  )
888
839
 
889
840
  def saveArea(self):
890
-
891
841
  if not self.closedPolygon:
892
842
  QMessageBox.critical(
893
843
  self,
894
844
  cfg.programName,
895
- ("You must close your area before saving it.\n" "The last vertex must correspond to the first one."),
845
+ ("You must close your area before saving it.\nThe last vertex must correspond to the first one."),
896
846
  )
897
847
 
898
848
  if len(self.view.points) < 3:
@@ -905,19 +855,20 @@ class BehaviorsMapCreatorWindow(QMainWindow):
905
855
  return
906
856
 
907
857
  # remove all lines
908
- for l in self.view.elList:
909
- self.view.scene().removeItem(l)
858
+ for x in self.view.elList:
859
+ self.view.scene().removeItem(x)
910
860
 
911
861
  # draw polygon
912
862
  self.closedPolygon.setBrush(QBrush(self.areaColor, Qt.SolidPattern))
913
- # self.polygonsList2[self.leAreaCode.text()] = self.closedPolygon
914
863
  self.polygonsList2.append([self.leAreaCode.text(), self.closedPolygon])
915
864
 
916
- self.closedPolygon, self.flagNewArea = None, None
865
+ self.flagNewArea = None
866
+ self.closedPolygon = None
917
867
  self.view._start = 0
918
- self.view.points, self.view.elList = [], []
868
+ self.view.points = []
869
+ self.view.elList = []
919
870
 
920
- for widget in [
871
+ for widget in (
921
872
  self.btSaveArea,
922
873
  self.btCancelAreaCreation,
923
874
  self.lb,
@@ -927,26 +878,26 @@ class BehaviorsMapCreatorWindow(QMainWindow):
927
878
  self.slAlpha,
928
879
  self.btDeleteArea,
929
880
  self.btNewArea,
930
- ]:
881
+ ):
931
882
  widget.setVisible(False)
932
883
 
933
884
  self.btNewArea.setVisible(True)
934
885
 
935
886
  self.leAreaCode.setText("")
936
887
 
888
+ self.update_area_list()
889
+
937
890
  self.flag_map_changed = True
938
891
  self.statusBar().showMessage("New area saved", 5000)
939
892
 
940
- self.update_area_list()
941
-
942
893
  def cancelAreaCreation(self):
943
894
  if self.closedPolygon:
944
895
  self.view.scene().removeItem(self.closedPolygon)
945
896
  self.closedPolygon = None
946
897
 
947
898
  # remove all lines
948
- for l in self.view.elList:
949
- self.view.scene().removeItem(l)
899
+ for x in self.view.elList:
900
+ self.view.scene().removeItem(x)
950
901
 
951
902
  self.view.elList = []
952
903
 
@@ -997,18 +948,15 @@ class BehaviorsMapCreatorWindow(QMainWindow):
997
948
  self.view._start = 0
998
949
  self.view.points = []
999
950
  self.flagNewArea = False
1000
- self.btSaveArea.setVisible(False)
1001
- self.lb.setVisible(False)
1002
951
 
1003
- self.btColor.setVisible(False)
1004
- self.slAlpha.setVisible(False)
952
+ for widget in (self.btSaveArea, self.lb, self.btColor, self.slAlpha, self.leAreaCode, self.btDeleteArea):
953
+ widget.setVisible(False)
954
+
1005
955
  self.btNewArea.setVisible(True)
1006
956
 
1007
- self.leAreaCode.setVisible(False)
1008
957
  self.leAreaCode.setText("")
1009
958
  self.btEditAreaCode.setVisible(False)
1010
959
 
1011
- self.btDeleteArea.setVisible(False)
1012
960
  self.statusBar().showMessage("")
1013
961
 
1014
962
  self.update_area_list()
@@ -1019,63 +967,114 @@ class BehaviorsMapCreatorWindow(QMainWindow):
1019
967
  """
1020
968
  self.flagNewArea = False
1021
969
  self.polygonsList2 = []
970
+ self.closedPolygon = None
971
+ self.selectedPolygon = None
972
+ self.area_list.clear()
1022
973
  self.view.scene().clear()
1023
- self.btLoad.setVisible(False)
1024
974
  self.btDeleteArea.setVisible(False)
1025
975
  self.btNewArea.setVisible(False)
1026
976
  self.saveMapAction.setEnabled(False)
1027
977
  self.saveAsMapAction.setEnabled(False)
1028
978
  self.addToProject.setEnabled(False)
1029
979
  self.mapNameAction.setEnabled(False)
980
+ self.resizeAction.setEnabled(False)
1030
981
  self.statusBar().showMessage("")
1031
982
 
983
+ self.btNewArea.setVisible(True)
984
+ self.btNewArea.setEnabled(True)
985
+
1032
986
  self.flag_map_changed = False
1033
987
 
1034
988
  def loadBitmap(self):
1035
989
  """
1036
990
  load bitmap as background for coding map
1037
- resize bitmap to CODING_MAP_RESIZE_W x CODING_MAP_RESIZE_H defined in config.py
991
+ no more resize bitmap to CODING_MAP_RESIZE_W x CODING_MAP_RESIZE_H defined in config.py
1038
992
  """
1039
993
 
1040
- fn = QFileDialog(self).getOpenFileName(self, "Load bitmap", "", "bitmap files (*.png *.jpg);;All files (*)")
1041
- fileName = fn[0] if type(fn) is tuple else fn
994
+ fileName, _ = QFileDialog.getOpenFileName(self, "Load bitmap", "", "bitmap files (*.png *.jpg);;All files (*)")
1042
995
 
1043
- if fileName:
1044
- self.bitmapFileName = fileName
996
+ if not fileName:
997
+ return
998
+ self.bitmapFileName = fileName
1045
999
 
1046
- self.pixmap.load(self.bitmapFileName)
1000
+ self.pixmap.load(self.bitmapFileName)
1047
1001
 
1048
- # scale image
1002
+ # scale image
1003
+ """
1004
+ if (
1005
+ self.pixmap.size().width() > cfg.CODING_MAP_RESIZE_W
1006
+ or self.pixmap.size().height() > cfg.CODING_MAP_RESIZE_H
1007
+ ):
1008
+ self.pixmap = self.pixmap.scaled(cfg.CODING_MAP_RESIZE_W, cfg.CODING_MAP_RESIZE_H, Qt.KeepAspectRatio)
1009
+ QMessageBox.information(
1010
+ self,
1011
+ cfg.programName,
1012
+ (
1013
+ f"The bitmap was resized to {self.pixmap.size().width()}x{self.pixmap.size().height()} pixels\n"
1014
+ "The original file was not modified"
1015
+ ),
1016
+ )
1017
+ """
1018
+
1019
+ self.view.setSceneRect(0, 0, self.pixmap.size().width(), self.pixmap.size().height())
1020
+ pixitem = QGraphicsPixmapItem(self.pixmap)
1021
+ pixitem.setPos(0, 0)
1022
+ self.view.scene().addItem(pixitem)
1023
+
1024
+ self.btNewArea.setVisible(True)
1025
+
1026
+ self.saveMapAction.setEnabled(True)
1027
+ self.saveAsMapAction.setEnabled(True)
1028
+ self.addToProject.setEnabled(True)
1029
+ self.mapNameAction.setEnabled(True)
1030
+ self.resizeAction.setEnabled(True)
1031
+
1032
+ self.statusBar().showMessage("""Click "New behavior area" to create a new behavior area""")
1033
+
1034
+ self.flag_map_changed = True
1035
+
1036
+ def resize_clicked(self):
1037
+ """
1038
+ resize the bitmap
1039
+ """
1040
+
1041
+ if self.polygonsList2:
1049
1042
  if (
1050
- self.pixmap.size().width() > cfg.CODING_MAP_RESIZE_W
1051
- or self.pixmap.size().height() > cfg.CODING_MAP_RESIZE_H
1052
- ):
1053
- self.pixmap = self.pixmap.scaled(cfg.CODING_MAP_RESIZE_W, cfg.CODING_MAP_RESIZE_H, Qt.KeepAspectRatio)
1054
- QMessageBox.information(
1055
- self,
1056
- cfg.programName,
1057
- (
1058
- f"The bitmap was resized to {self.pixmap.size().width()}x{self.pixmap.size().height()} pixels\n"
1059
- "The original file was not modified"
1060
- ),
1043
+ dialog.MessageDialog(
1044
+ "BORIS - Behaviors map creator",
1045
+ "The map contains modifiers. Erase all modifiers",
1046
+ (cfg.NO, cfg.YES),
1061
1047
  )
1048
+ == cfg.NO
1049
+ ):
1050
+ return
1062
1051
 
1063
- self.view.setSceneRect(0, 0, self.pixmap.size().width(), self.pixmap.size().height())
1064
- pixitem = QGraphicsPixmapItem(self.pixmap)
1065
- pixitem.setPos(0, 0)
1066
- self.view.scene().addItem(pixitem)
1052
+ integer, ok = QInputDialog.getInt(
1053
+ self, "Resize image", "New horizontal size (in pixels)", value=self.pixmap.size().width(), minValue=100, maxValue=3048, step=10
1054
+ )
1055
+ if not ok:
1056
+ return
1067
1057
 
1068
- self.btNewArea.setVisible(True)
1058
+ self.cancelMap()
1069
1059
 
1070
- self.btLoad.setVisible(False)
1071
- self.saveMapAction.setEnabled(True)
1072
- self.saveAsMapAction.setEnabled(True)
1073
- self.addToProject.setEnabled(True)
1074
- self.mapNameAction.setEnabled(True)
1060
+ h = int(self.pixmap.size().width() / (self.pixmap.size().width() / integer))
1075
1061
 
1076
- self.statusBar().showMessage("""Click "New behavior area" to create a new behavior area""")
1062
+ self.pixmap = self.pixmap.scaled(integer, h, Qt.KeepAspectRatio)
1077
1063
 
1078
- self.flag_map_changed = True
1064
+ self.view.scene().clear()
1065
+ self.view.setSceneRect(0, 0, self.pixmap.size().width(), self.pixmap.size().height())
1066
+ pixitem = QGraphicsPixmapItem(self.pixmap)
1067
+ pixitem.setPos(0, 0)
1068
+ self.view.scene().addItem(pixitem)
1069
+
1070
+ QMessageBox.information(
1071
+ self,
1072
+ cfg.programName,
1073
+ (
1074
+ f"The bitmap was resized to {self.pixmap.size().width()}x{self.pixmap.size().height()} pixels\n"
1075
+ "The original file was not modified"
1076
+ ),
1077
+ )
1079
1078
 
1080
1079
 
1081
1080
  def behaviors_coding_map_creator(self):
@@ -1085,7 +1084,7 @@ def behaviors_coding_map_creator(self):
1085
1084
 
1086
1085
  if not self.project:
1087
1086
  QMessageBox.warning(
1088
- self, cfg.programName, "No project found", QMessageBox.Ok | QMessageBox.Default, QMessageBox.NoButton
1087
+ self, cfg.programName, "Create or open a project first", QMessageBox.Ok | QMessageBox.Default, QMessageBox.NoButton
1089
1088
  )
1090
1089
  return
1091
1090
 
@@ -1101,11 +1100,11 @@ def behaviors_coding_map_creator(self):
1101
1100
 
1102
1101
 
1103
1102
  if __name__ == "__main__":
1104
-
1105
1103
  import sys
1106
1104
 
1107
1105
  app = QApplication(sys.argv)
1108
1106
  window = BehaviorsMapCreatorWindow(["North zone", "East zone", "South zone", "West zone"])
1109
- window.resize(cfg.CODING_MAP_RESIZE_W, cfg.CODING_MAP_RESIZE_H)
1107
+ window.bcm_list = []
1108
+ gui_utilities.resize_center(app, window, cfg.CODING_MAP_RESIZE_W, cfg.CODING_MAP_RESIZE_H)
1110
1109
  window.show()
1111
- sys.exit(app.exec_())
1110
+ sys.exit(app.exec())