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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. boris/__init__.py +1 -1
  2. boris/__main__.py +1 -1
  3. boris/about.py +24 -36
  4. boris/add_modifier.py +88 -80
  5. boris/add_modifier_ui.py +235 -131
  6. boris/advanced_event_filtering.py +23 -29
  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 +16 -34
  23. boris/config.py +102 -50
  24. boris/config_file.py +55 -64
  25. boris/connections.py +105 -58
  26. boris/converters.py +13 -37
  27. boris/converters_ui.py +187 -110
  28. boris/cooccurence.py +250 -0
  29. boris/core.py +2108 -1275
  30. boris/core_qrc.py +15892 -10829
  31. boris/core_ui.py +941 -806
  32. boris/db_functions.py +17 -42
  33. boris/dev.py +27 -7
  34. boris/dialog.py +461 -242
  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 +405 -281
  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 +180 -203
  43. boris/export_observation.py +60 -73
  44. boris/external_processes.py +123 -98
  45. boris/geometric_measurement.py +427 -218
  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 +304 -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 +16 -6
  55. boris/modifier_coding_map_creator.py +1013 -0
  56. boris/modifiers_coding_map.py +7 -9
  57. boris/mpv2.py +128 -35
  58. boris/observation.py +493 -210
  59. boris/observation_operations.py +1010 -391
  60. boris/observation_ui.py +573 -363
  61. boris/observations_list.py +51 -58
  62. boris/otx_parser.py +74 -68
  63. boris/param_panel.py +45 -59
  64. boris/param_panel_ui.py +254 -138
  65. boris/player_dock_widget.py +91 -56
  66. boris/plot_data_module.py +18 -53
  67. boris/plot_events.py +56 -153
  68. boris/plot_events_rt.py +16 -30
  69. boris/plot_spectrogram_rt.py +80 -56
  70. boris/plot_waveform_rt.py +23 -48
  71. boris/plugins.py +431 -0
  72. boris/portion/__init__.py +18 -8
  73. boris/portion/const.py +35 -18
  74. boris/portion/dict.py +5 -5
  75. boris/portion/func.py +2 -2
  76. boris/portion/interval.py +21 -41
  77. boris/portion/io.py +41 -32
  78. boris/preferences.py +298 -123
  79. boris/preferences_ui.py +664 -225
  80. boris/project.py +293 -270
  81. boris/project_functions.py +610 -537
  82. boris/project_import_export.py +204 -213
  83. boris/project_ui.py +673 -441
  84. boris/qrc_boris.py +6 -3
  85. boris/qrc_boris5.py +6 -3
  86. boris/select_modifiers.py +62 -90
  87. boris/select_observations.py +19 -197
  88. boris/select_subj_behav.py +67 -39
  89. boris/state_events.py +51 -33
  90. boris/subjects_pad.py +6 -8
  91. boris/synthetic_time_budget.py +42 -26
  92. boris/time_budget_functions.py +169 -169
  93. boris/time_budget_widget.py +77 -89
  94. boris/transitions.py +41 -41
  95. boris/utilities.py +562 -222
  96. boris/version.py +3 -3
  97. boris/video_equalizer.py +16 -14
  98. boris/video_equalizer_ui.py +199 -130
  99. boris/video_operations.py +78 -28
  100. boris/view_df.py +104 -0
  101. boris/view_df_ui.py +75 -0
  102. boris/write_event.py +240 -136
  103. boris_behav_obs-9.7.1.dist-info/METADATA +140 -0
  104. boris_behav_obs-9.7.1.dist-info/RECORD +109 -0
  105. {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.1.dist-info}/WHEEL +1 -1
  106. boris_behav_obs-9.7.1.dist-info/entry_points.txt +2 -0
  107. boris/README.TXT +0 -22
  108. boris/add_modifier.ui +0 -323
  109. boris/converters.ui +0 -289
  110. boris/core.qrc +0 -37
  111. boris/core.ui +0 -1571
  112. boris/edit_event.ui +0 -233
  113. boris/icons/logo_eye.ico +0 -0
  114. boris/map_creator.py +0 -982
  115. boris/observation.ui +0 -814
  116. boris/param_panel.ui +0 -379
  117. boris/preferences.ui +0 -537
  118. boris/project.ui +0 -1074
  119. boris/vlc_local.py +0 -90
  120. boris_behav_obs-8.16.5.dist-info/LICENSE.TXT +0 -674
  121. boris_behav_obs-8.16.5.dist-info/METADATA +0 -134
  122. boris_behav_obs-8.16.5.dist-info/RECORD +0 -107
  123. boris_behav_obs-8.16.5.dist-info/entry_points.txt +0 -2
  124. {boris → boris_behav_obs-9.7.1.dist-info/licenses}/LICENSE.TXT +0 -0
  125. {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.1.dist-info}/top_level.txt +0 -0
