boris-behav-obs 9.7.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. boris/__init__.py +26 -0
  2. boris/__main__.py +25 -0
  3. boris/about.py +143 -0
  4. boris/add_modifier.py +635 -0
  5. boris/add_modifier_ui.py +303 -0
  6. boris/advanced_event_filtering.py +455 -0
  7. boris/analysis_plugins/__init__.py +0 -0
  8. boris/analysis_plugins/_latency.py +59 -0
  9. boris/analysis_plugins/irr_cohen_kappa.py +109 -0
  10. boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
  11. boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
  12. boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
  13. boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
  14. boris/analysis_plugins/number_of_occurences.py +22 -0
  15. boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
  16. boris/analysis_plugins/time_budget.py +61 -0
  17. boris/behav_coding_map_creator.py +1110 -0
  18. boris/behavior_binary_table.py +305 -0
  19. boris/behaviors_coding_map.py +239 -0
  20. boris/boris_cli.py +340 -0
  21. boris/cmd_arguments.py +49 -0
  22. boris/coding_pad.py +280 -0
  23. boris/config.py +785 -0
  24. boris/config_file.py +356 -0
  25. boris/connections.py +409 -0
  26. boris/converters.py +333 -0
  27. boris/converters_ui.py +225 -0
  28. boris/cooccurence.py +250 -0
  29. boris/core.py +5901 -0
  30. boris/core_qrc.py +15958 -0
  31. boris/core_ui.py +1107 -0
  32. boris/db_functions.py +324 -0
  33. boris/dev.py +134 -0
  34. boris/dialog.py +1108 -0
  35. boris/duration_widget.py +238 -0
  36. boris/edit_event.py +245 -0
  37. boris/edit_event_ui.py +233 -0
  38. boris/event_operations.py +1040 -0
  39. boris/events_cursor.py +61 -0
  40. boris/events_snapshots.py +596 -0
  41. boris/exclusion_matrix.py +141 -0
  42. boris/export_events.py +1006 -0
  43. boris/export_observation.py +1203 -0
  44. boris/external_processes.py +332 -0
  45. boris/geometric_measurement.py +941 -0
  46. boris/gui_utilities.py +135 -0
  47. boris/image_overlay.py +72 -0
  48. boris/import_observations.py +242 -0
  49. boris/ipc_mpv.py +325 -0
  50. boris/irr.py +634 -0
  51. boris/latency.py +244 -0
  52. boris/measurement_widget.py +161 -0
  53. boris/media_file.py +115 -0
  54. boris/menu_options.py +213 -0
  55. boris/modifier_coding_map_creator.py +1013 -0
  56. boris/modifiers_coding_map.py +157 -0
  57. boris/mpv.py +2016 -0
  58. boris/mpv2.py +2193 -0
  59. boris/observation.py +1453 -0
  60. boris/observation_operations.py +2538 -0
  61. boris/observation_ui.py +679 -0
  62. boris/observations_list.py +337 -0
  63. boris/otx_parser.py +442 -0
  64. boris/param_panel.py +201 -0
  65. boris/param_panel_ui.py +305 -0
  66. boris/player_dock_widget.py +198 -0
  67. boris/plot_data_module.py +536 -0
  68. boris/plot_events.py +634 -0
  69. boris/plot_events_rt.py +237 -0
  70. boris/plot_spectrogram_rt.py +316 -0
  71. boris/plot_waveform_rt.py +230 -0
  72. boris/plugins.py +431 -0
  73. boris/portion/__init__.py +31 -0
  74. boris/portion/const.py +95 -0
  75. boris/portion/dict.py +365 -0
  76. boris/portion/func.py +52 -0
  77. boris/portion/interval.py +581 -0
  78. boris/portion/io.py +181 -0
  79. boris/preferences.py +510 -0
  80. boris/preferences_ui.py +770 -0
  81. boris/project.py +2007 -0
  82. boris/project_functions.py +2041 -0
  83. boris/project_import_export.py +1096 -0
  84. boris/project_ui.py +794 -0
  85. boris/qrc_boris.py +10389 -0
  86. boris/qrc_boris5.py +2579 -0
  87. boris/select_modifiers.py +312 -0
  88. boris/select_observations.py +210 -0
  89. boris/select_subj_behav.py +286 -0
  90. boris/state_events.py +197 -0
  91. boris/subjects_pad.py +106 -0
  92. boris/synthetic_time_budget.py +290 -0
  93. boris/time_budget_functions.py +1136 -0
  94. boris/time_budget_widget.py +1039 -0
  95. boris/transitions.py +365 -0
  96. boris/utilities.py +1810 -0
  97. boris/version.py +24 -0
  98. boris/video_equalizer.py +159 -0
  99. boris/video_equalizer_ui.py +248 -0
  100. boris/video_operations.py +310 -0
  101. boris/view_df.py +104 -0
  102. boris/view_df_ui.py +75 -0
  103. boris/write_event.py +538 -0
  104. boris_behav_obs-9.7.7.dist-info/METADATA +139 -0
  105. boris_behav_obs-9.7.7.dist-info/RECORD +109 -0
  106. boris_behav_obs-9.7.7.dist-info/WHEEL +5 -0
  107. boris_behav_obs-9.7.7.dist-info/entry_points.txt +2 -0
  108. boris_behav_obs-9.7.7.dist-info/licenses/LICENSE.TXT +674 -0
  109. boris_behav_obs-9.7.7.dist-info/top_level.txt +1 -0
