boris-behav-obs 9.7.7__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 (109) hide show
  1. boris/__init__.py +26 -0
  2. boris/__main__.py +25 -0
  3. boris/about.py +143 -0
  4. boris/add_modifier.py +635 -0
  5. boris/add_modifier_ui.py +303 -0
  6. boris/advanced_event_filtering.py +455 -0
  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 +1110 -0
  18. boris/behavior_binary_table.py +305 -0
  19. boris/behaviors_coding_map.py +239 -0
  20. boris/boris_cli.py +340 -0
  21. boris/cmd_arguments.py +49 -0
  22. boris/coding_pad.py +280 -0
  23. boris/config.py +785 -0
  24. boris/config_file.py +356 -0
  25. boris/connections.py +409 -0
  26. boris/converters.py +333 -0
  27. boris/converters_ui.py +225 -0
  28. boris/cooccurence.py +250 -0
  29. boris/core.py +5901 -0
  30. boris/core_qrc.py +15958 -0
  31. boris/core_ui.py +1107 -0
  32. boris/db_functions.py +324 -0
  33. boris/dev.py +134 -0
  34. boris/dialog.py +1108 -0
  35. boris/duration_widget.py +238 -0
  36. boris/edit_event.py +245 -0
  37. boris/edit_event_ui.py +233 -0
  38. boris/event_operations.py +1040 -0
  39. boris/events_cursor.py +61 -0
  40. boris/events_snapshots.py +596 -0
  41. boris/exclusion_matrix.py +141 -0
  42. boris/export_events.py +1006 -0
  43. boris/export_observation.py +1203 -0
  44. boris/external_processes.py +332 -0
  45. boris/geometric_measurement.py +941 -0
  46. boris/gui_utilities.py +135 -0
  47. boris/image_overlay.py +72 -0
  48. boris/import_observations.py +242 -0
  49. boris/ipc_mpv.py +325 -0
  50. boris/irr.py +634 -0
  51. boris/latency.py +244 -0
  52. boris/measurement_widget.py +161 -0
  53. boris/media_file.py +115 -0
  54. boris/menu_options.py +213 -0
  55. boris/modifier_coding_map_creator.py +1013 -0
  56. boris/modifiers_coding_map.py +157 -0
  57. boris/mpv.py +2016 -0
  58. boris/mpv2.py +2193 -0
  59. boris/observation.py +1453 -0
  60. boris/observation_operations.py +2538 -0
  61. boris/observation_ui.py +679 -0
  62. boris/observations_list.py +337 -0
  63. boris/otx_parser.py +442 -0
  64. boris/param_panel.py +201 -0
  65. boris/param_panel_ui.py +305 -0
  66. boris/player_dock_widget.py +198 -0
  67. boris/plot_data_module.py +536 -0
  68. boris/plot_events.py +634 -0
  69. boris/plot_events_rt.py +237 -0
  70. boris/plot_spectrogram_rt.py +316 -0
  71. boris/plot_waveform_rt.py +230 -0
  72. boris/plugins.py +431 -0
  73. boris/portion/__init__.py +31 -0
  74. boris/portion/const.py +95 -0
  75. boris/portion/dict.py +365 -0
  76. boris/portion/func.py +52 -0
  77. boris/portion/interval.py +581 -0
  78. boris/portion/io.py +181 -0
  79. boris/preferences.py +510 -0
  80. boris/preferences_ui.py +770 -0
  81. boris/project.py +2007 -0
  82. boris/project_functions.py +2041 -0
  83. boris/project_import_export.py +1096 -0
  84. boris/project_ui.py +794 -0
  85. boris/qrc_boris.py +10389 -0
  86. boris/qrc_boris5.py +2579 -0
  87. boris/select_modifiers.py +312 -0
  88. boris/select_observations.py +210 -0
  89. boris/select_subj_behav.py +286 -0
  90. boris/state_events.py +197 -0
  91. boris/subjects_pad.py +106 -0
  92. boris/synthetic_time_budget.py +290 -0
  93. boris/time_budget_functions.py +1136 -0
  94. boris/time_budget_widget.py +1039 -0
  95. boris/transitions.py +365 -0
  96. boris/utilities.py +1810 -0
  97. boris/version.py +24 -0
  98. boris/video_equalizer.py +159 -0
  99. boris/video_equalizer_ui.py +248 -0
  100. boris/video_operations.py +310 -0
  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.7.dist-info/METADATA +139 -0
  105. boris_behav_obs-9.7.7.dist-info/RECORD +109 -0
  106. boris_behav_obs-9.7.7.dist-info/WHEEL +5 -0
  107. boris_behav_obs-9.7.7.dist-info/entry_points.txt +2 -0
  108. boris_behav_obs-9.7.7.dist-info/licenses/LICENSE.TXT +674 -0
  109. boris_behav_obs-9.7.7.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1110 @@