boris/map_creator.py DELETED
@@ -1,982 +0,0 @@
1
- """
2
- BORIS
3
- Behavioral Observation Research Interactive Software
4
- Copyright 2012-2023 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 json
25
- import os
26
-
27
- from PyQt5.QtCore import (
28
- Qt,
29
- pyqtSignal,
30
- QPoint,
31
- QByteArray,
32
- QBuffer,
33
- QIODevice,
34
- QLineF,
35
- )
36
- from PyQt5.QtGui import (
37
- QColor,
38
- QBrush,
39
- QMouseEvent,
40
- QPixmap,
41
- QIcon,
42
- QPen,
43
- QPolygon,
44
- QPolygonF,
45
- )
46
-
47
- from PyQt5.QtWidgets import (
48
- QGraphicsPolygonItem,
49
- QGraphicsEllipseItem,
50
- QGraphicsPixmapItem,
51
- QGraphicsLineItem,
52
- QAction,
53
- QMainWindow,
54
- QGraphicsView,
55
- QPushButton,
56
- QLabel,
57
- QHBoxLayout,
58
- QLineEdit,
59
- QSlider,
60
- QGraphicsScene,
61
- QWidget,
62
- QColorDialog,
63
- QVBoxLayout,
64
- QMessageBox,
65
- QInputDialog,
66
- QFileDialog,
67
- QApplication,
68
- )
69
-
70
- from . import config as cfg
71
- from . import dialog
72
- from . import utilities as util
73
-
74
- designColor = QColor(255, 0, 0, 128) # red opacity: 50%
75
- penWidth = 0
76
- penStyle = Qt.NoPen
77
- selectedBrush = QBrush()
78
- selectedBrush.setStyle(Qt.SolidPattern)
79
- selectedBrush.setColor(QColor(255, 255, 0, 255))
80
-
81
-
82
- class ModifiersMapCreatorWindow(QMainWindow):
83
-
84
- closed = pyqtSignal()
85
-
86
- class View(QGraphicsView):
87
- """
88
- class for handling mousepress event in QGraphicsView
89
- """
90
-
91
- mousePress = pyqtSignal(QMouseEvent)
92
-
93
- def mousePressEvent(self, event):
94
- self.mousePress.emit(event)
95
-
96
- _start = 0
97
- elList, points = [], []
98
-
99
- def __init__(self, parent):
100
- QGraphicsView.__init__(self, parent)
101
- self.setBackgroundBrush(QColor(128, 128, 128))
102
- self.setScene(QGraphicsScene(self))
103
- self.scene().update()
104
-
105
- bitmapFileName, mapName, fileName = "", "", ""
106
- flagNewArea, flagMapChanged = False, False
107
- areasList, polygonsList2 = {}, {}
108
- areaColor = QColor("lime")
109
-
110
- def __init__(self):
111
-
112
- super(ModifiersMapCreatorWindow, self).__init__()
113
-
114
- self.pixmap = QPixmap()
115
- self.closedPolygon = None
116
- self.selectedPolygon = None
117
-
118
- self.setWindowTitle("BORIS - Modifiers map creator")
119
-
120
- self.newMapAction = QAction(QIcon(), "&New modifiers map", self)
121
- self.newMapAction.setShortcut("Ctrl+N")
122
- self.newMapAction.setStatusTip("Create a new modifiers map")
123
- self.newMapAction.triggered.connect(self.newMap)
124
-
125
- self.openMapAction = QAction(QIcon(), "&Open modifiers map", self)
126
- self.openMapAction.setShortcut("Ctrl+O")
127
- self.openMapAction.setStatusTip("Open a modifiers map")
128
- self.openMapAction.triggered.connect(self.openMap)
129
-
130
- self.saveMapAction = QAction(QIcon(), "&Save modifiers map", self)
131
- self.saveMapAction.setShortcut("Ctrl+S")
132
- self.saveMapAction.setStatusTip("Save modifiers map")
133
- self.saveMapAction.setEnabled(False)
134
- self.saveMapAction.triggered.connect(self.saveMap_clicked)
135
-
136
- self.saveAsMapAction = QAction(QIcon(), "Save modifiers map as", self)
137
- self.saveAsMapAction.setStatusTip("Save modifiers map as")
138
- self.saveAsMapAction.setEnabled(False)
139
- self.saveAsMapAction.triggered.connect(self.saveAsMap_clicked)
140
-
141
- self.mapNameAction = QAction(QIcon(), "&Modifiers map name", self)
142
- self.mapNameAction.setShortcut("Ctrl+M")
143
- self.mapNameAction.setStatusTip("Change modifiers map name")
144
- self.mapNameAction.setEnabled(False)
145
- self.mapNameAction.triggered.connect(self.mapName_clicked)
146
-
147
- self.exitAction = QAction(QIcon(), "&Close", self)
148
- self.exitAction.setStatusTip("Close modifiers map creator")
149
- self.exitAction.triggered.connect(self.close)
150
-
151
- menubar = self.menuBar()
152
- fileMenu = menubar.addMenu("&Modifiers Map creator")
153
- fileMenu.addAction(self.newMapAction)
154
- fileMenu.addAction(self.openMapAction)
155
- fileMenu.addAction(self.saveMapAction)
156
- fileMenu.addAction(self.saveAsMapAction)
157
- fileMenu.addSeparator()
158
- fileMenu.addAction(self.mapNameAction)
159
- fileMenu.addSeparator()
160
- fileMenu.addAction(self.exitAction)
161
-
162
- self.view = self.View(self)
163
- self.view.mousePress.connect(self.viewMousePressEvent)
164
-
165
- self.btLoad = QPushButton("Load bitmap", self)
166
- self.btLoad.clicked.connect(self.loadBitmap)
167
- self.btLoad.setVisible(False)
168
-
169
- self.btNewArea = QPushButton("New modifier", self)
170
- self.btNewArea.clicked.connect(self.newArea)
171
- self.btNewArea.setVisible(False)
172
-
173
- self.hlayout = QHBoxLayout()
174
-
175
- self.lb = QLabel("Modifier")
176
- self.lb.setVisible(False)
177
- self.hlayout.addWidget(self.lb)
178
-
179
- self.leAreaCode = QLineEdit(self)
180
- self.leAreaCode.setVisible(False)
181
- self.hlayout.addWidget(self.leAreaCode)
182
-
183
- self.btColor = QPushButton()
184
- self.btColor.clicked.connect(self.chooseColor)
185
- self.btColor.setVisible(False)
186
- self.btColor.setStyleSheet(
187
- "QWidget {{background-color:{}}}".format(self.areaColor.name())
188
- )
189
- self.hlayout.addWidget(self.btColor)
190
-
191
- self.slAlpha = QSlider(Qt.Horizontal)
192
- self.slAlpha.setRange(20, 100)
193
- self.slAlpha.setValue(50)
194
- self.slAlpha.valueChanged.connect(self.slAlpha_changed)
195
- self.slAlpha.setVisible(False)
196
- self.hlayout.addWidget(self.slAlpha)
197
-
198
- self.slAlpha_changed(50)
199
- """
200
- self.btCancelMap = QPushButton("Cancel modifiers map", self)
201
- self.btCancelMap.clicked.connect(self.cancelMap)
202
- self.btCancelMap.setVisible(False)
203
- """
204
-
205
- layout = QVBoxLayout()
206
- layout.addWidget(self.view)
207
- layout.addWidget(self.btLoad)
208
-
209
- hlayout2 = QHBoxLayout()
210
- hlayout2.addWidget(self.btNewArea)
211
-
212
- self.btSaveArea = QPushButton("Save modifier", self)
213
- self.btSaveArea.clicked.connect(self.saveArea)
214
- self.btSaveArea.setVisible(False)
215
- hlayout2.addWidget(self.btSaveArea)
216
-
217
- self.btCancelAreaCreation = QPushButton("Cancel new modifier", self)
218
- self.btCancelAreaCreation.clicked.connect(self.cancelAreaCreation)
219
- self.btCancelAreaCreation.setVisible(False)
220
- hlayout2.addWidget(self.btCancelAreaCreation)
221
-
222
- self.btDeleteArea = QPushButton("Delete selected modifier", self)
223
- self.btDeleteArea.clicked.connect(self.deleteArea)
224
- self.btDeleteArea.setVisible(True)
225
- self.btDeleteArea.setEnabled(False)
226
- hlayout2.addWidget(self.btDeleteArea)
227
-
228
- layout.addLayout(hlayout2)
229
- layout.addLayout(self.hlayout)
230
- """layout.addWidget(self.btCancelMap)"""
231
-
232
- main_widget = QWidget(self)
233
- main_widget.setLayout(layout)
234
- self.setCentralWidget(main_widget)
235
-
236
- self.statusBar().showMessage("")
237
-
238
- def slAlpha_changed(self, val):
239
- """
240
- opacity slider value changed
241
- """
242
-
243
- self.btColor.setText("Opacity: {} %".format(val))
244
- self.areaColor.setAlpha(int(val / 100 * 255))
245
-
246
- if self.selectedPolygon:
247
- self.selectedPolygon.setBrush(self.areaColor)
248
- self.areasList[self.leAreaCode.text()]["color"] = self.areaColor.rgba()
249
-
250
- if self.closedPolygon:
251
- self.closedPolygon.setBrush(self.areaColor)
252
-
253
- def chooseColor(self):
254
- """
255
- area color button clicked
256
- """
257
- cd = QColorDialog()
258
- cd.setWindowFlags(Qt.WindowStaysOnTopHint)
259
- cd.setOptions(QColorDialog.ShowAlphaChannel | QColorDialog.DontUseNativeDialog)
260
-
261
- if cd.exec_():
262
- self.areaColor = cd.currentColor()
263
- self.btColor.setStyleSheet(
264
- f"QWidget {{background-color:{self.areaColor.name()}}}"
265
- )
266
-
267
- if self.selectedPolygon:
268
- self.selectedPolygon.setBrush(self.areaColor)
269
- self.areasList[self.leAreaCode.text()]["color"] = self.areaColor.rgba()
270
-
271
- if self.closedPolygon:
272
- self.closedPolygon.setBrush(self.areaColor)
273
-
274
- def closeEvent(self, event):
275
-
276
- if self.flagMapChanged:
277
-
278
- response = dialog.MessageDialog(
279
- "BORIS - Modifiers map creator",
280
- "What to do about the current unsaved modifiers coding map?",
281
- ["Save", "Discard", "Cancel"],
282
- )
283
-
284
- if response == "Save":
285
- if not self.saveMap_clicked():
286
- event.ignore()
287
-
288
- if response == "Cancel":
289
- event.ignore()
290
- return
291
-
292
- self.closed.emit()
293
- event.accept()
294
-
295
- def viewMousePressEvent(self, event):
296
- """
297
- check if area selected with mouse
298
- """
299
-
300
- if not self.bitmapFileName:
301
- return
302
-
303
- self.btDeleteArea.setEnabled(False)
304
-
305
- test = self.view.mapToScene(event.pos()).toPoint()
306
-
307
- if (
308
- test.x() < 0
309
- or test.y() < 0
310
- or test.x() > self.pixmap.size().width()
311
- or test.y() > self.pixmap.size().height()
312
- ):
313
- return
314
-
315
- if not self.flagNewArea: # test clicked point for areas
316
- txt = ""
317
-
318
- # reset selected polygon to default pen
319
- if self.selectedPolygon:
320
- self.selectedPolygon.setPen(
321
- QPen(designColor, penWidth, penStyle, Qt.RoundCap, Qt.RoundJoin)
322
- )
323
- self.selectedPolygon = None
324
- self.selectedPolygonMemBrush = None
325
-
326
- for areaCode in self.polygonsList2:
327
-
328
- if self.polygonsList2[areaCode].contains(test):
329
-
330
- if txt:
331
- txt += ","
332
-
333
- txt += areaCode
334
- self.selectedPolygon = self.polygonsList2[areaCode]
335
- self.selectedPolygonAreaCode = areaCode
336
-
337
- self.selectedPolygonMemBrush = self.selectedPolygon.brush()
338
-
339
- self.selectedPolygon.setPen(
340
- QPen(
341
- QColor(255, 0, 0, 255),
342
- 2,
343
- Qt.SolidLine,
344
- Qt.RoundCap,
345
- Qt.RoundJoin,
346
- )
347
- )
348
-
349
- self.leAreaCode.setText(areaCode)
350
- self.leAreaCode.setVisible(True)
351
- self.btDeleteArea.setEnabled(True)
352
-
353
- self.areaColor = self.selectedPolygon.brush().color()
354
- self.btColor.setStyleSheet(
355
- "QWidget {{background-color:{}}}".format(
356
- self.selectedPolygon.brush().color().name()
357
- )
358
- )
359
- self.btColor.setVisible(True)
360
-
361
- self.slAlpha.setValue(
362
- int(self.selectedPolygon.brush().color().alpha() / 255 * 100)
363
- )
364
- self.slAlpha.setVisible(True)
365
-
366
- break
367
-
368
- if txt:
369
- self.statusBar().showMessage(
370
- "Modifier{}: {}".format("s" if "," in txt else "", txt)
371
- )
372
- else:
373
- self.statusBar().showMessage("")
374
-
375
- if not self.selectedPolygon:
376
- self.leAreaCode.setVisible(False)
377
- self.btColor.setVisible(False)
378
- self.slAlpha.setVisible(False)
379
- return
380
-
381
- # delete last line item
382
- if (event.buttons() & Qt.RightButton) and not self.closedPolygon:
383
-
384
- if self.view.points:
385
- self.view.points = self.view.points[0:-1]
386
-
387
- if self.view.points:
388
- self.view._start = QPoint(
389
- self.view.points[-1][0], self.view.points[-1][1]
390
- )
391
- else:
392
- self.view._start = None
393
-
394
- # remove graphical elements
395
- if self.view.elList:
396
- self.view.scene().removeItem(self.view.elList[-1])
397
- self.view.elList = self.view.elList[0:-1]
398
-
399
- # add line item
400
- if event.buttons() == Qt.LeftButton and not self.closedPolygon:
401
-
402
- if self.view._start:
403
-
404
- end = test
405
-
406
- # test is polygon is crossed
407
- if len(self.view.points) >= 3:
408
-
409
- for idx, point in enumerate(self.view.points[:-2]):
410
-
411
- if util.intersection(
412
- self.view.points[idx],
413
- self.view.points[idx + 1],
414
- self.view.points[-1],
415
- (int(end.x()), int(end.y())),
416
- ):
417
- QMessageBox.critical(
418
- self, "", "The polygon edges can not be intersected"
419
- )
420
- return
421
-
422
- # test if polygon closed (dist min 10 px)
423
- if (
424
- abs(end.x() - self.view.points[0][0]) < 10
425
- and abs(end.y() - self.view.points[0][1]) < 10
426
- ):
427
-
428
- line = QGraphicsLineItem(
429
- QLineF(
430
- self.view._start,
431
- QPoint(self.view.points[0][0], self.view.points[0][1]),
432
- )
433
- )
434
- line.setPen(
435
- QPen(
436
- designColor,
437
- penWidth,
438
- Qt.SolidLine,
439
- Qt.RoundCap,
440
- Qt.RoundJoin,
441
- )
442
- )
443
-
444
- self.view.scene().addItem(line)
445
- self.view.elList.append(line)
446
-
447
- self.statusBar().showMessage("Area completed")
448
-
449
- # create polygon
450
- newPolygon = QPolygonF()
451
- for p in self.view.points:
452
- newPolygon.append(QPoint(p[0], p[1]))
453
-
454
- # draw polygon a red polygon
455
- self.closedPolygon = QGraphicsPolygonItem(newPolygon)
456
-
457
- self.closedPolygon.setPen(
458
- QPen(designColor, penWidth, penStyle, Qt.RoundCap, Qt.RoundJoin)
459
- )
460
-
461
- self.closedPolygon.setBrush(self.areaColor)
462
-
463
- self.view.scene().addItem(self.closedPolygon)
464
-
465
- return
466
-
467
- self.view.points.append((int(end.x()), int(end.y())))
468
-
469
- line = QGraphicsLineItem(QLineF(self.view._start, end))
470
-
471
- line.setPen(
472
- QPen(designColor, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
473
- )
474
-
475
- self.view.scene().addItem(line)
476
- self.view.elList.append(line)
477
-
478
- self.view._start = test
479
-
480
- else: # first point
481
-
482
- self.view._start = test
483
-
484
- ellipse = QGraphicsEllipseItem(
485
- self.view._start.x(), self.view._start.y(), 3, 3
486
- )
487
- ellipse.setPen(
488
- QPen(designColor, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
489
- )
490
-
491
- brush = QBrush()
492
- brush.setStyle(Qt.SolidPattern)
493
- brush.setColor(designColor)
494
- ellipse.setBrush(brush)
495
-
496
- self.view.scene().addItem(ellipse)
497
- self.view.elList.append(ellipse)
498
-
499
- self.view.points.append((self.view._start.x(), self.view._start.y()))
500
-
501
- def mapName_clicked(self):
502
- """
503
- change map name
504
- """
505
- text, ok = QInputDialog.getText(
506
- self,
507
- "Modifiers map name",
508
- "Enter a name for the modifiers map",
509
- QLineEdit.Normal,
510
- self.mapName,
511
- )
512
- if ok:
513
- self.mapName = text
514
- self.setWindowTitle(
515
- "{} - Modifiers map creator tool - {}".format(
516
- cfg.programName, self.mapName
517
- )
518
- )
519
-
520
- def newMap(self):
521
- """
522
- create a new map
523
- """
524
-
525
- if self.flagMapChanged:
526
-
527
- response = dialog.MessageDialog(
528
- cfg.programName + " - Modifiers map creator",
529
- "What to do about the current unsaved coding map?",
530
- ["Save", "Discard", "Cancel"],
531
- )
532
-
533
- if response == "Save":
534
- if not self.saveMap_clicked():
535
- return
536
-
537
- if response == "Cancel":
538
- return
539
-
540
- self.cancelMap()
541
-
542
- text, ok = QInputDialog.getText(
543
- self, "Map name", "Enter a name for the new map"
544
- )
545
- if ok:
546
- self.mapName = text
547
- else:
548
- return
549
-
550
- if self.mapName == "":
551
- QMessageBox.critical(self, "", "You must define a name for the new map")
552
- return
553
-
554
- if self.mapName in ["areas", "bitmap"]:
555
- QMessageBox.critical(self, "", "This name is not allowed")
556
- return
557
-
558
- self.setWindowTitle(cfg.programName + " - Map creator tool - " + self.mapName)
559
-
560
- self.btLoad.setVisible(True)
561
- """self.btCancelMap.setVisible(True)"""
562
-
563
- self.statusBar().showMessage(
564
- 'Click "Load bitmap" button to select and load a bitmap into the viewer'
565
- )
566
-
567
- def openMap(self):
568
- """
569
- load bitmap from data
570
- show it in view scene
571
- """
572
- if self.flagMapChanged:
573
-
574
- response = dialog.MessageDialog(
575
- cfg.programName + " - Map creator",
576
- "What to do about the current unsaved coding map?",
577
- ["Save", "Discard", "Cancel"],
578
- )
579
-
580
- if response == "Save":
581
- if not self.saveMap_clicked():
582
- return
583
-
584
- if response == "Cancel":
585
- return
586
-
587
- fn = QFileDialog().getOpenFileName(
588
- self,
589
- "Open a coding map",
590
- "",
591
- "BORIS coding map (*.boris_map);;All files (*)",
592
- )
593
- fileName = fn[0] if type(fn) is tuple else fn
594
-
595
- if fileName:
596
-
597
- try:
598
- self.codingMap = json.loads(open(fileName, "r").read())
599
- except Exception:
600
- QMessageBox.critical(
601
- self,
602
- cfg.programName,
603
- "The file {} seems not a behaviors coding map...".format(fileName),
604
- )
605
- return
606
-
607
- self.cancelMap()
608
-
609
- self.mapName = self.codingMap["name"]
610
-
611
- self.setWindowTitle(
612
- cfg.programName + " - Map creator tool - " + self.mapName
613
- )
614
-
615
- self.bitmapFileName = True
616
-
617
- self.fileName = fileName
618
-
619
- self.areasList = self.codingMap["areas"] # dictionary of dictionaries
620
- bitmapContent = binascii.a2b_base64(self.codingMap["bitmap"])
621
-
622
- self.pixmap.loadFromData(bitmapContent)
623
-
624
- self.btDeleteArea.setEnabled(False)
625
-
626
- self.view.setSceneRect(
627
- 0, 0, self.pixmap.size().width(), self.pixmap.size().height()
628
- )
629
- pixItem = QGraphicsPixmapItem(self.pixmap)
630
- pixItem.setPos(0, 0)
631
- self.view.scene().addItem(pixItem)
632
-
633
- for areaCode in self.areasList:
634
- points = self.areasList[areaCode]["geometry"]
635
-
636
- newPolygon = QPolygonF()
637
- for p in points:
638
- newPolygon.append(QPoint(p[0], p[1]))
639
-
640
- clr = QColor()
641
- clr.setRgba(self.areasList[areaCode]["color"])
642
-
643
- # draw polygon
644
- polygon = QGraphicsPolygonItem()
645
-
646
- polygon.setPolygon(newPolygon)
647
-
648
- polygon.setPen(QPen(clr, penWidth, penStyle, Qt.RoundCap, Qt.RoundJoin))
649
-
650
- polygon.setBrush(QBrush(clr, Qt.SolidPattern))
651
-
652
- self.view.scene().addItem(polygon)
653
- self.polygonsList2[areaCode] = polygon
654
-
655
- self.btNewArea.setVisible(True)
656
-
657
- self.btLoad.setVisible(False)
658
-
659
- self.saveMapAction.setEnabled(True)
660
- self.saveAsMapAction.setEnabled(True)
661
- self.mapNameAction.setEnabled(True)
662
- self.statusBar().showMessage('Click "New area" to create a new area')
663
- else:
664
- self.statusBar().showMessage("No file", 5000)
665
-
666
- def saveMap(self):
667
-
668
- if self.fileName:
669
-
670
- # create dict with map name key
671
- mapDict = {"name": self.mapName}
672
-
673
- # add areas
674
- mapDict["areas"] = self.areasList
675
-
676
- import io
677
-
678
- # Save QPixmap to QByteArray via QBuffer.
679
- byte_array = QByteArray()
680
- buffer = QBuffer(byte_array)
681
- buffer.open(QIODevice.WriteOnly)
682
- self.pixmap.save(buffer, "PNG")
683
-
684
- string_io = io.BytesIO(byte_array)
685
-
686
- string_io.seek(0)
687
-
688
- # add bitmap
689
- mapDict["bitmap"] = binascii.b2a_base64(string_io.read()).decode("utf-8")
690
-
691
- with open(self.fileName, "w") as outfile:
692
- outfile.write(json.dumps(mapDict))
693
-
694
- self.flagMapChanged = False
695
-
696
- return True
697
- else:
698
- return False
699
-
700
- def saveAsMap_clicked(self):
701
-
702
- filters = "Modifiers map (*.boris_map);;All files (*)"
703
-
704
- fn = QFileDialog(self).getSaveFileName(
705
- self, "Save modifiers map as", "", filters
706
- )
707
- if type(fn) is tuple:
708
- self.fileName, _ = fn
709
- else:
710
- self.fileName = fn
711
-
712
- if self.fileName:
713
- if os.path.splitext(self.fileName)[1] != ".boris_map":
714
- self.fileName += ".boris_map"
715
- self.saveMap()
716
-
717
- def saveMap_clicked(self):
718
-
719
- if not self.fileName:
720
-
721
- fn = QFileDialog(self).getSaveFileName(
722
- self,
723
- "Save modifiers map",
724
- self.mapName + ".boris_map",
725
- "BORIS MAP (*.boris_map);;All files (*)",
726
- )
727
- if type(fn) is tuple:
728
- self.fileName, _ = fn
729
- else:
730
- self.fileName = fn
731
-
732
- if self.fileName and os.path.splitext(self.fileName)[1] != ".boris_map":
733
- self.fileName += ".boris_map"
734
-
735
- if self.fileName:
736
- return self.saveMap()
737
-
738
- return False
739
-
740
- def newArea(self):
741
-
742
- if not self.bitmapFileName:
743
- QMessageBox.critical(
744
- self, cfg.programName, "A bitmap must be loaded before to define areas"
745
- )
746
- return
747
-
748
- if self.selectedPolygon:
749
- self.selectedPolygon.setPen(
750
- QPen(designColor, penWidth, penStyle, Qt.RoundCap, Qt.RoundJoin)
751
- )
752
- self.selectedPolygon = None
753
-
754
- self.flagNewArea = True
755
- self.btSaveArea.setVisible(True)
756
- self.btCancelAreaCreation.setVisible(True)
757
- self.btNewArea.setVisible(False)
758
- self.lb.setVisible(True)
759
- self.leAreaCode.clear()
760
- self.leAreaCode.setVisible(True)
761
- self.btColor.setVisible(True)
762
- self.slAlpha.setVisible(True)
763
- self.btDeleteArea.setVisible(False)
764
-
765
- self.statusBar().showMessage(
766
- "Select the vertices of the area for this modifier with the mouse (right click will cancel the last point)"
767
- )
768
-
769
- def saveArea(self):
770
-
771
- if not self.closedPolygon:
772
- QMessageBox.critical(
773
- self,
774
- cfg.programName,
775
- "You must close your area before saving it.\nThe last vertex must correspond to the first one.",
776
- )
777
-
778
- if len(self.view.points) < 3:
779
- QMessageBox.critical(self, cfg.programName, "You must define a closed area")
780
- return
781
-
782
- # check if no area code
783
- if not self.leAreaCode.text():
784
- QMessageBox.critical(
785
- self, cfg.programName, "You must define a code for the new modifier"
786
- )
787
- return
788
-
789
- # check if not allowed character
790
- for c in "|,()":
791
- if c in self.leAreaCode.text():
792
- QMessageBox.critical(
793
- self,
794
- cfg.programName,
795
- "The modifier contains a character that is not allowed <b>()|,</b>.",
796
- )
797
- return
798
-
799
- # check if area code already used
800
-
801
- if self.leAreaCode.text() in self.areasList:
802
- QMessageBox.critical(
803
- self, cfg.programName, "The modifier is already in use"
804
- )
805
- return
806
-
807
- # create polygon
808
- newPolygon = QPolygon()
809
- for p in self.view.points:
810
- newPolygon.append(QPoint(p[0], p[1]))
811
-
812
- self.areasList[self.leAreaCode.text()] = {
813
- "geometry": self.view.points,
814
- "color": self.areaColor.rgba(),
815
- }
816
-
817
- # remove all lines
818
- for line in self.view.elList:
819
- self.view.scene().removeItem(line)
820
-
821
- # draw polygon
822
- self.closedPolygon.setBrush(QBrush(self.areaColor, Qt.SolidPattern))
823
- self.polygonsList2[self.leAreaCode.text()] = self.closedPolygon
824
- self.closedPolygon = None
825
- self.view._start = 0
826
- self.view.points = []
827
- self.view.elList = []
828
- self.flagNewArea = False
829
- self.closedPolygon = None
830
-
831
- self.btSaveArea.setVisible(False)
832
- self.btCancelAreaCreation.setVisible(False)
833
- self.lb.setVisible(False)
834
- self.leAreaCode.setVisible(False)
835
- self.btColor.setVisible(False)
836
- self.slAlpha.setVisible(False)
837
- self.btDeleteArea.setVisible(True)
838
- self.btNewArea.setVisible(True)
839
-
840
- self.leAreaCode.setText("")
841
-
842
- self.flagMapChanged = True
843
- self.statusBar().showMessage("New modifier saved", 5000)
844
-
845
- def cancelAreaCreation(self):
846
-
847
- if self.closedPolygon:
848
- self.view.scene().removeItem(self.closedPolygon)
849
- self.closedPolygon = None
850
-
851
- # remove all lines
852
- for line in self.view.elList:
853
- self.view.scene().removeItem(line)
854
-
855
- self.view.elList = []
856
-
857
- self.view._start = 0
858
- self.view.points = []
859
- self.flagNewArea = False
860
- self.btCancelAreaCreation.setVisible(False)
861
- self.btDeleteArea.setVisible(True)
862
- self.btSaveArea.setVisible(False)
863
- self.lb.setVisible(False)
864
-
865
- self.btColor.setVisible(False)
866
- self.slAlpha.setVisible(False)
867
- self.btNewArea.setVisible(True)
868
-
869
- self.leAreaCode.setVisible(False)
870
- self.leAreaCode.setText("")
871
-
872
- def deleteArea(self):
873
- """
874
- remove selected area from map
875
- """
876
-
877
- if self.selectedPolygon:
878
- self.view.scene().removeItem(self.selectedPolygon)
879
- self.view.scene().removeItem(
880
- self.polygonsList2[self.selectedPolygonAreaCode]
881
- )
882
-
883
- del self.polygonsList2[self.selectedPolygonAreaCode]
884
- del self.areasList[self.selectedPolygonAreaCode]
885
-
886
- self.flagMapChanged = True
887
-
888
- self.view.elList = []
889
-
890
- self.view._start = 0
891
- self.view.points = []
892
- self.flagNewArea = False
893
- self.btSaveArea.setVisible(False)
894
- self.lb.setVisible(False)
895
-
896
- self.btColor.setVisible(False)
897
- self.slAlpha.setVisible(False)
898
- self.btNewArea.setVisible(True)
899
-
900
- self.leAreaCode.setVisible(False)
901
- self.leAreaCode.setText("")
902
-
903
- def cancelMap(self):
904
- """
905
- remove current map
906
- """
907
- self.flagNewArea = False
908
- self.areasList = {}
909
- self.polygonsList2 = {}
910
- self.view.scene().clear()
911
- self.btLoad.setVisible(False)
912
- self.btDeleteArea.setVisible(False)
913
- self.btNewArea.setVisible(False)
914
- self.saveMapAction.setEnabled(False)
915
- self.saveAsMapAction.setEnabled(False)
916
- self.mapNameAction.setEnabled(False)
917
- self.statusBar().showMessage("")
918
- self.flagMapChanged = False
919
-
920
- def loadBitmap(self):
921
- """
922
- load bitmap as background for coding map
923
- resize bitmap to 512 px if bigger
924
- """
925
-
926
- maxSize = 512
927
-
928
- fn = QFileDialog().getOpenFileName(
929
- self, "Load bitmap", "", "bitmap files (*.png *.jpg);;All files (*)"
930
- )
931
- fileName = fn[0] if type(fn) is tuple else fn
932
-
933
- if fileName:
934
- self.bitmapFileName = fileName
935
-
936
- self.pixmap.load(self.bitmapFileName)
937
-
938
- if (
939
- self.pixmap.size().width() > maxSize
940
- or self.pixmap.size().height() > maxSize
941
- ):
942
- self.pixmap = self.pixmap.scaled(maxSize, maxSize, Qt.KeepAspectRatio)
943
- QMessageBox.information(
944
- self,
945
- cfg.programName,
946
- "The bitmap was resized to %d x %d pixels\nThe original file was not modified"
947
- % (self.pixmap.size().width(), self.pixmap.size().height()),
948
- )
949
-
950
- # scale image
951
- # pixmap = pixmap.scaled (256, 256, Qt.KeepAspectRatio)
952
-
953
- self.view.setSceneRect(
954
- 0, 0, self.pixmap.size().width(), self.pixmap.size().height()
955
- )
956
- pixitem = QGraphicsPixmapItem(self.pixmap)
957
- pixitem.setPos(0, 0)
958
- self.view.scene().addItem(pixitem)
959
-
960
- self.btNewArea.setVisible(True)
961
-
962
- self.btLoad.setVisible(False)
963
- self.saveMapAction.setEnabled(True)
964
- self.saveAsMapAction.setEnabled(True)
965
- self.mapNameAction.setEnabled(True)
966
-
967
- self.statusBar().showMessage(
968
- """Click "New modifier" to create a new modifier"""
969
- )
970
-
971
- self.flagMapChanged = True
972
-
973
-
974
- if __name__ == "__main__":
975
-
976
- import sys
977
-
978
- app = QApplication(sys.argv)
979
- window = ModifiersMapCreatorWindow()
980
- window.resize(640, 640)
981
- window.show()
982
- sys.exit(app.exec_())