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