1
+ """
2
+ BORIS
3
+ Behavioral Observation Research Interactive Software
4
+ Copyright 2012-2025 Olivier Friard
5
+
6
+ This file is part of BORIS.
7
+
8
+ BORIS is free software; you can redistribute it and/or modify
9
+ it under the terms of the GNU General Public License as published by
10
+ the Free Software Foundation; either version 3 of the License, or
11
+ any later version.
12
+
13
+ BORIS is distributed in the hope that it will be useful,
14
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ GNU General Public License for more details.
17
+
18
+ You should have received a copy of the GNU General Public License
19
+ along with this program; if not see <http://www.gnu.org/licenses/>.
20
+
21
+ """
22
+
23
+ import binascii
24
+ import io
25
+ import json
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 (
32
+ QApplication,
33
+ QColorDialog,
34
+ QFileDialog,
35
+ QFrame,
36
+ QGraphicsEllipseItem,
37
+ QGraphicsLineItem,
38
+ QGraphicsPixmapItem,
39
+ QGraphicsPolygonItem,
40
+ QGraphicsScene,
41
+ QGraphicsView,
42
+ QHBoxLayout,
43
+ QInputDialog,
44
+ QLabel,
45
+ QLineEdit,
46
+ QListWidget,
47
+ QMainWindow,
48
+ QMessageBox,
49
+ QPushButton,
50
+ QSizePolicy,
51
+ QSlider,
52
+ QSpacerItem,
53
+ QSplitter,
54
+ QVBoxLayout,
55
+ QWidget,
56
+ )
57
+
58
+ from . import config as cfg
59
+ from . import dialog
60
+ from . import utilities as util
61
+
62
+ designColor = QColor(255, 0, 0, 128) # red opacity: 50%
63
+ penWidth = 0
64
+ penStyle = Qt.NoPen
65
+ selectedBrush = QBrush()
66
+ selectedBrush.setStyle(Qt.SolidPattern)
67
+ selectedBrush.setColor(QColor(255, 255, 0, 255))
68
+
69
+
70
+ class BehaviorsMapCreatorWindow(QMainWindow):
71
+ signal_add_to_project = Signal(dict)
72
+
73
+ class View(QGraphicsView):
74
+ """
75
+ class for handling mousepress event in QGraphicsView
76
+ """
77
+
78
+ mousePress = Signal(QMouseEvent)
79
+
80
+ def mousePressEvent(self, event):
81
+ self.mousePress.emit(event)
82
+
83
+ _start = 0
84
+ elList, points = [], []
85
+
86
+ def __init__(self, parent):
87
+ QGraphicsView.__init__(self, parent)
88
+ self.setBackgroundBrush(QColor(128, 128, 128))
89
+ self.setScene(QGraphicsScene(self))
90
+ self.scene().update()
91
+
92
+ bitmapFileName, mapName, fileName = "", "", ""
93
+ flagNewArea, flag_map_changed = False, False
94
+ polygonsList2 = []
95
+ areaColor = QColor("lime")
96
+
97
+ def __init__(self, arg):
98
+ self.codes_list = arg
99
+
100
+ super(BehaviorsMapCreatorWindow, self).__init__()
101
+
102
+ self.pixmap = QPixmap()
103
+ self.closedPolygon = None
104
+ self.selectedPolygon = None
105
+
106
+ self.setWindowTitle("BORIS - Behaviors coding map creator")
107
+
108
+ self.newMapAction = QAction(QIcon(), "&New behaviors coding map", self)
109
+ self.newMapAction.setShortcut("Ctrl+N")
110
+ self.newMapAction.setStatusTip("Create a new behaviors coding map")
111
+ self.newMapAction.triggered.connect(self.newMap)
112
+
113
+ self.openMapAction = QAction(QIcon(), "&Open a behaviors coding map", self)
114
+ self.openMapAction.setShortcut("Ctrl+O")
115
+ self.openMapAction.setStatusTip("Open a behaviors coding map")
116
+ self.openMapAction.triggered.connect(self.openMap)
117
+
118
+ self.saveMapAction = QAction(QIcon(), "&Save the behavior coding map", self)
119
+ self.saveMapAction.setShortcut("Ctrl+S")
120
+ self.saveMapAction.setStatusTip("Save the behavior coding map")
121
+ self.saveMapAction.setEnabled(False)
122
+ self.saveMapAction.triggered.connect(self.saveMap_clicked)
123
+
124
+ self.saveAsMapAction = QAction(QIcon(), "Save the behavior coding map as ...", self)
125
+ self.saveAsMapAction.setStatusTip("Save the behavior coding map as ...")
126
+ self.saveAsMapAction.setEnabled(False)
127
+ self.saveAsMapAction.triggered.connect(self.saveAsMap_clicked)
128
+
129
+ self.mapNameAction = QAction(QIcon(), "&Edit name of behaviors coding map", self)
130
+ self.mapNameAction.setShortcut("Ctrl+M")
131
+ self.mapNameAction.setStatusTip("Edit name of behaviors coding map")
132
+ self.mapNameAction.setEnabled(False)
133
+ self.mapNameAction.triggered.connect(self.mapName_clicked)
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
+ self.addToProject = QAction(QIcon(), "Add coding map to project", self)
141
+ self.addToProject.setStatusTip("Add coding map to project")
142
+ self.addToProject.setEnabled(False)
143
+ self.addToProject.triggered.connect(self.add_to_project)
144
+
145
+ self.exitAction = QAction(QIcon(), "&Close", self)
146
+ self.exitAction.setStatusTip(cfg.CLOSE)
147
+ self.exitAction.triggered.connect(self.close)
148
+
149
+ menubar = self.menuBar()
150
+ fileMenu = menubar.addMenu("&File")
151
+ fileMenu.addAction(self.newMapAction)
152
+ fileMenu.addAction(self.openMapAction)
153
+ fileMenu.addAction(self.saveMapAction)
154
+ fileMenu.addAction(self.saveAsMapAction)
155
+ fileMenu.addSeparator()
156
+ fileMenu.addAction(self.mapNameAction)
157
+ fileMenu.addSeparator()
158
+ fileMenu.addAction(self.resizeAction)
159
+ fileMenu.addSeparator()
160
+ fileMenu.addAction(self.addToProject)
161
+ fileMenu.addSeparator()
162
+ fileMenu.addAction(self.exitAction)
163
+
164
+ splitter1 = QSplitter(Qt.Vertical)
165
+
166
+ self.view = self.View(self)
167
+ self.view.mousePress.connect(self.viewMousePressEvent)
168
+ splitter1.addWidget(self.view)
169
+
170
+ vlayout_list = QVBoxLayout()
171
+ vlayout_list.addWidget(QLabel("Defined area"))
172
+
173
+ self.area_list = QListWidget(self)
174
+ self.area_list.itemClicked.connect(self.area_list_item_click)
175
+ vlayout_list.addWidget(self.area_list)
176
+ w = QWidget()
177
+ w.setLayout(vlayout_list)
178
+ splitter1.addWidget(w)
179
+ splitter1.setSizes([300, 100])
180
+ splitter1.setStretchFactor(2, 8)
181
+
182
+ hlayout_cmd = QHBoxLayout()
183
+
184
+ self.btNewArea = QPushButton("New behavior area", self)
185
+ self.btNewArea.clicked.connect(self.newArea)
186
+ self.btNewArea.setVisible(False)
187
+ hlayout_cmd.addWidget(self.btNewArea)
188
+
189
+ self.btSaveArea = QPushButton("Save the behavior area", self)
190
+ self.btSaveArea.clicked.connect(self.saveArea)
191
+ self.btSaveArea.setVisible(False)
192
+ hlayout_cmd.addWidget(self.btSaveArea)
193
+
194
+ self.btCancelAreaCreation = QPushButton("Cancel", self)
195
+ self.btCancelAreaCreation.clicked.connect(self.cancelAreaCreation)
196
+ self.btCancelAreaCreation.setVisible(False)
197
+ hlayout_cmd.addWidget(self.btCancelAreaCreation)
198
+
199
+ hlayout_cmd.addItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
200
+
201
+ self.btDeleteArea = QPushButton("Delete selected behavior area", self)
202
+ self.btDeleteArea.clicked.connect(self.deleteArea)
203
+ self.btDeleteArea.setVisible(False)
204
+ hlayout_cmd.addWidget(self.btDeleteArea)
205
+
206
+ hlayout_area = QHBoxLayout()
207
+
208
+ self.lb = QLabel("Behavior")
209
+ self.lb.setVisible(False)
210
+ hlayout_area.addWidget(self.lb)
211
+
212
+ self.leAreaCode = QLineEdit(self)
213
+ self.leAreaCode.setReadOnly(True)
214
+ self.leAreaCode.setVisible(False)
215
+ self.leAreaCode.setEnabled(False)
216
+ hlayout_area.addWidget(self.leAreaCode)
217
+
218
+ self.btEditAreaCode = QPushButton("Select behavior")
219
+ self.btEditAreaCode.clicked.connect(self.edit_area_code)
220
+ self.btEditAreaCode.setVisible(False)
221
+ hlayout_area.addWidget(self.btEditAreaCode)
222
+
223
+ self.btColor = QPushButton(clicked=self.chooseColor)
224
+ self.btColor.setVisible(False)
225
+ self.btColor.setStyleSheet(f"QWidget {{background-color:{self.areaColor.name()}}}")
226
+ hlayout_area.addWidget(self.btColor)
227
+
228
+ self.slAlpha = QSlider(Qt.Horizontal)
229
+ self.slAlpha.setRange(20, 100)
230
+ self.slAlpha.setValue(50)
231
+ self.slAlpha.valueChanged.connect(self.slAlpha_changed)
232
+ self.slAlpha.setVisible(False)
233
+ hlayout_area.addWidget(self.slAlpha)
234
+
235
+ self.slAlpha_changed(50)
236
+
237
+ vlayout_frame = QVBoxLayout()
238
+ vlayout_frame.addLayout(hlayout_cmd)
239
+ vlayout_frame.addLayout(hlayout_area)
240
+ vlayout_frame.addItem(QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding))
241
+
242
+ frame = QFrame()
243
+ frame.setFrameStyle(QFrame.Panel | QFrame.Plain)
244
+ frame.setMinimumHeight(120)
245
+ frame.setMaximumHeight(120)
246
+
247
+ frame.setLayout(vlayout_frame)
248
+
249
+ vlayout = QVBoxLayout()
250
+
251
+ vlayout.addWidget(splitter1)
252
+ vlayout.addWidget(frame)
253
+
254
+ main_widget = QWidget(self)
255
+ main_widget.setLayout(vlayout)
256
+ self.setCentralWidget(main_widget)
257
+
258
+ self.statusBar().showMessage("")
259
+
260
+ def add_to_project(self, item):
261
+ """
262
+ add coding map to project
263
+ """
264
+
265
+ map_dict = self.make_coding_map_dict()
266
+ if map_dict["areas"] == {}:
267
+ QMessageBox.critical(
268
+ self,
269
+ cfg.programName,
270
+ (
271
+ "The behaviors coding map does not contain any behavior area.<br>"
272
+ "Add some behavior areas before adding the coding map to the project"
273
+ ),
274
+ )
275
+
276
+ return
277
+
278
+ self.signal_add_to_project.emit(map_dict)
279
+
280
+ def area_list_item_click(self, item):
281
+ """
282
+ select the polygon corresponding to the clicked area
283
+ """
284
+
285
+ if self.selectedPolygon:
286
+ self.selectedPolygon.setPen(QPen(designColor, penWidth, penStyle, Qt.RoundCap, Qt.RoundJoin))
287
+ self.selectedPolygon = None
288
+ self.selectedPolygonMemBrush = None
289
+
290
+ idx = int(item.text().split("#")[1])
291
+ ac, pg = self.polygonsList2[idx]
292
+
293
+ self.selectedPolygon = pg
294
+
295
+ self.selectedPolygonMemBrush = self.selectedPolygon.brush()
296
+
297
+ self.selectedPolygon.setPen(QPen(QColor(255, 0, 0, 255), 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
298
+
299
+ self.leAreaCode.setText(ac)
300
+
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)
310
+
311
+ self.areaColor = self.selectedPolygon.brush().color()
312
+ self.btColor.setStyleSheet(f"QWidget {{background-color:{self.selectedPolygon.brush().color().name()}}}")
313
+ self.slAlpha.setValue(int(self.areaColor.alpha() / 255 * 100))
314
+
315
+ def edit_area_code(self):
316
+ """
317
+ select a behavior
318
+ """
319
+
320
+ if self.leAreaCode.text() in self.codes_list:
321
+ code_index = self.codes_list.index(self.leAreaCode.text())
322
+ else:
323
+ code_index = 0
324
+
325
+ item, ok = QInputDialog.getItem(self, "Select a behavior", "Available behaviors", self.codes_list, code_index, False)
326
+ self.leAreaCode.setText(item)
327
+
328
+ if self.selectedPolygon:
329
+ for idx, area in enumerate(self.polygonsList2):
330
+ ac, pg = area
331
+ if pg == self.selectedPolygon:
332
+ self.polygonsList2[idx] = [self.leAreaCode.text(), pg]
333
+ break
334
+
335
+ self.update_area_list()
336
+
337
+ def slAlpha_changed(self, val):
338
+ """
339
+ opacity slider value changed
340
+ """
341
+
342
+ self.btColor.setText(f"Opacity: {val} %")
343
+ self.areaColor.setAlpha(int(val / 100 * 255))
344
+
345
+ if self.selectedPolygon:
346
+ self.selectedPolygon.setBrush(self.areaColor)
347
+ for idx, area in enumerate(self.polygonsList2):
348
+ ac, pg = area
349
+ if pg == self.selectedPolygon:
350
+ pg.setBrush(self.areaColor)
351
+ self.polygonsList2[idx] = [ac, pg]
352
+ break
353
+
354
+ if self.closedPolygon:
355
+ self.closedPolygon.setBrush(self.areaColor)
356
+
357
+ def chooseColor(self):
358
+ """
359
+ area color button clicked
360
+ """
361
+ cd = QColorDialog()
362
+ cd.setWindowFlags(Qt.WindowStaysOnTopHint)
363
+ cd.setOptions(QColorDialog.DontUseNativeDialog)
364
+
365
+ if cd.exec():
366
+ self.areaColor = cd.currentColor()
367
+ self.btColor.setStyleSheet(f"QWidget {{background-color:{self.areaColor.name()}}}")
368
+ self.areaColor.setAlpha(int(self.slAlpha.value() / 100 * 255))
369
+
370
+ if self.selectedPolygon:
371
+ self.selectedPolygon.setBrush(self.areaColor)
372
+
373
+ for idx, area in enumerate(self.polygonsList2):
374
+ ac, pg = area
375
+ if pg == self.selectedPolygon:
376
+ pg.setBrush(self.areaColor)
377
+ self.polygonsList2[idx] = [ac, pg]
378
+ break
379
+
380
+ if self.closedPolygon:
381
+ self.closedPolygon.setBrush(self.areaColor)
382
+
383
+ def closeEvent(self, event):
384
+ if self.flag_map_changed:
385
+ response = dialog.MessageDialog(
386
+ "BORIS - Behaviors map creator",
387
+ "What to do about the current unsaved behaviors coding map?",
388
+ [cfg.SAVE, cfg.DISCARD, cfg.CANCEL],
389
+ )
390
+
391
+ if response == cfg.SAVE:
392
+ if not self.saveMap_clicked():
393
+ event.ignore()
394
+
395
+ if response == cfg.CANCEL:
396
+ event.ignore()
397
+ return
398
+
399
+ self.flag_map_changed = False
400
+ event.accept()
401
+
402
+ def viewMousePressEvent(self, event):
403
+ """
404
+ check if area selected with mouse
405
+ """
406
+
407
+ def add_polygon():
408
+ # create polygon
409
+ newPolygon = QPolygonF()
410
+ for p in self.view.points:
411
+ newPolygon.append(QPoint(p[0], p[1]))
412
+
413
+ # draw polygon a red polygon
414
+ self.closedPolygon = QGraphicsPolygonItem(newPolygon)
415
+ self.closedPolygon.setPen(QPen(designColor, penWidth, penStyle, Qt.RoundCap, Qt.RoundJoin))
416
+ self.closedPolygon.setBrush(self.areaColor)
417
+ self.view.scene().addItem(self.closedPolygon)
418
+
419
+ if not self.bitmapFileName:
420
+ return
421
+
422
+ test = self.view.mapToScene(event.pos()).toPoint() # coordinates of clicked point
423
+
424
+ if test.x() < 0 or test.y() < 0 or test.x() > self.pixmap.size().width() or test.y() > self.pixmap.size().height():
425
+ return
426
+
427
+ if not self.flagNewArea: # test clicked point for areas
428
+ # reset selected polygon to default pen
429
+ if self.selectedPolygon:
430
+ self.selectedPolygon.setPen(QPen(designColor, penWidth, penStyle, Qt.RoundCap, Qt.RoundJoin))
431
+ self.selectedPolygon = None
432
+ self.selectedPolygonMemBrush = None
433
+
434
+ idx = 0
435
+ for areaCode, pg in self.polygonsList2:
436
+ if pg.contains(test):
437
+ self.selectedPolygon = pg
438
+ self.selectedPolygonMemBrush = self.selectedPolygon.brush()
439
+ self.selectedPolygon.setPen(QPen(QColor(255, 0, 0, 255), 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
440
+ self.leAreaCode.setText(areaCode)
441
+
442
+ for widget in (self.lb, self.leAreaCode, self.btEditAreaCode, self.btColor, self.slAlpha, self.btDeleteArea):
443
+ widget.setVisible(True)
444
+
445
+ self.areaColor = self.selectedPolygon.brush().color()
446
+ self.btColor.setStyleSheet(f"QWidget {{background-color:{self.selectedPolygon.brush().color().name()}}}")
447
+
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)
453
+
454
+ break
455
+ idx += 1
456
+
457
+ if not self.selectedPolygon:
458
+ self.leAreaCode.setVisible(False)
459
+ self.lb.setVisible(False)
460
+ self.btDeleteArea.setVisible(False)
461
+ self.btEditAreaCode.setVisible(False)
462
+ self.btColor.setVisible(False)
463
+ self.slAlpha.setVisible(False)
464
+ return
465
+
466
+ # delete last line item
467
+ if (event.buttons() & Qt.RightButton) and not self.closedPolygon:
468
+ if self.view.points:
469
+ self.view.points = self.view.points[0:-1]
470
+
471
+ if self.view.points:
472
+ self.view._start = QPoint(self.view.points[-1][0], self.view.points[-1][1])
473
+ else:
474
+ self.view._start = None
475
+
476
+ # remove graphical elements
477
+ if self.view.elList:
478
+ self.view.scene().removeItem(self.view.elList[-1])
479
+ self.view.elList = self.view.elList[0:-1]
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
+
495
+ # add line item
496
+ if event.buttons() == Qt.LeftButton and not self.closedPolygon:
497
+ if self.view._start:
498
+ end = test
499
+
500
+ # test is polygon is crossed
501
+ if len(self.view.points) >= 3:
502
+ for idx, _ in enumerate(self.view.points[:-2]):
503
+ if util.intersection(
504
+ self.view.points[idx],
505
+ self.view.points[idx + 1],
506
+ self.view.points[-1],
507
+ (int(end.x()), int(end.y())),
508
+ ):
509
+ QMessageBox.critical(self, "", "The polygon edges can not be intersected")
510
+ return
511
+
512
+ # test if polygon closed (dist min 10 px)
513
+ if abs(end.x() - self.view.points[0][0]) < 10 and abs(end.y() - self.view.points[0][1]) < 10:
514
+ line = QGraphicsLineItem(QLineF(self.view._start, QPoint(self.view.points[0][0], self.view.points[0][1])))
515
+ line.setPen(QPen(designColor, penWidth, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
516
+
517
+ self.view.scene().addItem(line)
518
+ self.view.elList.append(line)
519
+
520
+ self.statusBar().showMessage("Area completed")
521
+
522
+ # create polygon
523
+ add_polygon()
524
+ return
525
+
526
+ self.view.points.append((int(end.x()), int(end.y())))
527
+
528
+ line = QGraphicsLineItem(QLineF(self.view._start, end))
529
+ line.setPen(QPen(designColor, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
530
+ self.view.scene().addItem(line)
531
+ self.view.elList.append(line)
532
+
533
+ self.view._start = test
534
+
535
+ else: # first point
536
+ self.view._start = test
537
+
538
+ ellipse = QGraphicsEllipseItem(self.view._start.x(), self.view._start.y(), 3, 3)
539
+ ellipse.setPen(QPen(designColor, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
540
+
541
+ brush = QBrush()
542
+ brush.setStyle(Qt.SolidPattern)
543
+ brush.setColor(designColor)
544
+ ellipse.setBrush(brush)
545
+
546
+ self.view.scene().addItem(ellipse)
547
+ self.view.elList.append(ellipse)
548
+
549
+ self.view.points.append((self.view._start.x(), self.view._start.y()))
550
+
551
+ # automatically close the polygon
552
+ if event.buttons() == Qt.MiddleButton and not self.closedPolygon:
553
+ # add first point as last point of polygon
554
+
555
+ # test is polygon is crossed
556
+
557
+ if len(self.view.points) >= 3:
558
+ for idx, _ in enumerate(self.view.points[1:-2]):
559
+ if util.intersection(
560
+ self.view.points[idx],
561
+ self.view.points[idx + 1],
562
+ self.view.points[-1],
563
+ self.view.points[0],
564
+ ):
565
+ QMessageBox.critical(self, "", "The polygon edges can not be intersected")
566
+ return
567
+
568
+ line = QGraphicsLineItem(
569
+ QLineF(self.view.points[-1][0], self.view.points[-1][1], self.view.points[0][0], self.view.points[0][1])
570
+ )
571
+ line.setPen(QPen(designColor, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
572
+ self.view.scene().addItem(line)
573
+ self.view.elList.append(line)
574
+
575
+ self.view.points.append(self.view.points[0])
576
+ add_polygon()
577
+ self.statusBar().showMessage("Area completed")
578
+
579
+ def mapName_clicked(self):
580
+ """
581
+ Edit map name
582
+ """
583
+
584
+ while True:
585
+ map_name, ok = QInputDialog.getText(
586
+ self, "Behaviors coding map name", "Enter a name for the coding map", QLineEdit.Normal, self.mapName
587
+ )
588
+ if map_name.upper() in self.bcm_list:
589
+ QMessageBox.critical(
590
+ self,
591
+ "",
592
+ (
593
+ "The name for the new coding map already exists.<br>"
594
+ f"{', '.join(self.bcm_list)} are already defined.<br>"
595
+ "To reuse the same name the existing coding map must be deleted (File > Edit project)"
596
+ ),
597
+ )
598
+ if ok and map_name and map_name.upper() not in self.bcm_list:
599
+ self.mapName = map_name
600
+ self.setWindowTitle(f"{cfg.programName} - Behaviors coding map creator - {self.mapName}")
601
+ break
602
+ if not ok:
603
+ return
604
+
605
+ def newMap(self):
606
+ """
607
+ create a new map
608
+ """
609
+
610
+ if self.flag_map_changed:
611
+ response = dialog.MessageDialog(
612
+ cfg.programName + " - Behaviors coding map creator",
613
+ "What to do about the current unsaved coding map?",
614
+ [cfg.SAVE, cfg.DISCARD, cfg.CANCEL],
615
+ )
616
+
617
+ if response == cfg.SAVE:
618
+ if not self.saveMap_clicked():
619
+ return
620
+
621
+ if response == cfg.CANCEL:
622
+ return
623
+
624
+ self.cancelMap()
625
+
626
+ while True:
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
630
+ if map_name.upper() in self.bcm_list:
631
+ QMessageBox.critical(
632
+ self,
633
+ "",
634
+ (
635
+ "The name for the new coding map already exists.<br>"
636
+ f"{', '.join(self.bcm_list)} are already defined.<br>"
637
+ "To reuse the same name the existing coding map must be deleted (File > Edit project)"
638
+ ),
639
+ )
640
+ if ok and map_name and map_name.upper() not in self.bcm_list:
641
+ self.mapName = map_name
642
+ break
643
+
644
+ self.setWindowTitle(f"{cfg.programName} - Behaviors coding map creator tool - {self.mapName}")
645
+
646
+ self.loadBitmap()
647
+
648
+ def openMap(self):
649
+ """
650
+ open a coding map from file
651
+
652
+ load bitmap from data
653
+ show it in view scene
654
+ """
655
+ if self.flag_map_changed:
656
+ response = dialog.MessageDialog(
657
+ cfg.programName + " - Behaviors coding map creator",
658
+ "What to do about the current unsaved coding map?",
659
+ ["Save", "Discard", "Cancel"],
660
+ )
661
+
662
+ if (response == "Save" and not self.saveMap_clicked()) or (response == "Cancel"):
663
+ return
664
+
665
+ fileName, _ = QFileDialog(self).getOpenFileName(
666
+ self, "Open a behaviors coding map", "", "Behaviors coding map (*.behav_coding_map);;All files (*)"
667
+ )
668
+
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
676
+
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
680
+
681
+ self.cancelMap()
682
+
683
+ self.mapName = self.codingMap["name"]
684
+
685
+ self.setWindowTitle(f"{cfg.programName} - Behaviors coding map creator - {self.mapName}")
686
+
687
+ self.bitmapFileName = True
688
+
689
+ self.fileName = fileName
690
+
691
+ bitmapContent = binascii.a2b_base64(self.codingMap["bitmap"])
692
+
693
+ self.pixmap.loadFromData(bitmapContent)
694
+
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)
701
+
702
+ for key in self.codingMap["areas"]:
703
+ areaCode = self.codingMap["areas"][key]["code"]
704
+ points = self.codingMap["areas"][key]["geometry"]
705
+
706
+ newPolygon = QPolygonF()
707
+ for p in points:
708
+ newPolygon.append(QPoint(p[0], p[1]))
709
+
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))
717
+
718
+ self.view.scene().addItem(polygon)
719
+
720
+ self.polygonsList2.append([areaCode, polygon])
721
+
722
+ self.btNewArea.setVisible(True)
723
+
724
+ for action in (
725
+ self.saveMapAction,
726
+ self.saveAsMapAction,
727
+ self.addToProject,
728
+ self.mapNameAction,
729
+ self.resizeAction,
730
+ ):
731
+ action.setEnabled(True)
732
+
733
+ self.update_area_list()
734
+
735
+ def make_coding_map_dict(self) -> dict:
736
+ """
737
+ create the coding map dictionary
738
+ the image is encoded in bse64 format
739
+ """
740
+ map_dict = {"coding_map_type": "BORIS behaviors coding map", "name": self.mapName, "areas": {}}
741
+
742
+ for ac, pg in self.polygonsList2:
743
+ if not map_dict["areas"]:
744
+ idx = 0
745
+ else:
746
+ idx = max(map_dict["areas"].keys()) + 1
747
+
748
+ points = []
749
+ for p in range(pg.polygon().count()):
750
+ points.append([int(pg.polygon().value(p).x()), int(pg.polygon().value(p).y())])
751
+
752
+ map_dict["areas"][idx] = {"code": ac, "geometry": points, "color": pg.brush().color().rgba()}
753
+
754
+ map_dict["areas"] = json.loads(json.dumps(map_dict["areas"]))
755
+
756
+ # Save QPixmap to QByteArray via QBuffer.
757
+ byte_array = QByteArray()
758
+ buffer = QBuffer(byte_array)
759
+ buffer.open(QIODevice.WriteOnly)
760
+ self.pixmap.save(buffer, "PNG")
761
+ string_io = io.BytesIO(byte_array)
762
+ string_io.seek(0)
763
+
764
+ # add codified bitmap
765
+ map_dict["bitmap"] = binascii.b2a_base64(string_io.read()).decode("utf-8")
766
+
767
+ return map_dict
768
+
769
+ def saveMap(self):
770
+ """
771
+ save current coding map in JSON format
772
+ """
773
+
774
+ if self.fileName:
775
+ mapDict = self.make_coding_map_dict()
776
+
777
+ with open(self.fileName, "w") as outfile:
778
+ outfile.write(json.dumps(mapDict))
779
+
780
+ self.flag_map_changed = False
781
+
782
+ return True
783
+ else:
784
+ return False
785
+
786
+ def saveAsMap_clicked(self):
787
+ filters = "Behaviors coding map (*.behav_coding_map);;All files (*)"
788
+
789
+ self.fileName, _ = QFileDialog.getSaveFileName(self, "Save behaviors coding map as", "", filters)
790
+
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()
796
+
797
+ def saveMap_clicked(self):
798
+ if not self.fileName:
799
+ self.fileName, _ = QFileDialog().getSaveFileName(
800
+ self,
801
+ "Save modifiers map",
802
+ self.mapName + ".behav_coding_map",
803
+ "Behaviors coding map (*.behav_coding_map);;All files (*)",
804
+ )
805
+
806
+ if self.fileName and Path(self.fileName).suffix != ".behav_coding_map":
807
+ self.fileName += ".behav_coding_map"
808
+
809
+ if self.fileName:
810
+ return self.saveMap()
811
+
812
+ return False
813
+
814
+ def newArea(self):
815
+ if not self.bitmapFileName:
816
+ QMessageBox.critical(self, cfg.programName, "An image must be loaded before to define areas")
817
+ return
818
+
819
+ if self.selectedPolygon:
820
+ self.selectedPolygon.setPen(QPen(designColor, penWidth, penStyle, Qt.RoundCap, Qt.RoundJoin))
821
+ self.selectedPolygon = None
822
+
823
+ self.flagNewArea = True
824
+ self.btSaveArea.setVisible(True)
825
+ self.btCancelAreaCreation.setVisible(True)
826
+ self.btNewArea.setVisible(False)
827
+ self.lb.setVisible(True)
828
+ self.leAreaCode.clear()
829
+ self.leAreaCode.setVisible(True)
830
+ # self.leAreaCode.setEnabled(False)
831
+ self.btEditAreaCode.setVisible(True)
832
+ self.btColor.setVisible(True)
833
+ self.slAlpha.setVisible(True)
834
+ self.btDeleteArea.setVisible(False)
835
+
836
+ self.statusBar().showMessage(
837
+ ("Click on bitmap to set the vertices of the area with the mouse (right click will cancel the last point)")
838
+ )
839
+
840
+ def saveArea(self):
841
+ if not self.closedPolygon:
842
+ QMessageBox.critical(
843
+ self,
844
+ cfg.programName,
845
+ ("You must close your area before saving it.\nThe last vertex must correspond to the first one."),
846
+ )
847
+
848
+ if len(self.view.points) < 3:
849
+ QMessageBox.critical(self, cfg.programName, "You must define a closed area")
850
+ return
851
+
852
+ # check if no area code
853
+ if not self.leAreaCode.text():
854
+ QMessageBox.critical(self, cfg.programName, "You must define a code for the new behavior area")
855
+ return
856
+
857
+ # remove all lines
858
+ for x in self.view.elList:
859
+ self.view.scene().removeItem(x)
860
+
861
+ # draw polygon
862
+ self.closedPolygon.setBrush(QBrush(self.areaColor, Qt.SolidPattern))
863
+ self.polygonsList2.append([self.leAreaCode.text(), self.closedPolygon])
864
+
865
+ self.flagNewArea = None
866
+ self.closedPolygon = None
867
+ self.view._start = 0
868
+ self.view.points = []
869
+ self.view.elList = []
870
+
871
+ for widget in (
872
+ self.btSaveArea,
873
+ self.btCancelAreaCreation,
874
+ self.lb,
875
+ self.leAreaCode,
876
+ self.btEditAreaCode,
877
+ self.btColor,
878
+ self.slAlpha,
879
+ self.btDeleteArea,
880
+ self.btNewArea,
881
+ ):
882
+ widget.setVisible(False)
883
+
884
+ self.btNewArea.setVisible(True)
885
+
886
+ self.leAreaCode.setText("")
887
+
888
+ self.update_area_list()
889
+
890
+ self.flag_map_changed = True
891
+ self.statusBar().showMessage("New area saved", 5000)
892
+
893
+ def cancelAreaCreation(self):
894
+ if self.closedPolygon:
895
+ self.view.scene().removeItem(self.closedPolygon)
896
+ self.closedPolygon = None
897
+
898
+ # remove all lines
899
+ for x in self.view.elList:
900
+ self.view.scene().removeItem(x)
901
+
902
+ self.view.elList = []
903
+
904
+ self.view._start = 0
905
+ self.view.points = []
906
+ self.flagNewArea = False
907
+ self.btCancelAreaCreation.setVisible(False)
908
+ self.btDeleteArea.setVisible(False)
909
+ self.btSaveArea.setVisible(False)
910
+ self.lb.setVisible(False)
911
+
912
+ self.btColor.setVisible(False)
913
+ self.slAlpha.setVisible(False)
914
+ self.btNewArea.setVisible(True)
915
+
916
+ self.leAreaCode.setVisible(False)
917
+ self.leAreaCode.setText("")
918
+
919
+ self.btEditAreaCode.setVisible(False)
920
+
921
+ def update_area_list(self):
922
+ self.area_list.clear()
923
+ for idx, area in enumerate(self.polygonsList2):
924
+ ac, pg = area
925
+ self.area_list.addItem(f"{ac} #{idx}")
926
+
927
+ def deleteArea(self):
928
+ """
929
+ remove selected area from map
930
+ """
931
+
932
+ if self.selectedPolygon:
933
+ self.view.scene().removeItem(self.selectedPolygon)
934
+
935
+ to_delete = -1
936
+ for idx, area in enumerate(self.polygonsList2):
937
+ ac, pg = area
938
+ if pg == self.selectedPolygon:
939
+ to_delete = idx
940
+
941
+ if to_delete != -1:
942
+ del self.polygonsList2[to_delete]
943
+
944
+ self.flag_map_changed = True
945
+
946
+ self.view.elList = []
947
+
948
+ self.view._start = 0
949
+ self.view.points = []
950
+ self.flagNewArea = False
951
+
952
+ for widget in (self.btSaveArea, self.lb, self.btColor, self.slAlpha, self.leAreaCode, self.btDeleteArea):
953
+ widget.setVisible(False)
954
+
955
+ self.btNewArea.setVisible(True)
956
+
957
+ self.leAreaCode.setText("")
958
+ self.btEditAreaCode.setVisible(False)
959
+
960
+ self.statusBar().showMessage("")
961
+
962
+ self.update_area_list()
963
+
964
+ def cancelMap(self):
965
+ """
966
+ remove current map
967
+ """
968
+ self.flagNewArea = False
969
+ self.polygonsList2 = []
970
+ self.closedPolygon = None
971
+ self.selectedPolygon = None
972
+ self.area_list.clear()
973
+ self.view.scene().clear()
974
+ self.btDeleteArea.setVisible(False)
975
+ self.btNewArea.setVisible(False)
976
+ self.saveMapAction.setEnabled(False)
977
+ self.saveAsMapAction.setEnabled(False)
978
+ self.addToProject.setEnabled(False)
979
+ self.mapNameAction.setEnabled(False)
980
+ self.resizeAction.setEnabled(False)
981
+ self.statusBar().showMessage("")
982
+
983
+ self.btNewArea.setVisible(True)
984
+ self.btNewArea.setEnabled(True)
985
+
986
+ self.flag_map_changed = False
987
+
988
+ def loadBitmap(self):
989
+ """
990
+ load bitmap as background for coding map
991
+ no more resize bitmap to CODING_MAP_RESIZE_W x CODING_MAP_RESIZE_H defined in config.py
992
+ """
993
+
994
+ fileName, _ = QFileDialog.getOpenFileName(self, "Load bitmap", "", "bitmap files (*.png *.jpg);;All files (*)")
995
+
996
+ if not fileName:
997
+ return
998
+ self.bitmapFileName = fileName
999
+
1000
+ self.pixmap.load(self.bitmapFileName)
1001
+
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:
1042
+ if (
1043
+ dialog.MessageDialog(
1044
+ "BORIS - Behaviors map creator",
1045
+ "The map contains modifiers. Erase all modifiers",
1046
+ (cfg.NO, cfg.YES),
1047
+ )
1048
+ == cfg.NO
1049
+ ):
1050
+ return
1051
+
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
1057
+
1058
+ self.cancelMap()
1059
+
1060
+ h = int(self.pixmap.size().width() / (self.pixmap.size().width() / integer))
1061
+
1062
+ self.pixmap = self.pixmap.scaled(integer, h, Qt.KeepAspectRatio)
1063
+
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
+ )
1078
+
1079
+
1080
+ def behaviors_coding_map_creator(self):
1081
+ """
1082
+ show behaviors coding map creator window
1083
+ """
1084
+
1085
+ if not self.project:
1086
+ QMessageBox.warning(
1087
+ self, cfg.programName, "Create or open a project first", QMessageBox.Ok | QMessageBox.Default, QMessageBox.NoButton
1088
+ )
1089
+ return
1090
+
1091
+ codes_list = [self.pj[cfg.ETHOGRAM][key][cfg.BEHAVIOR_CODE] for key in self.pj[cfg.ETHOGRAM]]
1092
+
1093
+ self.mapCreatorWindow = BehaviorsMapCreatorWindow(codes_list)
1094
+ # behaviors coding map list
1095
+ self.mapCreatorWindow.bcm_list = [x["name"].upper() for x in self.pj.get(cfg.BEHAVIORS_CODING_MAP, [])]
1096
+ self.mapCreatorWindow.signal_add_to_project.connect(self.behaviors_coding_map_creator_signal_addtoproject)
1097
+ self.mapCreatorWindow.move(self.pos())
1098
+ self.mapCreatorWindow.resize(cfg.CODING_MAP_RESIZE_W, cfg.CODING_MAP_RESIZE_H)
1099
+ self.mapCreatorWindow.show()
1100
+
1101
+
1102
+ if __name__ == "__main__":
1103
+ import sys
1104
+
1105
+ app = QApplication(sys.argv)
1106
+ window = BehaviorsMapCreatorWindow(["North zone", "East zone", "South zone", "West zone"])
1107
+ window.bcm_list = []
1108
+ gui_utilities.resize_center(app, window, cfg.CODING_MAP_RESIZE_W, cfg.CODING_MAP_RESIZE_H)
1109
+ window.show()
1110
+ sys.exit(app.exec())