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