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
boris/qrc_boris.py
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
# Resource object code
|
|
4
4
|
#
|
|
5
|
-
# Created by: The Resource Compiler for
|
|
5
|
+
# Created by: The Resource Compiler for PySide6 (Qt v5.13.0)
|
|
6
6
|
#
|
|
7
7
|
# WARNING! All changes made in this file will be lost!
|
|
8
8
|
|
|
9
|
-
from
|
|
9
|
+
from PySide6 import QtCore
|
|
10
10
|
|
|
11
11
|
qt_resource_data = b"\
|
|
12
12
|
\x00\x00\x04\xba\
|
|
@@ -10369,7 +10369,7 @@ qt_resource_struct_v2 = b"\
|
|
|
10369
10369
|
\x00\x00\x01\x70\xe3\xd1\xd7\xb0\
|
|
10370
10370
|
"
|
|
10371
10371
|
|
|
10372
|
-
qt_version = [int(v) for v in QtCore.qVersion().split(
|
|
10372
|
+
qt_version = [int(v) for v in QtCore.qVersion().split(".")]
|
|
10373
10373
|
if qt_version < [5, 8, 0]:
|
|
10374
10374
|
rcc_version = 1
|
|
10375
10375
|
qt_resource_struct = qt_resource_struct_v1
|
|
@@ -10377,10 +10377,13 @@ else:
|
|
|
10377
10377
|
rcc_version = 2
|
|
10378
10378
|
qt_resource_struct = qt_resource_struct_v2
|
|
10379
10379
|
|
|
10380
|
+
|
|
10380
10381
|
def qInitResources():
|
|
10381
10382
|
QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
|
|
10382
10383
|
|
|
10384
|
+
|
|
10383
10385
|
def qCleanupResources():
|
|
10384
10386
|
QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
|
|
10385
10387
|
|
|
10388
|
+
|
|
10386
10389
|
qInitResources()
|
boris/qrc_boris5.py
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
# Resource object code
|
|
4
4
|
#
|
|
5
|
-
# Created by: The Resource Compiler for
|
|
5
|
+
# Created by: The Resource Compiler for PySide6 (Qt v5.11.2)
|
|
6
6
|
#
|
|
7
7
|
# WARNING! All changes made in this file will be lost!
|
|
8
8
|
|
|
9
|
-
from
|
|
9
|
+
from PySide6 import QtCore
|
|
10
10
|
|
|
11
11
|
qt_resource_data = b"\
|
|
12
12
|
\x00\x00\x04\xa6\
|
|
@@ -2559,7 +2559,7 @@ qt_resource_struct_v2 = b"\
|
|
|
2559
2559
|
\x00\x00\x01\x68\x32\x38\xb4\xfd\
|
|
2560
2560
|
"
|
|
2561
2561
|
|
|
2562
|
-
qt_version = [int(v) for v in QtCore.qVersion().split(
|
|
2562
|
+
qt_version = [int(v) for v in QtCore.qVersion().split(".")]
|
|
2563
2563
|
if qt_version < [5, 8, 0]:
|
|
2564
2564
|
rcc_version = 1
|
|
2565
2565
|
qt_resource_struct = qt_resource_struct_v1
|
|
@@ -2567,10 +2567,13 @@ else:
|
|
|
2567
2567
|
rcc_version = 2
|
|
2568
2568
|
qt_resource_struct = qt_resource_struct_v2
|
|
2569
2569
|
|
|
2570
|
+
|
|
2570
2571
|
def qInitResources():
|
|
2571
2572
|
QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
|
|
2572
2573
|
|
|
2574
|
+
|
|
2573
2575
|
def qCleanupResources():
|
|
2574
2576
|
QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
|
|
2575
2577
|
|
|
2578
|
+
|
|
2576
2579
|
qInitResources()
|
boris/select_modifiers.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
|
|
|
@@ -22,8 +22,8 @@ This file is part of BORIS.
|
|
|
22
22
|
|
|
23
23
|
import re
|
|
24
24
|
|
|
25
|
-
from
|
|
26
|
-
from
|
|
25
|
+
from PySide6.QtCore import Qt, QEvent
|
|
26
|
+
from PySide6.QtWidgets import (
|
|
27
27
|
QDialog,
|
|
28
28
|
QVBoxLayout,
|
|
29
29
|
QLabel,
|
|
@@ -32,14 +32,21 @@ from PyQt5.QtWidgets import (
|
|
|
32
32
|
QLineEdit,
|
|
33
33
|
QPushButton,
|
|
34
34
|
QListWidgetItem,
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
QSizePolicy,
|
|
36
|
+
QSpacerItem,
|
|
37
|
+
QAbstractItemView,
|
|
38
|
+
QMessageBox,
|
|
39
|
+
)
|
|
37
40
|
|
|
38
41
|
from . import config as cfg
|
|
39
42
|
from . import utilities as util
|
|
40
43
|
|
|
41
44
|
|
|
42
45
|
class ModifiersList(QDialog):
|
|
46
|
+
"""
|
|
47
|
+
class for selection the modifier(s)
|
|
48
|
+
"""
|
|
49
|
+
|
|
43
50
|
def __init__(self, code: str, modifiers_dict: dict, currentModifier: str):
|
|
44
51
|
super().__init__()
|
|
45
52
|
self.setWindowTitle(cfg.programName)
|
|
@@ -50,34 +57,36 @@ class ModifiersList(QDialog):
|
|
|
50
57
|
|
|
51
58
|
V1layout = QVBoxLayout()
|
|
52
59
|
label = QLabel()
|
|
53
|
-
label.setText(
|
|
54
|
-
f"Choose the modifier{'s' * (len(self.modifiers_dict) > 1)} for <b>{code}</b> behavior"
|
|
55
|
-
)
|
|
60
|
+
label.setText(f"Choose the modifier{'s' * (len(self.modifiers_dict) > 1)} for <b>{code}</b> behavior")
|
|
56
61
|
V1layout.addWidget(label)
|
|
57
62
|
|
|
58
63
|
Hlayout = QHBoxLayout()
|
|
59
64
|
self.modifiersSetNumber = 0
|
|
60
65
|
|
|
61
66
|
for idx in util.sorted_keys(modifiers_dict):
|
|
62
|
-
if self.modifiers_dict[idx]["type"] not in
|
|
67
|
+
if self.modifiers_dict[idx]["type"] not in (
|
|
63
68
|
cfg.SINGLE_SELECTION,
|
|
64
69
|
cfg.MULTI_SELECTION,
|
|
65
70
|
cfg.NUMERIC_MODIFIER,
|
|
66
|
-
|
|
71
|
+
):
|
|
67
72
|
continue
|
|
68
73
|
|
|
69
74
|
V2layout = QVBoxLayout()
|
|
70
75
|
|
|
71
76
|
self.modifiersSetNumber += 1
|
|
72
77
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
if self.modifiers_dict[idx].get("name", ""):
|
|
79
|
+
lb1 = QLabel(f"Modifier <b>{self.modifiers_dict[idx].get('name', '')}</b>")
|
|
80
|
+
V2layout.addWidget(lb1)
|
|
76
81
|
|
|
77
|
-
if self.modifiers_dict[idx]
|
|
82
|
+
if self.modifiers_dict[idx].get("description", ""):
|
|
83
|
+
lb2 = QLabel(f"<small>{self.modifiers_dict[idx].get('description', '')[:50]}</small>")
|
|
84
|
+
V2layout.addWidget(lb2)
|
|
85
|
+
|
|
86
|
+
if self.modifiers_dict[idx]["type"] in (
|
|
78
87
|
cfg.SINGLE_SELECTION,
|
|
79
88
|
cfg.MULTI_SELECTION,
|
|
80
|
-
|
|
89
|
+
):
|
|
81
90
|
lw = QListWidget()
|
|
82
91
|
self.modifiers_dict[idx]["widget"] = lw
|
|
83
92
|
lw.setObjectName(f"lw_modifiers_({self.modifiers_dict[idx]['type']})")
|
|
@@ -95,9 +104,7 @@ class ModifiersList(QDialog):
|
|
|
95
104
|
|
|
96
105
|
# previously selected
|
|
97
106
|
try:
|
|
98
|
-
if currentModifierList != [""] and re.sub(
|
|
99
|
-
" \(.\)", "", modifier
|
|
100
|
-
) in currentModifierList[int(idx)].split(","):
|
|
107
|
+
if currentModifierList != [""] and re.sub(r" \(.\)", "", modifier) in currentModifierList[int(idx)].split(","):
|
|
101
108
|
item.setCheckState(Qt.Checked)
|
|
102
109
|
except Exception: # for old projects due to a fixed bug
|
|
103
110
|
pass
|
|
@@ -106,11 +113,7 @@ class ModifiersList(QDialog):
|
|
|
106
113
|
|
|
107
114
|
if self.modifiers_dict[idx]["type"] == cfg.SINGLE_SELECTION:
|
|
108
115
|
try:
|
|
109
|
-
if (
|
|
110
|
-
currentModifierList != [""]
|
|
111
|
-
and re.sub(" \(.\)", "", modifier)
|
|
112
|
-
== currentModifierList[int(idx)]
|
|
113
|
-
):
|
|
116
|
+
if currentModifierList != [""] and re.sub(r" \(.\)", "", modifier) == currentModifierList[int(idx)]:
|
|
114
117
|
item.setSelected(True)
|
|
115
118
|
except Exception: # for old projects due to a fixed bug
|
|
116
119
|
pass
|
|
@@ -119,19 +122,15 @@ class ModifiersList(QDialog):
|
|
|
119
122
|
if self.modifiers_dict[idx]["type"] in [cfg.NUMERIC_MODIFIER]:
|
|
120
123
|
le = QLineEdit()
|
|
121
124
|
self.modifiers_dict[idx]["widget"] = le
|
|
125
|
+
le.setObjectName(f"le_modifiers_({self.modifiers_dict[idx]['type']})")
|
|
122
126
|
|
|
123
|
-
if (
|
|
124
|
-
currentModifierList != [""]
|
|
125
|
-
and currentModifierList[int(idx)] != "None"
|
|
126
|
-
):
|
|
127
|
+
if currentModifierList != [""] and currentModifierList[int(idx)] != "None":
|
|
127
128
|
le.setText(currentModifierList[int(idx)])
|
|
128
129
|
|
|
129
130
|
V2layout.addWidget(le)
|
|
130
131
|
|
|
131
132
|
# vertical spacer
|
|
132
|
-
spacerItem = QSpacerItem(
|
|
133
|
-
20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding
|
|
134
|
-
)
|
|
133
|
+
spacerItem = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
|
|
135
134
|
V2layout.addItem(spacerItem)
|
|
136
135
|
|
|
137
136
|
Hlayout.addLayout(V2layout)
|
|
@@ -169,13 +168,15 @@ class ModifiersList(QDialog):
|
|
|
169
168
|
return False
|
|
170
169
|
|
|
171
170
|
# accept and close dialog if enter pressed
|
|
172
|
-
if
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
self.accept()
|
|
171
|
+
if ek == Qt.Key_Enter or ek == Qt.Key_Return: # enter or enter from numeric pad
|
|
172
|
+
if not self.pbOK_clicked():
|
|
173
|
+
return False
|
|
176
174
|
return True
|
|
177
175
|
|
|
176
|
+
modifiersSetIndex = 0
|
|
178
177
|
for widget in self.children():
|
|
178
|
+
if "_modifiers" in widget.objectName():
|
|
179
|
+
modifiersSetIndex = modifiersSetIndex + 1
|
|
179
180
|
if "lw_modifiers" in widget.objectName():
|
|
180
181
|
if self.modifiersSetNumber == 1:
|
|
181
182
|
# check if modifiers have code
|
|
@@ -184,32 +185,16 @@ class ModifiersList(QDialog):
|
|
|
184
185
|
break
|
|
185
186
|
else:
|
|
186
187
|
# modifiers have no associated code: the modifier starting with hit key will be selected
|
|
187
|
-
if ek not in
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
in widget.objectName()
|
|
192
|
-
): # checking using SPACE bar
|
|
193
|
-
if (
|
|
194
|
-
widget.item(widget.currentRow()).checkState()
|
|
195
|
-
== Qt.Checked
|
|
196
|
-
):
|
|
197
|
-
widget.item(widget.currentRow()).setCheckState(
|
|
198
|
-
Qt.Unchecked
|
|
199
|
-
)
|
|
188
|
+
if ek not in (Qt.Key_Down, Qt.Key_Up):
|
|
189
|
+
if ek == Qt.Key_Space and f"({cfg.MULTI_SELECTION})" in widget.objectName(): # checking using SPACE bar
|
|
190
|
+
if widget.item(widget.currentRow()).checkState() == Qt.Checked:
|
|
191
|
+
widget.item(widget.currentRow()).setCheckState(Qt.Unchecked)
|
|
200
192
|
else:
|
|
201
|
-
widget.item(widget.currentRow()).setCheckState(
|
|
202
|
-
Qt.Checked
|
|
203
|
-
)
|
|
193
|
+
widget.item(widget.currentRow()).setCheckState(Qt.Checked)
|
|
204
194
|
|
|
205
195
|
else:
|
|
206
196
|
for index in range(widget.count()):
|
|
207
|
-
if (
|
|
208
|
-
widget.item(index)
|
|
209
|
-
.text()
|
|
210
|
-
.upper()
|
|
211
|
-
.startswith(ek_text.upper())
|
|
212
|
-
):
|
|
197
|
+
if widget.item(index).text().upper().startswith(ek_text.upper()):
|
|
213
198
|
widget.setCurrentRow(index)
|
|
214
199
|
widget.scrollToItem(
|
|
215
200
|
widget.item(index),
|
|
@@ -218,10 +203,7 @@ class ModifiersList(QDialog):
|
|
|
218
203
|
return True
|
|
219
204
|
else: # up / down keys
|
|
220
205
|
try:
|
|
221
|
-
if (
|
|
222
|
-
ek == Qt.Key_Down
|
|
223
|
-
and widget.currentRow() < widget.count() - 1
|
|
224
|
-
):
|
|
206
|
+
if ek == Qt.Key_Down and widget.currentRow() < widget.count() - 1:
|
|
225
207
|
widget.setCurrentRow(widget.currentRow() + 1)
|
|
226
208
|
if ek == Qt.Key_Up and widget.currentRow() > 0:
|
|
227
209
|
widget.setCurrentRow(widget.currentRow() - 1)
|
|
@@ -229,17 +211,19 @@ class ModifiersList(QDialog):
|
|
|
229
211
|
return
|
|
230
212
|
|
|
231
213
|
for index in range(widget.count()):
|
|
214
|
+
# check function kesy (F1, F2...)
|
|
232
215
|
if ek in cfg.function_keys:
|
|
233
|
-
if (
|
|
234
|
-
f"({cfg.function_keys[ek]})"
|
|
235
|
-
in widget.item(index).text().upper()
|
|
236
|
-
):
|
|
216
|
+
if f"({cfg.function_keys[ek]})" in widget.item(index).text().upper():
|
|
237
217
|
if f"({cfg.SINGLE_SELECTION})" in widget.objectName():
|
|
238
218
|
widget.item(index).setSelected(True)
|
|
239
219
|
# close dialog if one set of modifiers
|
|
240
220
|
if self.modifiersSetNumber == 1:
|
|
241
221
|
self.accept()
|
|
242
222
|
return True
|
|
223
|
+
# else move to next set of mofifiers
|
|
224
|
+
elif modifiersSetIndex != self.modifiersSetNumber:
|
|
225
|
+
widget.parent().focusNextChild()
|
|
226
|
+
return True
|
|
243
227
|
|
|
244
228
|
if f"({cfg.MULTI_SELECTION})" in widget.objectName():
|
|
245
229
|
if widget.item(index).checkState() == Qt.Checked:
|
|
@@ -254,6 +238,10 @@ class ModifiersList(QDialog):
|
|
|
254
238
|
if self.modifiersSetNumber == 1:
|
|
255
239
|
self.accept()
|
|
256
240
|
return True
|
|
241
|
+
# else move to next set of mofifiers
|
|
242
|
+
elif modifiersSetIndex != self.modifiersSetNumber:
|
|
243
|
+
widget.parent().focusNextChild()
|
|
244
|
+
return True
|
|
257
245
|
|
|
258
246
|
if f"({cfg.MULTI_SELECTION})" in widget.objectName():
|
|
259
247
|
if widget.item(index).checkState() == Qt.Checked:
|
|
@@ -270,7 +258,7 @@ class ModifiersList(QDialog):
|
|
|
270
258
|
get modifiers
|
|
271
259
|
returns list of selected modifiers
|
|
272
260
|
"""
|
|
273
|
-
|
|
261
|
+
|
|
274
262
|
for idx in util.sorted_keys(self.modifiers_dict):
|
|
275
263
|
if self.modifiers_dict[idx]["type"] in [
|
|
276
264
|
cfg.SINGLE_SELECTION,
|
|
@@ -281,13 +269,10 @@ class ModifiersList(QDialog):
|
|
|
281
269
|
|
|
282
270
|
if self.modifiers_dict[idx]["type"] == cfg.MULTI_SELECTION:
|
|
283
271
|
for j in range(self.modifiers_dict[idx]["widget"].count()):
|
|
284
|
-
if (
|
|
285
|
-
self.modifiers_dict[idx]["widget"].item(j).checkState()
|
|
286
|
-
== Qt.Checked
|
|
287
|
-
):
|
|
272
|
+
if self.modifiers_dict[idx]["widget"].item(j).checkState() == Qt.Checked:
|
|
288
273
|
self.modifiers_dict[idx]["selected"].append(
|
|
289
274
|
re.sub(
|
|
290
|
-
" \(.*\)",
|
|
275
|
+
r" \(.*\)",
|
|
291
276
|
"",
|
|
292
277
|
self.modifiers_dict[idx]["widget"].item(j).text(),
|
|
293
278
|
)
|
|
@@ -298,30 +283,19 @@ class ModifiersList(QDialog):
|
|
|
298
283
|
|
|
299
284
|
if self.modifiers_dict[idx]["type"] == cfg.SINGLE_SELECTION:
|
|
300
285
|
for item in self.modifiers_dict[idx]["widget"].selectedItems():
|
|
301
|
-
self.modifiers_dict[idx]["selected"].append(
|
|
302
|
-
re.sub(" \(.*\)", "", item.text())
|
|
303
|
-
)
|
|
286
|
+
self.modifiers_dict[idx]["selected"].append(re.sub(r" \(.*\)", "", item.text()))
|
|
304
287
|
|
|
305
288
|
if self.modifiers_dict[idx]["type"] == cfg.NUMERIC_MODIFIER:
|
|
306
289
|
self.modifiers_dict[idx]["selected"] = (
|
|
307
|
-
self.modifiers_dict[idx]["widget"].text()
|
|
308
|
-
if self.modifiers_dict[idx]["widget"].text()
|
|
309
|
-
else "None"
|
|
290
|
+
self.modifiers_dict[idx]["widget"].text() if self.modifiers_dict[idx]["widget"].text() else "None"
|
|
310
291
|
)
|
|
311
|
-
"""
|
|
312
|
-
for widget in self.children():
|
|
313
|
-
if widget.objectName() == "lw_modifiers_classic":
|
|
314
|
-
for item in widget.selectedItems():
|
|
315
|
-
modifiers.append(re.sub(" \(.*\)", "", item.text()))
|
|
316
|
-
if widget.objectName() == "lw_modifiers_from_set":
|
|
317
|
-
for idx in range(widget.count()):
|
|
318
|
-
if widget.item(idx).checkState() == Qt.Checked:
|
|
319
|
-
modifiers.append(widget.item(idx).text())
|
|
320
|
-
"""
|
|
321
292
|
|
|
322
293
|
return self.modifiers_dict
|
|
323
294
|
|
|
324
295
|
def pbOK_clicked(self):
|
|
296
|
+
"""
|
|
297
|
+
OK button is clicked
|
|
298
|
+
"""
|
|
325
299
|
for idx in util.sorted_keys(self.modifiers_dict):
|
|
326
300
|
if self.modifiers_dict[idx]["type"] == cfg.NUMERIC_MODIFIER:
|
|
327
301
|
if self.modifiers_dict[idx]["widget"].text():
|
|
@@ -331,10 +305,8 @@ class ModifiersList(QDialog):
|
|
|
331
305
|
QMessageBox.warning(
|
|
332
306
|
self,
|
|
333
307
|
cfg.programName,
|
|
334
|
-
"<b>{}</b> is not a numeric value"
|
|
335
|
-
self.modifiers_dict[idx]["widget"].text()
|
|
336
|
-
),
|
|
308
|
+
f"<b>{self.modifiers_dict[idx]['widget'].text()}</b> is not a numeric value",
|
|
337
309
|
)
|
|
338
|
-
return
|
|
310
|
+
return False
|
|
339
311
|
|
|
340
312
|
self.accept()
|
boris/select_observations.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
|
|
@@ -20,9 +20,12 @@ Copyright 2012-2023 Olivier Friard
|
|
|
20
20
|
MA 02110-1301, USA.
|
|
21
21
|
|
|
22
22
|
"""
|
|
23
|
+
|
|
24
|
+
import logging
|
|
23
25
|
from typing import Tuple
|
|
24
|
-
|
|
25
|
-
from
|
|
26
|
+
|
|
27
|
+
from PySide6.QtCore import Qt
|
|
28
|
+
from PySide6.QtWidgets import QAbstractItemView
|
|
26
29
|
|
|
27
30
|
from . import config as cfg
|
|
28
31
|
from . import gui_utilities, observations_list, project_functions
|
|
@@ -54,36 +57,32 @@ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str,
|
|
|
54
57
|
indep_var_header.append(pj[cfg.INDEPENDENT_VARIABLES][idx]["label"])
|
|
55
58
|
column_type.append(pj[cfg.INDEPENDENT_VARIABLES][idx]["type"])
|
|
56
59
|
|
|
57
|
-
state_events_list = [
|
|
58
|
-
pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE]
|
|
59
|
-
for x in pj[cfg.ETHOGRAM]
|
|
60
|
-
if cfg.STATE in pj[cfg.ETHOGRAM][x][cfg.TYPE].upper()
|
|
61
|
-
]
|
|
60
|
+
state_events_list = util.state_behavior_codes(pj[cfg.ETHOGRAM])
|
|
62
61
|
|
|
63
62
|
data: list = []
|
|
64
63
|
not_paired: list = []
|
|
65
64
|
|
|
65
|
+
# check if observations changed
|
|
66
66
|
if hash(str(self.pj[cfg.OBSERVATIONS])) != self.mem_hash_obs:
|
|
67
|
+
logging.debug("observations changed")
|
|
67
68
|
|
|
68
69
|
for obs in sorted(list(pj[cfg.OBSERVATIONS].keys())):
|
|
69
70
|
date = pj[cfg.OBSERVATIONS][obs]["date"].replace("T", " ")
|
|
70
71
|
descr = util.eol2space(pj[cfg.OBSERVATIONS][obs][cfg.DESCRIPTION])
|
|
71
72
|
|
|
72
73
|
# subjects
|
|
73
|
-
observed_subjects = [
|
|
74
|
-
cfg.NO_FOCAL_SUBJECT if x == "" else x for x in project_functions.extract_observed_subjects(pj, [obs])
|
|
75
|
-
]
|
|
74
|
+
observed_subjects = [cfg.NO_FOCAL_SUBJECT if x == "" else x for x in project_functions.extract_observed_subjects(pj, [obs])]
|
|
76
75
|
|
|
77
76
|
subjectsList = ", ".join(observed_subjects)
|
|
78
77
|
|
|
79
78
|
# observed time interval
|
|
80
79
|
interval = project_functions.observed_interval(pj[cfg.OBSERVATIONS][obs])
|
|
81
|
-
observed_interval_str = str(interval[1] - interval[0])
|
|
80
|
+
observed_interval_str = str(round(interval[1] - interval[0], 3))
|
|
82
81
|
|
|
83
82
|
# media
|
|
84
|
-
media = ""
|
|
83
|
+
media: str = ""
|
|
85
84
|
if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.MEDIA:
|
|
86
|
-
media_list = []
|
|
85
|
+
media_list: list = []
|
|
87
86
|
if pj[cfg.OBSERVATIONS][obs][cfg.FILE]:
|
|
88
87
|
for player in sorted(pj[cfg.OBSERVATIONS][obs][cfg.FILE].keys()):
|
|
89
88
|
for media in pj[cfg.OBSERVATIONS][obs][cfg.FILE][player]:
|
|
@@ -98,13 +97,13 @@ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str,
|
|
|
98
97
|
media = cfg.LIVE
|
|
99
98
|
|
|
100
99
|
if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.IMAGES:
|
|
101
|
-
dir_list = []
|
|
100
|
+
dir_list: list = []
|
|
102
101
|
for dir_path in pj[cfg.OBSERVATIONS][obs].get(cfg.DIRECTORIES_LIST, []):
|
|
103
102
|
dir_list.append(dir_path)
|
|
104
103
|
media = "; ".join(dir_list)
|
|
105
104
|
|
|
106
105
|
# independent variables
|
|
107
|
-
indepvar = []
|
|
106
|
+
indepvar: list = []
|
|
108
107
|
if cfg.INDEPENDENT_VARIABLES in pj[cfg.OBSERVATIONS][obs]:
|
|
109
108
|
for var_label in indep_var_header:
|
|
110
109
|
if var_label in pj[cfg.OBSERVATIONS][obs][cfg.INDEPENDENT_VARIABLES]:
|
|
@@ -113,18 +112,14 @@ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str,
|
|
|
113
112
|
indepvar.append("")
|
|
114
113
|
|
|
115
114
|
# check unpaired events
|
|
116
|
-
ok, _ = project_functions.check_state_events_obs(
|
|
117
|
-
obs, pj[cfg.ETHOGRAM], pj[cfg.OBSERVATIONS][obs], cfg.HHMMSS
|
|
118
|
-
)
|
|
115
|
+
ok, _ = project_functions.check_state_events_obs(obs, pj[cfg.ETHOGRAM], pj[cfg.OBSERVATIONS][obs], cfg.HHMMSS)
|
|
119
116
|
if not ok:
|
|
120
117
|
not_paired.append(obs)
|
|
121
118
|
|
|
122
119
|
# exhaustivity
|
|
123
120
|
if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] in (cfg.MEDIA, cfg.LIVE):
|
|
124
121
|
# check exhaustivity of observation
|
|
125
|
-
exhaustivity = project_functions.check_observation_exhaustivity(
|
|
126
|
-
pj[cfg.OBSERVATIONS][obs][cfg.EVENTS], state_events_list
|
|
127
|
-
)
|
|
122
|
+
exhaustivity = project_functions.check_observation_exhaustivity(pj[cfg.OBSERVATIONS][obs][cfg.EVENTS], state_events_list)
|
|
128
123
|
elif pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.IMAGES:
|
|
129
124
|
exhaustivity = project_functions.check_observation_exhaustivity_pictures(pj[cfg.OBSERVATIONS][obs])
|
|
130
125
|
|
|
@@ -134,11 +129,12 @@ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str,
|
|
|
134
129
|
data, header=fields_list + indep_var_header, column_type=column_type, not_paired=not_paired
|
|
135
130
|
)
|
|
136
131
|
self.data = data
|
|
132
|
+
self.not_paired = not_paired
|
|
137
133
|
self.mem_hash_obs = hash(str(self.pj[cfg.OBSERVATIONS]))
|
|
138
134
|
|
|
139
135
|
else:
|
|
140
136
|
obsList = observations_list.observationsList_widget(
|
|
141
|
-
self.data, header=fields_list + indep_var_header, column_type=column_type, not_paired=not_paired
|
|
137
|
+
self.data, header=fields_list + indep_var_header, column_type=column_type, not_paired=self.not_paired
|
|
142
138
|
)
|
|
143
139
|
|
|
144
140
|
if windows_title:
|
|
@@ -212,177 +208,3 @@ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str,
|
|
|
212
208
|
resultStr = cfg.VIEW
|
|
213
209
|
|
|
214
210
|
return resultStr, selected_observations
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
'''
|
|
218
|
-
def select_observations(pj: dict, mode: str, windows_title: str = "") -> Tuple[str, list]:
|
|
219
|
-
"""
|
|
220
|
-
allow user to select observations
|
|
221
|
-
mode: accepted values: OPEN, EDIT, SINGLE, MULTIPLE, SELECT1
|
|
222
|
-
|
|
223
|
-
Args:
|
|
224
|
-
pj (dict): BORIS project dictionary
|
|
225
|
-
mode (str): mode for selection: OPEN, EDIT, SINGLE, MULTIPLE, SELECT1
|
|
226
|
-
windows_title (str): title for windows
|
|
227
|
-
|
|
228
|
-
Returns:
|
|
229
|
-
str: selected mode: OPEN, EDIT, VIEW
|
|
230
|
-
list: list of selected observations
|
|
231
|
-
"""
|
|
232
|
-
|
|
233
|
-
fields_list = ["id", "date", "description", "subjects", "observation duration", "exhaustivity %", "media"]
|
|
234
|
-
indep_var_header, column_type = [], [cfg.TEXT, cfg.TEXT, cfg.TEXT, cfg.TEXT, cfg.NUMERIC, cfg.NUMERIC, cfg.TEXT]
|
|
235
|
-
|
|
236
|
-
if cfg.INDEPENDENT_VARIABLES in pj:
|
|
237
|
-
for idx in util.sorted_keys(pj[cfg.INDEPENDENT_VARIABLES]):
|
|
238
|
-
indep_var_header.append(pj[cfg.INDEPENDENT_VARIABLES][idx]["label"])
|
|
239
|
-
column_type.append(pj[cfg.INDEPENDENT_VARIABLES][idx]["type"])
|
|
240
|
-
|
|
241
|
-
state_events_list = [
|
|
242
|
-
pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE]
|
|
243
|
-
for x in pj[cfg.ETHOGRAM]
|
|
244
|
-
if cfg.STATE in pj[cfg.ETHOGRAM][x][cfg.TYPE].upper()
|
|
245
|
-
]
|
|
246
|
-
|
|
247
|
-
data: list = []
|
|
248
|
-
not_paired: list = []
|
|
249
|
-
|
|
250
|
-
for obs in sorted(list(pj[cfg.OBSERVATIONS].keys())):
|
|
251
|
-
date = pj[cfg.OBSERVATIONS][obs]["date"].replace("T", " ")
|
|
252
|
-
descr = util.eol2space(pj[cfg.OBSERVATIONS][obs][cfg.DESCRIPTION])
|
|
253
|
-
|
|
254
|
-
# subjects
|
|
255
|
-
observed_subjects = [
|
|
256
|
-
cfg.NO_FOCAL_SUBJECT if x == "" else x for x in project_functions.extract_observed_subjects(pj, [obs])
|
|
257
|
-
]
|
|
258
|
-
|
|
259
|
-
subjectsList = ", ".join(observed_subjects)
|
|
260
|
-
|
|
261
|
-
# observed time interval
|
|
262
|
-
interval = project_functions.observed_interval(pj[cfg.OBSERVATIONS][obs])
|
|
263
|
-
observed_interval_str = str(interval[1] - interval[0])
|
|
264
|
-
|
|
265
|
-
# media
|
|
266
|
-
media = ""
|
|
267
|
-
if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.MEDIA:
|
|
268
|
-
media_list = []
|
|
269
|
-
if pj[cfg.OBSERVATIONS][obs][cfg.FILE]:
|
|
270
|
-
for player in sorted(pj[cfg.OBSERVATIONS][obs][cfg.FILE].keys()):
|
|
271
|
-
for media in pj[cfg.OBSERVATIONS][obs][cfg.FILE][player]:
|
|
272
|
-
media_list.append(f"#{player}: {media}")
|
|
273
|
-
|
|
274
|
-
if len(media_list) > 8:
|
|
275
|
-
media = " ".join(media_list)
|
|
276
|
-
else:
|
|
277
|
-
media = "\n".join(media_list)
|
|
278
|
-
|
|
279
|
-
if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.LIVE:
|
|
280
|
-
media = cfg.LIVE
|
|
281
|
-
|
|
282
|
-
if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.IMAGES:
|
|
283
|
-
dir_list = []
|
|
284
|
-
for dir_path in pj[cfg.OBSERVATIONS][obs].get(cfg.DIRECTORIES_LIST, []):
|
|
285
|
-
dir_list.append(dir_path)
|
|
286
|
-
media = "; ".join(dir_list)
|
|
287
|
-
|
|
288
|
-
# independent variables
|
|
289
|
-
indepvar = []
|
|
290
|
-
if cfg.INDEPENDENT_VARIABLES in pj[cfg.OBSERVATIONS][obs]:
|
|
291
|
-
for var_label in indep_var_header:
|
|
292
|
-
if var_label in pj[cfg.OBSERVATIONS][obs][cfg.INDEPENDENT_VARIABLES]:
|
|
293
|
-
indepvar.append(pj[cfg.OBSERVATIONS][obs][cfg.INDEPENDENT_VARIABLES][var_label])
|
|
294
|
-
else:
|
|
295
|
-
indepvar.append("")
|
|
296
|
-
|
|
297
|
-
# check unpaired events
|
|
298
|
-
ok, _ = project_functions.check_state_events_obs(obs, pj[cfg.ETHOGRAM], pj[cfg.OBSERVATIONS][obs], cfg.HHMMSS)
|
|
299
|
-
if not ok:
|
|
300
|
-
not_paired.append(obs)
|
|
301
|
-
|
|
302
|
-
# exhaustivity
|
|
303
|
-
if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] in (cfg.MEDIA, cfg.LIVE):
|
|
304
|
-
# check exhaustivity of observation
|
|
305
|
-
exhaustivity = project_functions.check_observation_exhaustivity(
|
|
306
|
-
pj[cfg.OBSERVATIONS][obs][cfg.EVENTS], state_events_list
|
|
307
|
-
)
|
|
308
|
-
elif pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.IMAGES:
|
|
309
|
-
# TODO: add exhaustivity for images observation (number of coded images?)
|
|
310
|
-
exhaustivity = project_functions.check_observation_exhaustivity_pictures(pj[cfg.OBSERVATIONS][obs])
|
|
311
|
-
|
|
312
|
-
data.append([obs, date, descr, subjectsList, observed_interval_str, str(exhaustivity), media] + indepvar)
|
|
313
|
-
|
|
314
|
-
obsList = observations_list.observationsList_widget(
|
|
315
|
-
data, header=fields_list + indep_var_header, column_type=column_type, not_paired=not_paired
|
|
316
|
-
)
|
|
317
|
-
if windows_title:
|
|
318
|
-
obsList.setWindowTitle(windows_title)
|
|
319
|
-
|
|
320
|
-
obsList.pbOpen.setVisible(False)
|
|
321
|
-
obsList.pbView.setVisible(False)
|
|
322
|
-
obsList.pbEdit.setVisible(False)
|
|
323
|
-
obsList.pbOk.setVisible(False)
|
|
324
|
-
obsList.pbSelectAll.setVisible(False)
|
|
325
|
-
obsList.pbUnSelectAll.setVisible(False)
|
|
326
|
-
obsList.mode = mode
|
|
327
|
-
|
|
328
|
-
if mode == cfg.OPEN:
|
|
329
|
-
obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
|
|
330
|
-
obsList.pbOpen.setVisible(True)
|
|
331
|
-
|
|
332
|
-
if mode == cfg.VIEW:
|
|
333
|
-
obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
|
|
334
|
-
obsList.pbView.setVisible(True)
|
|
335
|
-
|
|
336
|
-
if mode == cfg.EDIT:
|
|
337
|
-
obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
|
|
338
|
-
obsList.pbEdit.setVisible(True)
|
|
339
|
-
|
|
340
|
-
if mode == cfg.SINGLE:
|
|
341
|
-
obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
|
|
342
|
-
obsList.pbOpen.setVisible(True)
|
|
343
|
-
obsList.pbView.setVisible(True)
|
|
344
|
-
obsList.pbEdit.setVisible(True)
|
|
345
|
-
|
|
346
|
-
if mode == cfg.MULTIPLE:
|
|
347
|
-
obsList.view.setSelectionMode(QAbstractItemView.MultiSelection)
|
|
348
|
-
obsList.pbOk.setVisible(True)
|
|
349
|
-
obsList.pbSelectAll.setVisible(True)
|
|
350
|
-
obsList.pbUnSelectAll.setVisible(True)
|
|
351
|
-
|
|
352
|
-
if mode == cfg.SELECT1:
|
|
353
|
-
obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
|
|
354
|
-
obsList.pbOk.setVisible(True)
|
|
355
|
-
|
|
356
|
-
# restore window geometry
|
|
357
|
-
gui_utilities.restore_geometry(obsList, "observations list", (900, 600))
|
|
358
|
-
|
|
359
|
-
obsList.view.sortItems(0, Qt.AscendingOrder)
|
|
360
|
-
for row in range(obsList.view.rowCount()):
|
|
361
|
-
obsList.view.resizeRowToContents(row)
|
|
362
|
-
|
|
363
|
-
selected_observations = []
|
|
364
|
-
|
|
365
|
-
result = obsList.exec_()
|
|
366
|
-
|
|
367
|
-
# saving window geometry in ini file
|
|
368
|
-
gui_utilities.save_geometry(obsList, "observations list")
|
|
369
|
-
|
|
370
|
-
if result:
|
|
371
|
-
if obsList.view.selectedIndexes():
|
|
372
|
-
for idx in obsList.view.selectedIndexes():
|
|
373
|
-
if idx.column() == 0: # first column
|
|
374
|
-
selected_observations.append(idx.data())
|
|
375
|
-
|
|
376
|
-
if result == 0: # cancel
|
|
377
|
-
resultStr = ""
|
|
378
|
-
if result == 1: # select
|
|
379
|
-
resultStr = "ok"
|
|
380
|
-
if result == 2: # open
|
|
381
|
-
resultStr = cfg.OPEN
|
|
382
|
-
if result == 3: # edit
|
|
383
|
-
resultStr = cfg.EDIT
|
|
384
|
-
if result == 4: # view
|
|
385
|
-
resultStr = cfg.VIEW
|
|
386
|
-
|
|
387
|
-
return resultStr, selected_observations
|
|
388
|
-
'''
|