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.
- boris/__init__.py +1 -1
- boris/__main__.py +1 -1
- boris/about.py +28 -39
- boris/add_modifier.py +122 -109
- boris/add_modifier_ui.py +239 -135
- boris/advanced_event_filtering.py +81 -45
- boris/analysis_plugins/__init__.py +0 -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 +228 -229
- 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 +42 -49
- boris/config.py +141 -65
- boris/config_file.py +58 -67
- boris/connections.py +107 -61
- boris/converters.py +13 -37
- boris/converters_ui.py +187 -110
- boris/cooccurence.py +250 -0
- boris/core.py +2373 -1786
- boris/core_qrc.py +15895 -10743
- boris/core_ui.py +943 -798
- boris/db_functions.py +17 -42
- boris/dev.py +109 -8
- boris/dialog.py +482 -236
- boris/duration_widget.py +9 -14
- boris/edit_event.py +61 -31
- boris/edit_event_ui.py +208 -97
- boris/event_operations.py +408 -293
- boris/events_cursor.py +25 -17
- boris/events_snapshots.py +36 -82
- boris/exclusion_matrix.py +4 -9
- boris/export_events.py +184 -223
- boris/export_observation.py +74 -100
- boris/external_processes.py +123 -98
- boris/geometric_measurement.py +644 -290
- 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 +17 -6
- boris/modifier_coding_map_creator.py +1013 -0
- boris/modifiers_coding_map.py +7 -9
- boris/mpv.py +1 -0
- boris/mpv2.py +732 -705
- boris/observation.py +533 -221
- boris/observation_operations.py +1025 -390
- boris/observation_ui.py +572 -362
- boris/observations_list.py +71 -53
- boris/otx_parser.py +74 -68
- boris/param_panel.py +31 -16
- boris/param_panel_ui.py +254 -138
- boris/player_dock_widget.py +90 -60
- boris/plot_data_module.py +25 -33
- boris/plot_events.py +127 -90
- boris/plot_events_rt.py +17 -31
- boris/plot_spectrogram_rt.py +95 -30
- boris/plot_waveform_rt.py +32 -21
- boris/plugins.py +431 -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 +306 -83
- boris/preferences_ui.py +684 -227
- boris/project.py +448 -293
- boris/project_functions.py +671 -238
- boris/project_import_export.py +213 -222
- boris/project_ui.py +674 -438
- boris/qrc_boris.py +6 -3
- boris/qrc_boris5.py +6 -3
- boris/select_modifiers.py +74 -48
- boris/select_observations.py +20 -198
- boris/select_subj_behav.py +67 -39
- boris/state_events.py +52 -35
- boris/subjects_pad.py +6 -9
- boris/synthetic_time_budget.py +45 -28
- boris/time_budget_functions.py +171 -171
- boris/time_budget_widget.py +84 -114
- boris/transitions.py +41 -47
- boris/utilities.py +627 -236
- boris/version.py +3 -3
- boris/video_equalizer.py +16 -14
- boris/video_equalizer_ui.py +199 -130
- boris/video_operations.py +95 -29
- boris/view_df.py +104 -0
- boris/view_df_ui.py +75 -0
- boris/write_event.py +538 -0
- boris_behav_obs-9.7.6.dist-info/METADATA +139 -0
- boris_behav_obs-9.7.6.dist-info/RECORD +109 -0
- {boris_behav_obs-8.12.dist-info → boris_behav_obs-9.7.6.dist-info}/WHEEL +1 -1
- boris_behav_obs-9.7.6.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 -36
- boris/core.ui +0 -1556
- boris/edit_event.ui +0 -233
- boris/icons/logo_eye.ico +0 -0
- boris/map_creator.py +0 -850
- boris/observation.ui +0 -814
- boris/param_panel.ui +0 -379
- boris/preferences.ui +0 -537
- boris/project.ui +0 -1069
- boris/project_server.py +0 -236
- boris/vlc.py +0 -10343
- boris/vlc_local.py +0 -90
- boris_behav_obs-8.12.dist-info/LICENSE.TXT +0 -674
- boris_behav_obs-8.12.dist-info/METADATA +0 -128
- boris_behav_obs-8.12.dist-info/RECORD +0 -108
- boris_behav_obs-8.12.dist-info/entry_points.txt +0 -3
- {boris → boris_behav_obs-9.7.6.dist-info/licenses}/LICENSE.TXT +0 -0
- {boris_behav_obs-8.12.dist-info → boris_behav_obs-9.7.6.dist-info}/top_level.txt +0 -0
boris/behavior_binary_table.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
BORIS
|
|
3
3
|
Behavioral Observation Research Interactive Software
|
|
4
|
-
Copyright 2012-
|
|
4
|
+
Copyright 2012-2025 Olivier Friard
|
|
5
5
|
|
|
6
6
|
This program is free software; you can redistribute it and/or modify
|
|
7
7
|
it under the terms of the GNU General Public License as published by
|
|
@@ -21,12 +21,10 @@ Copyright 2012-2023 Olivier Friard
|
|
|
21
21
|
|
|
22
22
|
import os
|
|
23
23
|
import pathlib
|
|
24
|
-
import re
|
|
25
24
|
from decimal import Decimal as dec
|
|
26
25
|
|
|
27
26
|
import tablib
|
|
28
|
-
from
|
|
29
|
-
from PyQt5.QtWidgets import QFileDialog, QInputDialog, QMessageBox
|
|
27
|
+
from PySide6.QtWidgets import QFileDialog, QInputDialog, QMessageBox
|
|
30
28
|
|
|
31
29
|
from . import observation_operations
|
|
32
30
|
|
|
@@ -38,9 +36,7 @@ from . import config as cfg
|
|
|
38
36
|
from . import select_subj_behav
|
|
39
37
|
|
|
40
38
|
|
|
41
|
-
def create_behavior_binary_table(
|
|
42
|
-
pj: dict, selected_observations: list, parameters_obs: dict, time_interval: float
|
|
43
|
-
) -> dict:
|
|
39
|
+
def create_behavior_binary_table(pj: dict, selected_observations: list, parameters_obs: dict, time_interval: float) -> dict:
|
|
44
40
|
"""
|
|
45
41
|
create behavior binary table
|
|
46
42
|
|
|
@@ -57,17 +53,12 @@ def create_behavior_binary_table(
|
|
|
57
53
|
|
|
58
54
|
results_df = {}
|
|
59
55
|
|
|
60
|
-
state_behavior_codes = [
|
|
61
|
-
|
|
62
|
-
]
|
|
63
|
-
point_behavior_codes = [
|
|
64
|
-
x for x in util.point_behavior_codes(pj[cfg.ETHOGRAM]) if x in parameters_obs[cfg.SELECTED_BEHAVIORS]
|
|
65
|
-
]
|
|
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]]
|
|
66
58
|
if not state_behavior_codes and not point_behavior_codes:
|
|
67
59
|
return {"error": True, "msg": "No events selected"}
|
|
68
60
|
|
|
69
61
|
for obs_id in selected_observations:
|
|
70
|
-
|
|
71
62
|
start_time = parameters_obs[cfg.START_TIME]
|
|
72
63
|
end_time = parameters_obs[cfg.END_TIME]
|
|
73
64
|
|
|
@@ -78,7 +69,6 @@ def create_behavior_binary_table(
|
|
|
78
69
|
end_time = dec(max_obs_length)
|
|
79
70
|
|
|
80
71
|
if parameters_obs["time"] == cfg.TIME_EVENTS:
|
|
81
|
-
|
|
82
72
|
try:
|
|
83
73
|
start_time = dec(pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS][0][0])
|
|
84
74
|
except Exception:
|
|
@@ -89,23 +79,29 @@ def create_behavior_binary_table(
|
|
|
89
79
|
max_obs_length, _ = observation_operations.observation_length(pj, [obs_id])
|
|
90
80
|
end_time = dec(max_obs_length)
|
|
91
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
|
+
|
|
92
90
|
if obs_id not in results_df:
|
|
93
91
|
results_df[obs_id] = {}
|
|
94
92
|
|
|
95
93
|
for subject in parameters_obs[cfg.SELECTED_SUBJECTS]:
|
|
96
|
-
|
|
97
94
|
# extract tuple (behavior, modifier)
|
|
98
95
|
behav_modif_list = [
|
|
99
96
|
(idx[2], idx[3])
|
|
100
97
|
for idx in pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]
|
|
101
|
-
if idx[1] == (subject if subject != cfg.NO_FOCAL_SUBJECT else "")
|
|
102
|
-
and idx[2] in parameters_obs[cfg.SELECTED_BEHAVIORS]
|
|
98
|
+
if idx[1] == (subject if subject != cfg.NO_FOCAL_SUBJECT else "") and idx[2] in parameters_obs[cfg.SELECTED_BEHAVIORS]
|
|
103
99
|
]
|
|
104
100
|
|
|
105
101
|
# extract observed subjects NOT USED at the moment
|
|
106
|
-
observed_subjects = [
|
|
102
|
+
"""observed_subjects = [
|
|
107
103
|
event[cfg.EVENT_SUBJECT_FIELD_IDX] for event in pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]
|
|
108
|
-
]
|
|
104
|
+
]"""
|
|
109
105
|
|
|
110
106
|
# add selected behavior if not found in (behavior, modifier)
|
|
111
107
|
if not parameters_obs[cfg.EXCLUDE_BEHAVIORS]:
|
|
@@ -127,17 +123,12 @@ def create_behavior_binary_table(
|
|
|
127
123
|
sel_subject_dict = {"": {cfg.SUBJECT_NAME: ""}}
|
|
128
124
|
else:
|
|
129
125
|
sel_subject_dict = dict(
|
|
130
|
-
[
|
|
131
|
-
(idx, pj[cfg.SUBJECTS][idx])
|
|
132
|
-
for idx in pj[cfg.SUBJECTS]
|
|
133
|
-
if pj[cfg.SUBJECTS][idx][cfg.SUBJECT_NAME] == subject
|
|
134
|
-
]
|
|
126
|
+
[(idx, pj[cfg.SUBJECTS][idx]) for idx in pj[cfg.SUBJECTS] if pj[cfg.SUBJECTS][idx][cfg.SUBJECT_NAME] == subject]
|
|
135
127
|
)
|
|
136
128
|
|
|
137
129
|
row_idx = 0
|
|
138
130
|
t = start_time
|
|
139
131
|
while t <= end_time:
|
|
140
|
-
|
|
141
132
|
# state events
|
|
142
133
|
current_states = util.get_current_states_modifiers_by_subject_2(
|
|
143
134
|
state_behavior_codes, pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS], sel_subject_dict, t
|
|
@@ -175,8 +166,8 @@ def behavior_binary_table(self):
|
|
|
175
166
|
None,
|
|
176
167
|
cfg.programName,
|
|
177
168
|
(
|
|
178
|
-
"Depending
|
|
179
|
-
"the execution of this function may
|
|
169
|
+
"Depending on the length of yours observations "
|
|
170
|
+
"the execution of this function may take a long time.<br>"
|
|
180
171
|
"The program interface may freeze, be patient. <br>"
|
|
181
172
|
),
|
|
182
173
|
)
|
|
@@ -214,24 +205,26 @@ def behavior_binary_table(self):
|
|
|
214
205
|
return
|
|
215
206
|
"""
|
|
216
207
|
|
|
217
|
-
max_media_duration_all_obs, _ = observation_operations.media_duration(
|
|
218
|
-
self.pj[cfg.OBSERVATIONS], selected_observations
|
|
219
|
-
)
|
|
208
|
+
max_media_duration_all_obs, _ = observation_operations.media_duration(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
220
209
|
|
|
221
210
|
start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
222
211
|
|
|
212
|
+
start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
213
|
+
|
|
223
214
|
parameters = select_subj_behav.choose_obs_subj_behav_category(
|
|
224
215
|
self,
|
|
225
216
|
selected_observations,
|
|
226
217
|
start_coding=start_coding,
|
|
227
218
|
end_coding=end_coding,
|
|
219
|
+
start_interval=start_interval,
|
|
220
|
+
end_interval=end_interval,
|
|
228
221
|
maxTime=max_media_duration_all_obs,
|
|
229
|
-
|
|
230
|
-
|
|
222
|
+
show_include_modifiers=True,
|
|
223
|
+
show_exclude_non_coded_behaviors=True,
|
|
231
224
|
by_category=False,
|
|
232
225
|
n_observations=len(selected_observations),
|
|
233
226
|
)
|
|
234
|
-
if parameters
|
|
227
|
+
if not parameters:
|
|
235
228
|
return
|
|
236
229
|
if not parameters[cfg.SELECTED_SUBJECTS] or not parameters[cfg.SELECTED_BEHAVIORS]:
|
|
237
230
|
QMessageBox.warning(None, cfg.programName, "Select subject(s) and behavior(s) to analyze")
|
|
@@ -253,7 +246,6 @@ def behavior_binary_table(self):
|
|
|
253
246
|
file_formats = [cfg.TSV, cfg.CSV, cfg.ODS, cfg.XLSX, cfg.XLS, cfg.HTML]
|
|
254
247
|
|
|
255
248
|
if len(selected_observations) == 1:
|
|
256
|
-
|
|
257
249
|
file_name, filter_ = QFileDialog().getSaveFileName(None, "Save results", "", ";;".join(file_formats))
|
|
258
250
|
if not file_name:
|
|
259
251
|
return
|
|
@@ -265,18 +257,15 @@ def behavior_binary_table(self):
|
|
|
265
257
|
# check if file with new extension already exists
|
|
266
258
|
if pathlib.Path(file_name).is_file():
|
|
267
259
|
if (
|
|
268
|
-
dialog.MessageDialog(
|
|
269
|
-
cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE]
|
|
270
|
-
)
|
|
260
|
+
dialog.MessageDialog(cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE])
|
|
271
261
|
== cfg.CANCEL
|
|
272
262
|
):
|
|
273
263
|
return
|
|
274
264
|
else:
|
|
275
|
-
|
|
276
265
|
item, ok = QInputDialog.getItem(None, "Save results", "Available formats", file_formats, 0, False)
|
|
277
266
|
if not ok:
|
|
278
267
|
return
|
|
279
|
-
|
|
268
|
+
|
|
280
269
|
output_format = cfg.FILE_NAME_SUFFIX[item]
|
|
281
270
|
|
|
282
271
|
export_dir = QFileDialog().getExistingDirectory(
|
|
@@ -287,17 +276,11 @@ def behavior_binary_table(self):
|
|
|
287
276
|
|
|
288
277
|
mem_command = ""
|
|
289
278
|
for obs_id in results_df:
|
|
290
|
-
|
|
291
279
|
for subject in results_df[obs_id]:
|
|
292
|
-
|
|
293
280
|
if len(selected_observations) > 1:
|
|
294
|
-
file_name_with_subject = (
|
|
295
|
-
str(pathlib.Path(export_dir) / util.safeFileName(obs_id + "_" + subject)) + "." + output_format
|
|
296
|
-
)
|
|
281
|
+
file_name_with_subject = str(pathlib.Path(export_dir) / util.safeFileName(obs_id + "_" + subject)) + "." + output_format
|
|
297
282
|
else:
|
|
298
|
-
file_name_with_subject = (
|
|
299
|
-
str(os.path.splitext(file_name)[0] + util.safeFileName("_" + subject)) + "." + output_format
|
|
300
|
-
)
|
|
283
|
+
file_name_with_subject = str(os.path.splitext(file_name)[0] + util.safeFileName("_" + subject)) + "." + output_format
|
|
301
284
|
|
|
302
285
|
# check if file with new extension already exists
|
|
303
286
|
if mem_command != cfg.OVERWRITE_ALL and pathlib.Path(file_name_with_subject).is_file():
|
|
@@ -313,10 +296,10 @@ def behavior_binary_table(self):
|
|
|
313
296
|
if mem_command in ["Skip", "Skip all"]:
|
|
314
297
|
continue
|
|
315
298
|
|
|
316
|
-
if output_format in [
|
|
299
|
+
if output_format in [cfg.CSV_EXT, cfg.TSV_EXT, cfg.HTML]:
|
|
317
300
|
with open(file_name_with_subject, "wb") as f:
|
|
318
301
|
f.write(str.encode(results_df[obs_id][subject].export(output_format)))
|
|
319
302
|
|
|
320
|
-
if output_format in [
|
|
303
|
+
if output_format in [cfg.ODS_EXT, cfg.XLSX_EXT, cfg.XLS_EXT]:
|
|
321
304
|
with open(file_name_with_subject, "wb") as f:
|
|
322
305
|
f.write(results_df[obs_id][subject].export(output_format))
|
boris/behaviors_coding_map.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
BORIS
|
|
3
3
|
Behavioral Observation Research Interactive Software
|
|
4
|
-
Copyright 2012-
|
|
4
|
+
Copyright 2012-2025 Olivier Friard
|
|
5
5
|
|
|
6
6
|
This file is part of BORIS.
|
|
7
7
|
|
|
@@ -20,9 +20,12 @@ This file is part of BORIS.
|
|
|
20
20
|
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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 (
|
|
26
29
|
QLabel,
|
|
27
30
|
QHBoxLayout,
|
|
28
31
|
QGraphicsView,
|
|
@@ -38,20 +41,17 @@ from PyQt5.QtWidgets import (
|
|
|
38
41
|
QApplication,
|
|
39
42
|
)
|
|
40
43
|
|
|
41
|
-
import json
|
|
42
|
-
import binascii
|
|
43
44
|
from . import config as cfg
|
|
44
45
|
|
|
45
|
-
codeSeparator = ","
|
|
46
|
-
penWidth = 0
|
|
46
|
+
codeSeparator: str = ","
|
|
47
|
+
penWidth: int = 0
|
|
47
48
|
penStyle = Qt.NoPen
|
|
48
49
|
|
|
49
50
|
|
|
50
51
|
class BehaviorsCodingMapWindowClass(QWidget):
|
|
51
52
|
class View(QGraphicsView):
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
mouseMove = pyqtSignal(QMouseEvent)
|
|
53
|
+
mousePress = Signal(QMouseEvent)
|
|
54
|
+
mouseMove = Signal(QMouseEvent)
|
|
55
55
|
|
|
56
56
|
def eventFilter(self, source, event):
|
|
57
57
|
if event.type() == QEvent.MouseMove:
|
|
@@ -72,9 +72,9 @@ class BehaviorsCodingMapWindowClass(QWidget):
|
|
|
72
72
|
self.viewport().installEventFilter(self)
|
|
73
73
|
self.setMouseTracking(True)
|
|
74
74
|
|
|
75
|
-
clickSignal =
|
|
76
|
-
keypressSignal =
|
|
77
|
-
close_signal =
|
|
75
|
+
clickSignal = Signal(str, list) # click signal to be sent to mainwindow
|
|
76
|
+
keypressSignal = Signal(QEvent)
|
|
77
|
+
close_signal = Signal(str)
|
|
78
78
|
|
|
79
79
|
def __init__(self, behaviors_coding_map, idx=0):
|
|
80
80
|
super(BehaviorsCodingMapWindowClass, self).__init__()
|
|
@@ -103,7 +103,7 @@ class BehaviorsCodingMapWindowClass(QWidget):
|
|
|
103
103
|
self.leareaCode = QLineEdit(self)
|
|
104
104
|
hBoxLayout1.addWidget(self.leareaCode)
|
|
105
105
|
|
|
106
|
-
self.btClose = QPushButton(
|
|
106
|
+
self.btClose = QPushButton(cfg.CLOSE)
|
|
107
107
|
self.btClose.clicked.connect(self.close)
|
|
108
108
|
hBoxLayout1.addWidget(self.btClose)
|
|
109
109
|
|
|
@@ -140,13 +140,13 @@ class BehaviorsCodingMapWindowClass(QWidget):
|
|
|
140
140
|
codes.append(areaCode)
|
|
141
141
|
self.leareaCode.setText(", ".join(codes))
|
|
142
142
|
|
|
143
|
-
def viewMousePressEvent(self, event):
|
|
143
|
+
def viewMousePressEvent(self, event) -> None:
|
|
144
144
|
"""
|
|
145
145
|
insert clicked areas codes
|
|
146
146
|
"""
|
|
147
147
|
|
|
148
148
|
test = self.view.mapToScene(event.pos()).toPoint()
|
|
149
|
-
to_be_sent = []
|
|
149
|
+
to_be_sent: list = []
|
|
150
150
|
|
|
151
151
|
for areaCode, pg in self.polygonsList2:
|
|
152
152
|
if pg.contains(test):
|
|
@@ -227,7 +227,6 @@ def show_behaviors_coding_map(self):
|
|
|
227
227
|
|
|
228
228
|
|
|
229
229
|
if __name__ == "__main__":
|
|
230
|
-
|
|
231
230
|
import sys
|
|
232
231
|
|
|
233
232
|
app = QApplication(sys.argv)
|
boris/boris_cli.py
CHANGED
|
@@ -3,7 +3,7 @@ BORIS CLI
|
|
|
3
3
|
|
|
4
4
|
Behavioral Observation Research Interactive Software Command Line Interface
|
|
5
5
|
|
|
6
|
-
Copyright 2012-
|
|
6
|
+
Copyright 2012-2025 Olivier Friard
|
|
7
7
|
|
|
8
8
|
This program is free software; you can redistribute it and/or modify
|
|
9
9
|
it under the terms of the GNU General Public License as published by
|
|
@@ -91,9 +91,7 @@ commands_usage = {
|
|
|
91
91
|
parser = argparse.ArgumentParser(description="BORIS CLI")
|
|
92
92
|
parser.add_argument("-v", "--version", action="store_true", dest="version", help="BORIS version")
|
|
93
93
|
parser.add_argument("-p", "--project", action="store", dest="project_file", help="Project file path")
|
|
94
|
-
parser.add_argument(
|
|
95
|
-
"-o", "--observation", nargs="*", action="store", default=[], dest="observation_id", help="Observation id"
|
|
96
|
-
)
|
|
94
|
+
parser.add_argument("-o", "--observation", nargs="*", action="store", default=[], dest="observation_id", help="Observation id")
|
|
97
95
|
parser.add_argument("-i", "--info", action="store_true", dest="project_info", help="Project information")
|
|
98
96
|
parser.add_argument("-c", "--command", nargs="*", action="store", dest="command", help="Command to execute")
|
|
99
97
|
|
|
@@ -117,7 +115,6 @@ if args.command:
|
|
|
117
115
|
sys.exit()
|
|
118
116
|
|
|
119
117
|
if args.project_file:
|
|
120
|
-
|
|
121
118
|
if not args.command:
|
|
122
119
|
print("Project path: {}".format(args.project_file))
|
|
123
120
|
|
|
@@ -164,9 +161,7 @@ if args.project_info:
|
|
|
164
161
|
print("Subjects\n========")
|
|
165
162
|
print("Number of subjects: {}".format(len(pj[SUBJECTS])))
|
|
166
163
|
for idx in utilities.sorted_keys(pj[SUBJECTS]):
|
|
167
|
-
print(
|
|
168
|
-
"Name: {}\tDescription: {}".format(pj[SUBJECTS][idx]["name"], pj[SUBJECTS][idx]["description"])
|
|
169
|
-
)
|
|
164
|
+
print("Name: {}\tDescription: {}".format(pj[SUBJECTS][idx]["name"], pj[SUBJECTS][idx]["description"]))
|
|
170
165
|
print()
|
|
171
166
|
|
|
172
167
|
print("Observations\n============")
|
|
@@ -179,7 +174,6 @@ if args.project_info:
|
|
|
179
174
|
for observation_id in observations_id_list:
|
|
180
175
|
print("Observation id: {}".format(observation_id))
|
|
181
176
|
if pj[OBSERVATIONS][observation_id][EVENTS]:
|
|
182
|
-
|
|
183
177
|
for event in pj[OBSERVATIONS][observation_id][EVENTS]:
|
|
184
178
|
print("\t".join([str(x) for x in event]))
|
|
185
179
|
else:
|
|
@@ -190,7 +184,6 @@ if args.project_info:
|
|
|
190
184
|
sys.exit()
|
|
191
185
|
|
|
192
186
|
if args.command:
|
|
193
|
-
|
|
194
187
|
print("Command: {}\n".format(" ".join(args.command)))
|
|
195
188
|
|
|
196
189
|
if not pj:
|
|
@@ -198,20 +191,16 @@ if args.command:
|
|
|
198
191
|
sys.exit()
|
|
199
192
|
|
|
200
193
|
if "check_state_events" in args.command:
|
|
201
|
-
|
|
202
194
|
if not observations_id_list:
|
|
203
195
|
print("No observation selected. Command applied on all observations found in project\n")
|
|
204
196
|
observations_id_list = all_observations(pj)
|
|
205
197
|
|
|
206
198
|
for observation_id in observations_id_list:
|
|
207
|
-
ret, msg = project_functions.check_state_events_obs(
|
|
208
|
-
observation_id, pj[ETHOGRAM], pj[OBSERVATIONS][observation_id], HHMMSS
|
|
209
|
-
)
|
|
199
|
+
ret, msg = project_functions.check_state_events_obs(observation_id, pj[ETHOGRAM], pj[OBSERVATIONS][observation_id], HHMMSS)
|
|
210
200
|
print("{}: {}".format(observation_id, cleanhtml(msg)))
|
|
211
201
|
sys.exit()
|
|
212
202
|
|
|
213
203
|
if "export_events" in args.command:
|
|
214
|
-
|
|
215
204
|
if not observations_id_list:
|
|
216
205
|
print("No observation selected. Command applied on all observations found in project\n")
|
|
217
206
|
observations_id_list = [idx for idx in pj[OBSERVATIONS]]
|
|
@@ -261,21 +250,14 @@ if args.command:
|
|
|
261
250
|
if len(args.command) > 2:
|
|
262
251
|
include_modifiers = "TRUE" in args.command[2].upper()
|
|
263
252
|
|
|
264
|
-
K, out = irr.cohen_kappa(
|
|
265
|
-
cursor, observations_id_list[0], observations_id_list[1], interval, subjects, include_modifiers
|
|
266
|
-
)
|
|
253
|
+
K, out = irr.cohen_kappa(cursor, observations_id_list[0], observations_id_list[1], interval, subjects, include_modifiers)
|
|
267
254
|
|
|
268
|
-
print(
|
|
269
|
-
("Cohen's Kappa - Index of Inter-Rater Reliability\n\n" "Interval time: {interval:.3f} s\n").format(
|
|
270
|
-
interval=interval
|
|
271
|
-
)
|
|
272
|
-
)
|
|
255
|
+
print(("Cohen's Kappa - Index of Inter-Rater Reliability\n\nInterval time: {interval:.3f} s\n").format(interval=interval))
|
|
273
256
|
|
|
274
257
|
print(out)
|
|
275
258
|
sys.exit()
|
|
276
259
|
|
|
277
260
|
if "subtitles" in args.command:
|
|
278
|
-
|
|
279
261
|
if not observations_id_list:
|
|
280
262
|
print("No observation selected. Command applied on all observations found in project\n")
|
|
281
263
|
observations_id_list = all_observations(pj)
|
|
@@ -309,7 +291,6 @@ if args.command:
|
|
|
309
291
|
sys.exit()
|
|
310
292
|
|
|
311
293
|
if "plot_events" in args.command[0]:
|
|
312
|
-
|
|
313
294
|
if not observations_id_list:
|
|
314
295
|
print("No observation selected. Command applied on all observations found in project\n")
|
|
315
296
|
observations_id_list = all_observations(pj)
|
boris/cmd_arguments.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
BORIS
|
|
3
3
|
Behavioral Observation Research Interactive Software
|
|
4
|
-
Copyright 2012-
|
|
4
|
+
Copyright 2012-2025 Olivier Friard
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
This program is free software; you can redistribute it and/or modify
|
|
@@ -30,9 +30,20 @@ def parse_arguments():
|
|
|
30
30
|
parser = OptionParser(usage=usage)
|
|
31
31
|
|
|
32
32
|
parser.add_option("-d", "--debug", action="store_true", default=False, dest="debug", help="Use debugging mode")
|
|
33
|
+
parser.add_option("-q", "--quit", action="store_true", default=False, dest="quit", help="Quit after launch")
|
|
33
34
|
parser.add_option("-v", "--version", action="store_true", default=False, dest="version", help="Print version")
|
|
34
35
|
parser.add_option("-n", "--nosplashscreen", action="store_true", default=False, help="No splash screen")
|
|
35
36
|
parser.add_option("-p", "--project", action="store", default="", dest="project", help="Project file")
|
|
36
37
|
parser.add_option("-o", "--observation", action="store", default="", dest="observation", help="Observation id")
|
|
38
|
+
parser.add_option("-i", "--ipc", action="store_true", default="", dest="ipc", help="MPV IPC mode")
|
|
39
|
+
|
|
40
|
+
parser.add_option(
|
|
41
|
+
"-f",
|
|
42
|
+
"--no-first-launch-dialog",
|
|
43
|
+
action="store_true",
|
|
44
|
+
default=False,
|
|
45
|
+
dest="no_first_launch_dialog",
|
|
46
|
+
help="No first launch dialog (for new version automatic check)",
|
|
47
|
+
)
|
|
37
48
|
|
|
38
49
|
return parser.parse_args()
|
boris/coding_pad.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
BORIS
|
|
3
3
|
Behavioral Observation Research Interactive Software
|
|
4
|
-
Copyright 2012-
|
|
4
|
+
Copyright 2012-2025 Olivier Friard
|
|
5
5
|
|
|
6
6
|
This program is free software; you can redistribute it and/or modify
|
|
7
7
|
it under the terms of the GNU General Public License as published by
|
|
@@ -19,9 +19,9 @@ Copyright 2012-2023 Olivier Friard
|
|
|
19
19
|
MA 02110-1301, USA.
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
from
|
|
23
|
-
from
|
|
24
|
-
from
|
|
22
|
+
from PySide6.QtCore import Qt, Signal, QEvent, QRect
|
|
23
|
+
from PySide6.QtGui import QFont
|
|
24
|
+
from PySide6.QtWidgets import QWidget, QPushButton, QHBoxLayout, QGridLayout, QComboBox, QMessageBox
|
|
25
25
|
|
|
26
26
|
from . import config as cfg
|
|
27
27
|
from . import utilities as util
|
|
@@ -38,10 +38,9 @@ class Button(QWidget):
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
class CodingPad(QWidget):
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
close_signal = pyqtSignal(QRect, dict)
|
|
41
|
+
clickSignal = Signal(str)
|
|
42
|
+
sendEventSignal = Signal(QEvent)
|
|
43
|
+
close_signal = Signal(QRect, dict)
|
|
45
44
|
|
|
46
45
|
def __init__(self, pj: dict, filtered_behaviors, parent=None):
|
|
47
46
|
super().__init__(parent)
|
|
@@ -56,16 +55,13 @@ class CodingPad(QWidget):
|
|
|
56
55
|
|
|
57
56
|
self.preferences: dict = {"button font size": 20, "button color": cfg.BEHAVIOR_CATEGORY}
|
|
58
57
|
|
|
59
|
-
self.button_css: str =
|
|
60
|
-
"min-width: 50px; min-height:50px; font-weight: bold; max-height:5000px; max-width: 5000px;"
|
|
61
|
-
)
|
|
58
|
+
self.button_css: str = "min-width: 50px; min-height:50px; font-weight: bold; max-height:5000px; max-width: 5000px;"
|
|
62
59
|
|
|
63
60
|
self.setWindowTitle("Coding pad")
|
|
64
61
|
|
|
65
62
|
self.grid = QGridLayout(self)
|
|
66
63
|
|
|
67
64
|
self.installEventFilter(self)
|
|
68
|
-
# self.compose()
|
|
69
65
|
|
|
70
66
|
def config(self):
|
|
71
67
|
"""
|
|
@@ -96,6 +92,7 @@ class CodingPad(QWidget):
|
|
|
96
92
|
# combobox for coding pad configuration
|
|
97
93
|
vlayout = QHBoxLayout()
|
|
98
94
|
self.cb_config = QComboBox()
|
|
95
|
+
self.cb_config.setFocusPolicy(Qt.NoFocus)
|
|
99
96
|
self.cb_config.addItems(
|
|
100
97
|
[
|
|
101
98
|
"Choose an option to configure",
|
|
@@ -110,9 +107,7 @@ class CodingPad(QWidget):
|
|
|
110
107
|
vlayout.addWidget(self.cb_config)
|
|
111
108
|
self.grid.addLayout(vlayout, 0, 1, 1, 1)
|
|
112
109
|
|
|
113
|
-
self.all_behaviors = [
|
|
114
|
-
self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] for x in util.sorted_keys(self.pj[cfg.ETHOGRAM])
|
|
115
|
-
]
|
|
110
|
+
self.all_behaviors = [self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] for x in util.sorted_keys(self.pj[cfg.ETHOGRAM])]
|
|
116
111
|
|
|
117
112
|
# behavioral category colors
|
|
118
113
|
self.unique_behavioral_categories = sorted(
|
|
@@ -127,8 +122,7 @@ class CodingPad(QWidget):
|
|
|
127
122
|
behaviorsList = [
|
|
128
123
|
[self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CATEGORY], self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE]]
|
|
129
124
|
for x in util.sorted_keys(self.pj[cfg.ETHOGRAM])
|
|
130
|
-
if cfg.BEHAVIOR_CATEGORY in self.pj[cfg.ETHOGRAM][x]
|
|
131
|
-
and self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] in self.filtered_behaviors
|
|
125
|
+
if cfg.BEHAVIOR_CATEGORY in self.pj[cfg.ETHOGRAM][x] and self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] in self.filtered_behaviors
|
|
132
126
|
]
|
|
133
127
|
|
|
134
128
|
# square grid dimension
|
|
@@ -144,15 +138,13 @@ class CodingPad(QWidget):
|
|
|
144
138
|
|
|
145
139
|
self.button_configuration()
|
|
146
140
|
|
|
147
|
-
def addWidget(self, behavior_code, i, j):
|
|
148
|
-
|
|
141
|
+
def addWidget(self, behavior_code: str, i: int, j: int) -> None:
|
|
149
142
|
self.grid.addWidget(Button(), i, j)
|
|
150
143
|
index = self.grid.count() - 1
|
|
151
144
|
widget = self.grid.itemAt(index).widget()
|
|
152
145
|
|
|
153
146
|
if widget is not None:
|
|
154
147
|
widget.pushButton.setText(behavior_code)
|
|
155
|
-
|
|
156
148
|
widget.pushButton.clicked.connect(lambda: self.click(behavior_code))
|
|
157
149
|
|
|
158
150
|
def button_configuration(self):
|
|
@@ -168,36 +160,40 @@ class CodingPad(QWidget):
|
|
|
168
160
|
behavior_code = self.grid.itemAt(index).widget().pushButton.text()
|
|
169
161
|
|
|
170
162
|
if self.preferences["button color"] == cfg.BEHAVIOR_CATEGORY:
|
|
171
|
-
|
|
172
|
-
[
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
163
|
+
behav_cat = [
|
|
164
|
+
self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CATEGORY]
|
|
165
|
+
for x in self.pj[cfg.ETHOGRAM]
|
|
166
|
+
if self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] == behavior_code
|
|
167
|
+
][0]
|
|
168
|
+
if cfg.BEHAVIORAL_CATEGORIES_CONF in self.pj:
|
|
169
|
+
behav_cat_color = util.behav_category_user_color(self.pj[cfg.BEHAVIORAL_CATEGORIES_CONF], behav_cat)
|
|
170
|
+
else:
|
|
171
|
+
behav_cat_color = None
|
|
172
|
+
if behav_cat_color is None:
|
|
173
|
+
color = self.behavioral_category_colors[behav_cat]
|
|
174
|
+
else:
|
|
175
|
+
color = behav_cat_color
|
|
178
176
|
|
|
179
177
|
if self.preferences["button color"] == "behavior":
|
|
180
178
|
# behavioral categories are not defined
|
|
181
179
|
behavior_position = int(
|
|
182
|
-
[
|
|
183
|
-
x
|
|
184
|
-
for x in util.sorted_keys(self.pj[cfg.ETHOGRAM])
|
|
185
|
-
if self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] == behavior_code
|
|
186
|
-
][0]
|
|
187
|
-
)
|
|
188
|
-
color = self.behavior_colors_list[behavior_position % len(self.behavior_colors_list)].replace(
|
|
189
|
-
"tab:", ""
|
|
180
|
+
[x for x in util.sorted_keys(self.pj[cfg.ETHOGRAM]) if self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] == behavior_code][0]
|
|
190
181
|
)
|
|
191
182
|
|
|
183
|
+
# behavior button color
|
|
184
|
+
behav_color = util.behavior_user_color(self.pj[cfg.ETHOGRAM], behavior_code)
|
|
185
|
+
|
|
186
|
+
if behav_color is not None:
|
|
187
|
+
color = behav_color
|
|
188
|
+
else:
|
|
189
|
+
color = self.behavior_colors_list[behavior_position % len(self.behavior_colors_list)].replace("tab:", "")
|
|
190
|
+
|
|
192
191
|
if self.preferences["button color"] == "no color":
|
|
193
|
-
# color = cfg.NO_COLOR_CODING_PAD
|
|
194
192
|
color = ""
|
|
195
193
|
|
|
196
194
|
# set checkable if state behavior
|
|
197
195
|
self.grid.itemAt(index).widget().pushButton.setCheckable(behavior_code in state_behaviors_list)
|
|
198
|
-
self.grid.itemAt(index).widget().pushButton.setStyleSheet(
|
|
199
|
-
self.button_css + (f"background-color: {color};" if color else "")
|
|
200
|
-
)
|
|
196
|
+
self.grid.itemAt(index).widget().pushButton.setStyleSheet(self.button_css + (f"background-color: {color};" if color else ""))
|
|
201
197
|
font = QFont("Arial", self.preferences["button font size"])
|
|
202
198
|
self.grid.itemAt(index).widget().pushButton.setFont(font)
|
|
203
199
|
|
|
@@ -208,13 +204,13 @@ class CodingPad(QWidget):
|
|
|
208
204
|
"""
|
|
209
205
|
self.button_configuration()
|
|
210
206
|
|
|
211
|
-
def click(self,
|
|
207
|
+
def click(self, behavior_code: str) -> None:
|
|
212
208
|
"""
|
|
213
209
|
Button clicked
|
|
214
210
|
"""
|
|
215
|
-
self.clickSignal.emit(
|
|
211
|
+
self.clickSignal.emit(behavior_code)
|
|
216
212
|
|
|
217
|
-
def eventFilter(self, receiver, event):
|
|
213
|
+
def eventFilter(self, receiver, event) -> bool:
|
|
218
214
|
"""
|
|
219
215
|
send event (if keypress) to main window
|
|
220
216
|
"""
|
|
@@ -224,7 +220,7 @@ class CodingPad(QWidget):
|
|
|
224
220
|
else:
|
|
225
221
|
return False
|
|
226
222
|
|
|
227
|
-
def closeEvent(self, event):
|
|
223
|
+
def closeEvent(self, event) -> None:
|
|
228
224
|
"""
|
|
229
225
|
send event for widget geometry memory
|
|
230
226
|
"""
|
|
@@ -240,10 +236,7 @@ def show_coding_pad(self):
|
|
|
240
236
|
return
|
|
241
237
|
|
|
242
238
|
if hasattr(self, "codingpad"):
|
|
243
|
-
|
|
244
|
-
self.codingpad.filtered_behaviors = [
|
|
245
|
-
self.twEthogram.item(i, 1).text() for i in range(self.twEthogram.rowCount())
|
|
246
|
-
]
|
|
239
|
+
self.codingpad.filtered_behaviors = [self.twEthogram.item(i, 1).text() for i in range(self.twEthogram.rowCount())]
|
|
247
240
|
if not self.codingpad.filtered_behaviors:
|
|
248
241
|
QMessageBox.warning(self, cfg.programName, "No behaviors to show!")
|
|
249
242
|
return
|
|
@@ -254,7 +247,6 @@ def show_coding_pad(self):
|
|
|
254
247
|
self.codingpad.behavioral_category_colors_list = self.behav_category_colors
|
|
255
248
|
|
|
256
249
|
else: # coding pad does not exist
|
|
257
|
-
|
|
258
250
|
filtered_behaviors = [self.twEthogram.item(i, 1).text() for i in range(self.twEthogram.rowCount())]
|
|
259
251
|
if not filtered_behaviors:
|
|
260
252
|
QMessageBox.warning(self, cfg.programName, "No behaviors to show!")
|
|
@@ -268,6 +260,7 @@ def show_coding_pad(self):
|
|
|
268
260
|
|
|
269
261
|
self.codingpad.setWindowFlags(Qt.WindowStaysOnTopHint)
|
|
270
262
|
self.codingpad.sendEventSignal.connect(self.signal_from_widget)
|
|
263
|
+
|
|
271
264
|
self.codingpad.clickSignal.connect(self.click_signal_from_coding_pad)
|
|
272
265
|
self.codingpad.close_signal.connect(self.close_signal_from_coding_pad)
|
|
273
266
|
self.codingpad.show()
|
|
@@ -280,7 +273,7 @@ def show_coding_pad(self):
|
|
|
280
273
|
self.config_param[cfg.CODING_PAD_GEOMETRY].height(),
|
|
281
274
|
)
|
|
282
275
|
else:
|
|
283
|
-
self.codingpad.setGeometry(
|
|
276
|
+
self.codingpad.setGeometry(100, 100, 660, 500)
|
|
284
277
|
|
|
285
278
|
if self.config_param.get(cfg.CODING_PAD_CONFIG, {}):
|
|
286
279
|
self.codingpad.preferences = self.config_param[cfg.CODING_PAD_CONFIG]
|