@@ -0,0 +1,305 @@
1
+ """
2
+ BORIS
3
+ Behavioral Observation Research Interactive Software
4
+ Copyright 2012-2025 Olivier Friard
5
+
6
+ This program is free software; you can redistribute it and/or modify
7
+ it under the terms of the GNU General Public License as published by
8
+ the Free Software Foundation; either version 2 of the License, or
9
+ (at your option) any later version.
10
+
11
+ This program is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU General Public License for more details.
15
+
16
+ You should have received a copy of the GNU General Public License
17
+ along with this program; if not, write to the Free Software
18
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
+ MA 02110-1301, USA.
20
+ """
21
+
22
+ import os
23
+ import pathlib
24
+ from decimal import Decimal as dec
25
+
26
+ import tablib
27
+ from PySide6.QtWidgets import QFileDialog, QInputDialog, QMessageBox
28
+
29
+ from . import observation_operations
30
+
31
+ from . import dialog
32
+ from . import project_functions
33
+ from . import select_observations
34
+ from . import utilities as util
35
+ from . import config as cfg
36
+ from . import select_subj_behav
37
+
38
+
39
+ def create_behavior_binary_table(pj: dict, selected_observations: list, parameters_obs: dict, time_interval: float) -> dict:
40
+ """
41
+ create behavior binary table
42
+
43
+ Args:
44
+ pj (dict): project dictionary
45
+ selected_observations (list): list of selected observations
46
+ parameters_obs (dict): dcit of parameters
47
+ time_interval (float): time interval (in seconds)
48
+
49
+ Returns:
50
+ dict: dictionary of tablib dataset
51
+
52
+ """
53
+
54
+ results_df = {}
55
+
56
+ state_behavior_codes = [x for x in util.state_behavior_codes(pj[cfg.ETHOGRAM]) if x in parameters_obs[cfg.SELECTED_BEHAVIORS]]
57
+ point_behavior_codes = [x for x in util.point_behavior_codes(pj[cfg.ETHOGRAM]) if x in parameters_obs[cfg.SELECTED_BEHAVIORS]]
58
+ if not state_behavior_codes and not point_behavior_codes:
59
+ return {"error": True, "msg": "No events selected"}
60
+
61
+ for obs_id in selected_observations:
62
+ start_time = parameters_obs[cfg.START_TIME]
63
+ end_time = parameters_obs[cfg.END_TIME]
64
+
65
+ # check observation interval
66
+ if parameters_obs["time"] == cfg.TIME_FULL_OBS:
67
+ max_obs_length, _ = observation_operations.observation_length(pj, [obs_id])
68
+ start_time = dec("0.000")
69
+ end_time = dec(max_obs_length)
70
+
71
+ if parameters_obs["time"] == cfg.TIME_EVENTS:
72
+ try:
73
+ start_time = dec(pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS][0][0])
74
+ except Exception:
75
+ start_time = dec("0.000")
76
+ try:
77
+ end_time = dec(pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS][-1][0])
78
+ except Exception:
79
+ max_obs_length, _ = observation_operations.observation_length(pj, [obs_id])
80
+ end_time = dec(max_obs_length)
81
+
82
+ if parameters_obs["time"] == cfg.TIME_OBS_INTERVAL:
83
+ obs_interval = pj[cfg.OBSERVATIONS][obs_id].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0])
84
+ offset = pj[cfg.OBSERVATIONS][obs_id][cfg.TIME_OFFSET]
85
+ start_time = dec(obs_interval[0]) + offset
86
+ # Use max observation length for end time if no interval is defined (=0)
87
+ max_obs_length, _ = observation_operations.observation_length(pj, [obs_id])
88
+ end_time = dec(obs_interval[1]) + offset if obs_interval[1] not in (0, None) else dec(max_obs_length)
89
+
90
+ if obs_id not in results_df:
91
+ results_df[obs_id] = {}
92
+
93
+ for subject in parameters_obs[cfg.SELECTED_SUBJECTS]:
94
+ # extract tuple (behavior, modifier)
95
+ behav_modif_list = [
96
+ (idx[2], idx[3])
97
+ for idx in pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]
98
+ if idx[1] == (subject if subject != cfg.NO_FOCAL_SUBJECT else "") and idx[2] in parameters_obs[cfg.SELECTED_BEHAVIORS]
99
+ ]
100
+
101
+ # extract observed subjects NOT USED at the moment
102
+ """observed_subjects = [
103
+ event[cfg.EVENT_SUBJECT_FIELD_IDX] for event in pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]
104
+ ]"""
105
+
106
+ # add selected behavior if not found in (behavior, modifier)
107
+ if not parameters_obs[cfg.EXCLUDE_BEHAVIORS]:
108
+ # for behav in state_behavior_codes:
109
+ for behav in parameters_obs[cfg.SELECTED_BEHAVIORS]:
110
+ if behav not in [x[0] for x in behav_modif_list]:
111
+ behav_modif_list.append((behav, ""))
112
+
113
+ behav_modif_set = set(behav_modif_list)
114
+ observed_behav = [(x[0], x[1]) for x in sorted(behav_modif_set)]
115
+ if parameters_obs[cfg.INCLUDE_MODIFIERS]:
116
+ results_df[obs_id][subject] = tablib.Dataset(
117
+ headers=["time"] + [f"{x[0]}" + f" ({x[1]})" * (x[1] != "") for x in sorted(behav_modif_set)]
118
+ )
119
+ else:
120
+ results_df[obs_id][subject] = tablib.Dataset(headers=["time"] + [x[0] for x in sorted(behav_modif_set)])
121
+
122
+ if subject == cfg.NO_FOCAL_SUBJECT:
123
+ sel_subject_dict = {"": {cfg.SUBJECT_NAME: ""}}
124
+ else:
125
+ sel_subject_dict = dict(
126
+ [(idx, pj[cfg.SUBJECTS][idx]) for idx in pj[cfg.SUBJECTS] if pj[cfg.SUBJECTS][idx][cfg.SUBJECT_NAME] == subject]
127
+ )
128
+
129
+ row_idx = 0
130
+ t = start_time
131
+ while t <= end_time:
132
+ # state events
133
+ current_states = util.get_current_states_modifiers_by_subject_2(
134
+ state_behavior_codes, pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS], sel_subject_dict, t
135
+ )
136
+
137
+ # point events
138
+ current_point = util.get_current_points_by_subject(
139
+ point_behavior_codes, pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS], sel_subject_dict, t, time_interval
140
+ )
141
+
142
+ cols = [float(t)] # time
143
+
144
+ for behav in observed_behav:
145
+ if behav[0] in state_behavior_codes:
146
+ cols.append(int(behav in current_states[list(current_states.keys())[0]]))
147
+
148
+ if behav[0] in point_behavior_codes:
149
+ cols.append(current_point[list(current_point.keys())[0]].count(behav))
150
+
151
+ results_df[obs_id][subject].append(cols)
152
+
153
+ t += time_interval
154
+ row_idx += 1
155
+
156
+ return results_df
157
+
158
+
159
+ def behavior_binary_table(self):
160
+ """
161
+ ask user for parameters for behavior binary table
162
+ call create_behavior_binary_table
163
+ """
164
+
165
+ QMessageBox.warning(
166
+ None,
167
+ cfg.programName,
168
+ (
169
+ "Depending on the length of yours observations "
170
+ "the execution of this function may take a long time.<br>"
171
+ "The program interface may freeze, be patient. <br>"
172
+ ),
173
+ )
174
+
175
+ _, selected_observations = select_observations.select_observations2(
176
+ self, cfg.MULTIPLE, "Select observations for the behavior binary table"
177
+ )
178
+
179
+ if not selected_observations:
180
+ return
181
+
182
+ # check if coded behaviors are defined in ethogram
183
+ if project_functions.check_coded_behaviors_in_obs_list(self.pj, selected_observations):
184
+ return
185
+
186
+ # check if state events are paired
187
+ not_ok, selected_observations = project_functions.check_state_events(self.pj, selected_observations)
188
+ if not_ok or not selected_observations:
189
+ return
190
+
191
+ """
192
+ max_obs_length, _ = observation_operations.observation_length(self.pj, selected_observations)
193
+ if max_obs_length == dec(-1): # media length not available, user choose to not use events
194
+ return
195
+
196
+ # exit with message if events do not have timestamp
197
+ if max_obs_length.is_nan():
198
+ QMessageBox.critical(
199
+ None,
200
+ cfg.programName,
201
+ ("This function is not available for observations with events that do not have timestamp"),
202
+ QMessageBox.Ok | QMessageBox.Default,
203
+ QMessageBox.NoButton,
204
+ )
205
+ return
206
+ """
207
+
208
+ max_media_duration_all_obs, _ = observation_operations.media_duration(self.pj[cfg.OBSERVATIONS], selected_observations)
209
+
210
+ start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
211
+
212
+ start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
213
+
214
+ parameters = select_subj_behav.choose_obs_subj_behav_category(
215
+ self,
216
+ selected_observations,
217
+ start_coding=start_coding,
218
+ end_coding=end_coding,
219
+ start_interval=start_interval,
220
+ end_interval=end_interval,
221
+ maxTime=max_media_duration_all_obs,
222
+ show_include_modifiers=True,
223
+ show_exclude_non_coded_behaviors=True,
224
+ by_category=False,
225
+ n_observations=len(selected_observations),
226
+ )
227
+ if not parameters:
228
+ return
229
+ if not parameters[cfg.SELECTED_SUBJECTS] or not parameters[cfg.SELECTED_BEHAVIORS]:
230
+ QMessageBox.warning(None, cfg.programName, "Select subject(s) and behavior(s) to analyze")
231
+ return
232
+
233
+ # ask for time interval
234
+ i, ok = QInputDialog.getDouble(None, "Behavior binary table", "Time interval (in seconds):", 1.0, 0.001, 86400, 3)
235
+ if not ok:
236
+ return
237
+ time_interval = util.float2decimal(i)
238
+
239
+ results_df = create_behavior_binary_table(self.pj, selected_observations, parameters, time_interval)
240
+
241
+ if "error" in results_df:
242
+ QMessageBox.warning(None, cfg.programName, results_df["msg"])
243
+ return
244
+
245
+ # save results
246
+ file_formats = [cfg.TSV, cfg.CSV, cfg.ODS, cfg.XLSX, cfg.XLS, cfg.HTML]
247
+
248
+ if len(selected_observations) == 1:
249
+ file_name, filter_ = QFileDialog().getSaveFileName(None, "Save results", "", ";;".join(file_formats))
250
+ if not file_name:
251
+ return
252
+
253
+ output_format = cfg.FILE_NAME_SUFFIX[filter_]
254
+
255
+ if pathlib.Path(file_name).suffix != "." + output_format:
256
+ file_name = str(pathlib.Path(file_name)) + "." + output_format
257
+ # check if file with new extension already exists
258
+ if pathlib.Path(file_name).is_file():
259
+ if (
260
+ dialog.MessageDialog(cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE])
261
+ == cfg.CANCEL
262
+ ):
263
+ return
264
+ else:
265
+ item, ok = QInputDialog.getItem(None, "Save results", "Available formats", file_formats, 0, False)
266
+ if not ok:
267
+ return
268
+
269
+ output_format = cfg.FILE_NAME_SUFFIX[item]
270
+
271
+ export_dir = QFileDialog().getExistingDirectory(
272
+ None, "Choose a directory to save results", os.path.expanduser("~"), options=QFileDialog.ShowDirsOnly
273
+ )
274
+ if not export_dir:
275
+ return
276
+
277
+ mem_command = ""
278
+ for obs_id in results_df:
279
+ for subject in results_df[obs_id]:
280
+ if len(selected_observations) > 1:
281
+ file_name_with_subject = str(pathlib.Path(export_dir) / util.safeFileName(obs_id + "_" + subject)) + "." + output_format
282
+ else:
283
+ file_name_with_subject = str(os.path.splitext(file_name)[0] + util.safeFileName("_" + subject)) + "." + output_format
284
+
285
+ # check if file with new extension already exists
286
+ if mem_command != cfg.OVERWRITE_ALL and pathlib.Path(file_name_with_subject).is_file():
287
+ if mem_command == "Skip all":
288
+ continue
289
+ mem_command = dialog.MessageDialog(
290
+ cfg.programName,
291
+ f"The file {file_name_with_subject} already exists.",
292
+ [cfg.OVERWRITE, cfg.OVERWRITE_ALL, "Skip", "Skip all", cfg.CANCEL],
293
+ )
294
+ if mem_command == cfg.CANCEL:
295
+ return
296
+ if mem_command in ["Skip", "Skip all"]:
297
+ continue
298
+
299
+ if output_format in [cfg.CSV_EXT, cfg.TSV_EXT, cfg.HTML]:
300
+ with open(file_name_with_subject, "wb") as f:
301
+ f.write(str.encode(results_df[obs_id][subject].export(output_format)))
302
+
303
+ if output_format in [cfg.ODS_EXT, cfg.XLSX_EXT, cfg.XLS_EXT]:
304
+ with open(file_name_with_subject, "wb") as f:
305
+ f.write(results_df[obs_id][subject].export(output_format))
@@ -0,0 +1,239 @@
1
+ """
2
+ BORIS
3
+ Behavioral Observation Research Interactive Software
4
+ Copyright 2012-2025 Olivier Friard
5
+
6
+ This file is part of BORIS.
7
+
8
+ BORIS is free software; you can redistribute it and/or modify
9
+ it under the terms of the GNU General Public License as published by
10
+ the Free Software Foundation; either version 3 of the License, or
11
+ any later version.
12
+
13
+ BORIS is distributed in the hope that it will be useful,
14
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ GNU General Public License for more details.
17
+
18
+ You should have received a copy of the GNU General Public License
19
+ along with this program; if not see <http://www.gnu.org/licenses/>.
20
+
21
+ """
22
+
23
+ import json
24
+ import binascii
25
+
26
+ from PySide6.QtGui import QMouseEvent, QPixmap, QPolygonF, QColor, QBrush, QPen
27
+ from PySide6.QtCore import Qt, Signal, QEvent, QPoint
28
+ from PySide6.QtWidgets import (
29
+ QLabel,
30
+ QHBoxLayout,
31
+ QGraphicsView,
32
+ QGraphicsScene,
33
+ QWidget,
34
+ QVBoxLayout,
35
+ QLineEdit,
36
+ QPushButton,
37
+ QGraphicsPixmapItem,
38
+ QGraphicsPolygonItem,
39
+ QMessageBox,
40
+ QInputDialog,
41
+ QApplication,
42
+ )
43
+
44
+ from . import config as cfg
45
+
46
+ codeSeparator: str = ","
47
+ penWidth: int = 0
48
+ penStyle = Qt.NoPen
49
+
50
+
51
+ class BehaviorsCodingMapWindowClass(QWidget):
52
+ class View(QGraphicsView):
53
+ mousePress = Signal(QMouseEvent)
54
+ mouseMove = Signal(QMouseEvent)
55
+
56
+ def eventFilter(self, source, event):
57
+ if event.type() == QEvent.MouseMove:
58
+ self.mouseMove.emit(event)
59
+
60
+ if event.type() == QEvent.MouseButtonPress:
61
+ self.mousePress.emit(event)
62
+
63
+ return QWidget.eventFilter(self, source, event)
64
+
65
+ elList, points = [], []
66
+
67
+ def __init__(self, parent):
68
+ QGraphicsView.__init__(self, parent)
69
+ self.setScene(QGraphicsScene(self))
70
+ self.scene().update()
71
+
72
+ self.viewport().installEventFilter(self)
73
+ self.setMouseTracking(True)
74
+
75
+ clickSignal = Signal(str, list) # click signal to be sent to mainwindow
76
+ keypressSignal = Signal(QEvent)
77
+ close_signal = Signal(str)
78
+
79
+ def __init__(self, behaviors_coding_map, idx=0):
80
+ super(BehaviorsCodingMapWindowClass, self).__init__()
81
+
82
+ self.polygonsList2 = []
83
+
84
+ self.installEventFilter(self)
85
+
86
+ self.codingMap = behaviors_coding_map
87
+ self.idx = idx
88
+
89
+ self.setWindowTitle(f"Behaviors coding map: {self.codingMap['name']}")
90
+ Vlayout = QVBoxLayout()
91
+
92
+ self.view = self.View(self)
93
+ self.view.mousePress.connect(self.viewMousePressEvent)
94
+ self.view.mouseMove.connect(self.mouse_move_event)
95
+
96
+ Vlayout.addWidget(self.view)
97
+
98
+ hBoxLayout1 = QHBoxLayout()
99
+
100
+ self.label = QLabel("Behavior(s)")
101
+ hBoxLayout1.addWidget(self.label)
102
+
103
+ self.leareaCode = QLineEdit(self)
104
+ hBoxLayout1.addWidget(self.leareaCode)
105
+
106
+ self.btClose = QPushButton(cfg.CLOSE)
107
+ self.btClose.clicked.connect(self.close)
108
+ hBoxLayout1.addWidget(self.btClose)
109
+
110
+ Vlayout.addLayout(hBoxLayout1)
111
+
112
+ self.setLayout(Vlayout)
113
+
114
+ self.loadMap()
115
+
116
+ def closeEvent(self, event):
117
+ self.close_signal.emit(self.codingMap["name"])
118
+ event.accept()
119
+
120
+ def eventFilter(self, receiver, event):
121
+ """
122
+ send event (if keypress) to main window
123
+ """
124
+ if event.type() == QEvent.KeyPress:
125
+ self.keypressSignal.emit(event)
126
+ return True
127
+ else:
128
+ return False
129
+
130
+ def mouse_move_event(self, event):
131
+ """
132
+ display behavior under mouse position
133
+ """
134
+
135
+ self.leareaCode.clear()
136
+ codes = []
137
+ test = self.view.mapToScene(event.pos()).toPoint()
138
+ for areaCode, pg in self.polygonsList2:
139
+ if pg.contains(test):
140
+ codes.append(areaCode)
141
+ self.leareaCode.setText(", ".join(codes))
142
+
143
+ def viewMousePressEvent(self, event) -> None:
144
+ """
145
+ insert clicked areas codes
146
+ """
147
+
148
+ test = self.view.mapToScene(event.pos()).toPoint()
149
+ to_be_sent: list = []
150
+
151
+ for areaCode, pg in self.polygonsList2:
152
+ if pg.contains(test):
153
+ to_be_sent.append(areaCode)
154
+
155
+ if to_be_sent:
156
+ self.clickSignal.emit(self.codingMap["name"], to_be_sent)
157
+
158
+ def loadMap(self):
159
+ """
160
+ load bitmap from data
161
+ show it in view scene
162
+ """
163
+
164
+ pixmap = QPixmap()
165
+ pixmap.loadFromData(binascii.a2b_base64(self.codingMap["bitmap"]))
166
+
167
+ self.view.setSceneRect(0, 0, pixmap.size().width(), pixmap.size().height())
168
+ pixItem = QGraphicsPixmapItem(pixmap)
169
+ pixItem.setPos(0, 0)
170
+ self.view.scene().addItem(pixItem)
171
+
172
+ for key in self.codingMap["areas"]:
173
+ areaCode = self.codingMap["areas"][key]["code"]
174
+ points = self.codingMap["areas"][key]["geometry"]
175
+
176
+ newPolygon = QPolygonF()
177
+ for p in points:
178
+ newPolygon.append(QPoint(p[0], p[1]))
179
+
180
+ # draw polygon
181
+ polygon = QGraphicsPolygonItem()
182
+ polygon.setPolygon(newPolygon)
183
+ clr = QColor()
184
+ clr.setRgba(self.codingMap["areas"][key]["color"])
185
+ polygon.setPen(QPen(clr, penWidth, penStyle, Qt.RoundCap, Qt.RoundJoin))
186
+ polygon.setBrush(QBrush(clr, Qt.SolidPattern))
187
+
188
+ self.view.scene().addItem(polygon)
189
+
190
+ self.polygonsList2.append([areaCode, polygon])
191
+
192
+
193
+ def show_behaviors_coding_map(self):
194
+ """
195
+ show a behavior coding map
196
+ """
197
+
198
+ if cfg.BEHAVIORS_CODING_MAP not in self.pj or not self.pj[cfg.BEHAVIORS_CODING_MAP]:
199
+ QMessageBox.warning(self, cfg.programName, "No behaviors coding map found in current project")
200
+ return
201
+
202
+ items = [x["name"] for x in self.pj[cfg.BEHAVIORS_CODING_MAP]]
203
+ if len(items) == 1:
204
+ coding_map_name = items[0]
205
+ else:
206
+ item, ok = QInputDialog.getItem(self, "Select a coding map", "list of coding maps", items, 0, False)
207
+ if ok and item:
208
+ coding_map_name = item
209
+ else:
210
+ return
211
+
212
+ if self.bcm_dict.get(coding_map_name, None) is not None:
213
+ # if coding_map_name in self.bcm_dict and :
214
+ self.bcm_dict[coding_map_name].show()
215
+ else:
216
+ self.bcm_dict[coding_map_name] = BehaviorsCodingMapWindowClass(
217
+ self.pj[cfg.BEHAVIORS_CODING_MAP][items.index(coding_map_name)], idx=items.index(coding_map_name)
218
+ )
219
+
220
+ self.bcm_dict[coding_map_name].clickSignal.connect(self.click_signal_from_behaviors_coding_map)
221
+
222
+ # self.bcm_dict[coding_map_name].close_signal.connect(self.close_behaviors_coding_map)
223
+
224
+ self.bcm_dict[coding_map_name].resize(cfg.CODING_MAP_RESIZE_W, cfg.CODING_MAP_RESIZE_W)
225
+ self.bcm_dict[coding_map_name].setWindowFlags(Qt.WindowStaysOnTopHint)
226
+ self.bcm_dict[coding_map_name].show()
227
+
228
+
229
+ if __name__ == "__main__":
230
+ import sys
231
+
232
+ app = QApplication(sys.argv)
233
+
234
+ if len(sys.argv) > 1:
235
+ cm = json.loads(open(sys.argv[1]).read())
236
+ codingMapWindow = BehaviorsCodingMapWindowClass(cm)
237
+ codingMapWindow.resize(cfg.CODING_MAP_RESIZE_W, cfg.CODING_MAP_RESIZE_H)
238
+ codingMapWindow.show()
239
+ sys.exit(app.exec_())