boris-behav-obs 8.9.16__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 +36 -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 +161 -77
- boris/config_file.py +63 -83
- boris/connections.py +112 -57
- boris/converters.py +13 -37
- boris/converters_ui.py +187 -110
- boris/cooccurence.py +250 -0
- boris/core.py +2511 -1824
- boris/core_qrc.py +15895 -10185
- boris/core_ui.py +946 -792
- boris/db_functions.py +21 -41
- boris/dev.py +134 -0
- boris/dialog.py +505 -244
- boris/duration_widget.py +15 -20
- boris/edit_event.py +84 -28
- boris/edit_event_ui.py +214 -78
- boris/event_operations.py +517 -415
- boris/events_cursor.py +25 -17
- boris/events_snapshots.py +36 -82
- boris/exclusion_matrix.py +4 -9
- boris/export_events.py +213 -583
- boris/export_observation.py +98 -611
- boris/external_processes.py +156 -97
- boris/geometric_measurement.py +652 -287
- boris/gui_utilities.py +91 -14
- boris/image_overlay.py +9 -9
- boris/import_observations.py +190 -98
- boris/ipc_mpv.py +325 -0
- boris/irr.py +26 -63
- boris/latency.py +34 -25
- boris/measurement_widget.py +14 -18
- boris/media_file.py +52 -84
- 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 +655 -310
- boris/observation_operations.py +1036 -404
- boris/observation_ui.py +584 -356
- boris/observations_list.py +71 -53
- boris/otx_parser.py +74 -80
- 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 +43 -46
- 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 +685 -228
- boris/project.py +448 -293
- boris/project_functions.py +689 -254
- 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 -199
- boris/select_subj_behav.py +67 -39
- boris/state_events.py +53 -37
- 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 +766 -266
- boris/version.py +3 -3
- boris/video_equalizer.py +16 -14
- boris/video_equalizer_ui.py +199 -130
- boris/video_operations.py +125 -28
- 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.9.16.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/boris_ui.py +0 -886
- boris/converters.ui +0 -289
- boris/core.qrc +0 -35
- boris/core.ui +0 -1543
- boris/edit_event.ui +0 -175
- boris/icons/logo_eye.ico +0 -0
- boris/map_creator.py +0 -850
- boris/observation.ui +0 -773
- 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.9.16.dist-info/LICENSE.TXT +0 -674
- boris_behav_obs-8.9.16.dist-info/METADATA +0 -129
- boris_behav_obs-8.9.16.dist-info/RECORD +0 -108
- boris_behav_obs-8.9.16.dist-info/entry_points.txt +0 -2
- {boris → boris_behav_obs-9.7.6.dist-info/licenses}/LICENSE.TXT +0 -0
- {boris_behav_obs-8.9.16.dist-info → boris_behav_obs-9.7.6.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,17 +22,32 @@ This file is part of BORIS.
|
|
|
22
22
|
|
|
23
23
|
import re
|
|
24
24
|
|
|
25
|
-
from
|
|
26
|
-
from
|
|
27
|
-
|
|
25
|
+
from PySide6.QtCore import Qt, QEvent
|
|
26
|
+
from PySide6.QtWidgets import (
|
|
27
|
+
QDialog,
|
|
28
|
+
QVBoxLayout,
|
|
29
|
+
QLabel,
|
|
30
|
+
QListWidget,
|
|
31
|
+
QHBoxLayout,
|
|
32
|
+
QLineEdit,
|
|
33
|
+
QPushButton,
|
|
34
|
+
QListWidgetItem,
|
|
35
|
+
QSizePolicy,
|
|
36
|
+
QSpacerItem,
|
|
37
|
+
QAbstractItemView,
|
|
38
|
+
QMessageBox,
|
|
39
|
+
)
|
|
28
40
|
|
|
29
41
|
from . import config as cfg
|
|
30
42
|
from . import utilities as util
|
|
31
43
|
|
|
32
44
|
|
|
33
45
|
class ModifiersList(QDialog):
|
|
34
|
-
|
|
46
|
+
"""
|
|
47
|
+
class for selection the modifier(s)
|
|
48
|
+
"""
|
|
35
49
|
|
|
50
|
+
def __init__(self, code: str, modifiers_dict: dict, currentModifier: str):
|
|
36
51
|
super().__init__()
|
|
37
52
|
self.setWindowTitle(cfg.programName)
|
|
38
53
|
self.setWindowFlags(Qt.WindowStaysOnTopHint)
|
|
@@ -49,23 +64,29 @@ class ModifiersList(QDialog):
|
|
|
49
64
|
self.modifiersSetNumber = 0
|
|
50
65
|
|
|
51
66
|
for idx in util.sorted_keys(modifiers_dict):
|
|
52
|
-
|
|
53
|
-
if self.modifiers_dict[idx]["type"] not in [
|
|
67
|
+
if self.modifiers_dict[idx]["type"] not in (
|
|
54
68
|
cfg.SINGLE_SELECTION,
|
|
55
69
|
cfg.MULTI_SELECTION,
|
|
56
70
|
cfg.NUMERIC_MODIFIER,
|
|
57
|
-
|
|
71
|
+
):
|
|
58
72
|
continue
|
|
59
73
|
|
|
60
74
|
V2layout = QVBoxLayout()
|
|
61
75
|
|
|
62
76
|
self.modifiersSetNumber += 1
|
|
63
77
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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)
|
|
81
|
+
|
|
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)
|
|
67
85
|
|
|
68
|
-
if self.modifiers_dict[idx]["type"] in
|
|
86
|
+
if self.modifiers_dict[idx]["type"] in (
|
|
87
|
+
cfg.SINGLE_SELECTION,
|
|
88
|
+
cfg.MULTI_SELECTION,
|
|
89
|
+
):
|
|
69
90
|
lw = QListWidget()
|
|
70
91
|
self.modifiers_dict[idx]["widget"] = lw
|
|
71
92
|
lw.setObjectName(f"lw_modifiers_({self.modifiers_dict[idx]['type']})")
|
|
@@ -83,9 +104,7 @@ class ModifiersList(QDialog):
|
|
|
83
104
|
|
|
84
105
|
# previously selected
|
|
85
106
|
try:
|
|
86
|
-
if currentModifierList != [""] and re.sub(" \(.\)", "", modifier) in currentModifierList[
|
|
87
|
-
int(idx)
|
|
88
|
-
].split(","):
|
|
107
|
+
if currentModifierList != [""] and re.sub(r" \(.\)", "", modifier) in currentModifierList[int(idx)].split(","):
|
|
89
108
|
item.setCheckState(Qt.Checked)
|
|
90
109
|
except Exception: # for old projects due to a fixed bug
|
|
91
110
|
pass
|
|
@@ -94,10 +113,7 @@ class ModifiersList(QDialog):
|
|
|
94
113
|
|
|
95
114
|
if self.modifiers_dict[idx]["type"] == cfg.SINGLE_SELECTION:
|
|
96
115
|
try:
|
|
97
|
-
if (
|
|
98
|
-
currentModifierList != [""]
|
|
99
|
-
and re.sub(" \(.\)", "", modifier) == currentModifierList[int(idx)]
|
|
100
|
-
):
|
|
116
|
+
if currentModifierList != [""] and re.sub(r" \(.\)", "", modifier) == currentModifierList[int(idx)]:
|
|
101
117
|
item.setSelected(True)
|
|
102
118
|
except Exception: # for old projects due to a fixed bug
|
|
103
119
|
pass
|
|
@@ -106,6 +122,7 @@ class ModifiersList(QDialog):
|
|
|
106
122
|
if self.modifiers_dict[idx]["type"] in [cfg.NUMERIC_MODIFIER]:
|
|
107
123
|
le = QLineEdit()
|
|
108
124
|
self.modifiers_dict[idx]["widget"] = le
|
|
125
|
+
le.setObjectName(f"le_modifiers_({self.modifiers_dict[idx]['type']})")
|
|
109
126
|
|
|
110
127
|
if currentModifierList != [""] and currentModifierList[int(idx)] != "None":
|
|
111
128
|
le.setText(currentModifierList[int(idx)])
|
|
@@ -152,12 +169,15 @@ class ModifiersList(QDialog):
|
|
|
152
169
|
|
|
153
170
|
# accept and close dialog if enter pressed
|
|
154
171
|
if ek == Qt.Key_Enter or ek == Qt.Key_Return: # enter or enter from numeric pad
|
|
155
|
-
self.
|
|
172
|
+
if not self.pbOK_clicked():
|
|
173
|
+
return False
|
|
156
174
|
return True
|
|
157
175
|
|
|
176
|
+
modifiersSetIndex = 0
|
|
158
177
|
for widget in self.children():
|
|
178
|
+
if "_modifiers" in widget.objectName():
|
|
179
|
+
modifiersSetIndex = modifiersSetIndex + 1
|
|
159
180
|
if "lw_modifiers" in widget.objectName():
|
|
160
|
-
|
|
161
181
|
if self.modifiersSetNumber == 1:
|
|
162
182
|
# check if modifiers have code
|
|
163
183
|
for index in range(widget.count()):
|
|
@@ -165,11 +185,8 @@ class ModifiersList(QDialog):
|
|
|
165
185
|
break
|
|
166
186
|
else:
|
|
167
187
|
# modifiers have no associated code: the modifier starting with hit key will be selected
|
|
168
|
-
if ek not in
|
|
169
|
-
|
|
170
|
-
if (
|
|
171
|
-
ek == Qt.Key_Space and f"({cfg.MULTI_SELECTION})" in widget.objectName()
|
|
172
|
-
): # checking using SPACE bar
|
|
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
|
|
173
190
|
if widget.item(widget.currentRow()).checkState() == Qt.Checked:
|
|
174
191
|
widget.item(widget.currentRow()).setCheckState(Qt.Unchecked)
|
|
175
192
|
else:
|
|
@@ -179,7 +196,10 @@ class ModifiersList(QDialog):
|
|
|
179
196
|
for index in range(widget.count()):
|
|
180
197
|
if widget.item(index).text().upper().startswith(ek_text.upper()):
|
|
181
198
|
widget.setCurrentRow(index)
|
|
182
|
-
widget.scrollToItem(
|
|
199
|
+
widget.scrollToItem(
|
|
200
|
+
widget.item(index),
|
|
201
|
+
QAbstractItemView.EnsureVisible,
|
|
202
|
+
)
|
|
183
203
|
return True
|
|
184
204
|
else: # up / down keys
|
|
185
205
|
try:
|
|
@@ -191,7 +211,7 @@ class ModifiersList(QDialog):
|
|
|
191
211
|
return
|
|
192
212
|
|
|
193
213
|
for index in range(widget.count()):
|
|
194
|
-
|
|
214
|
+
# check function kesy (F1, F2...)
|
|
195
215
|
if ek in cfg.function_keys:
|
|
196
216
|
if f"({cfg.function_keys[ek]})" in widget.item(index).text().upper():
|
|
197
217
|
if f"({cfg.SINGLE_SELECTION})" in widget.objectName():
|
|
@@ -200,6 +220,10 @@ class ModifiersList(QDialog):
|
|
|
200
220
|
if self.modifiersSetNumber == 1:
|
|
201
221
|
self.accept()
|
|
202
222
|
return True
|
|
223
|
+
# else move to next set of mofifiers
|
|
224
|
+
elif modifiersSetIndex != self.modifiersSetNumber:
|
|
225
|
+
widget.parent().focusNextChild()
|
|
226
|
+
return True
|
|
203
227
|
|
|
204
228
|
if f"({cfg.MULTI_SELECTION})" in widget.objectName():
|
|
205
229
|
if widget.item(index).checkState() == Qt.Checked:
|
|
@@ -208,13 +232,16 @@ class ModifiersList(QDialog):
|
|
|
208
232
|
widget.item(index).setCheckState(Qt.Checked)
|
|
209
233
|
|
|
210
234
|
if ek < 1114112 and f"({ek_text})" in widget.item(index).text():
|
|
211
|
-
|
|
212
235
|
if f"({cfg.SINGLE_SELECTION})" in widget.objectName():
|
|
213
236
|
widget.item(index).setSelected(True)
|
|
214
237
|
# close dialog if one set of modifiers
|
|
215
238
|
if self.modifiersSetNumber == 1:
|
|
216
239
|
self.accept()
|
|
217
240
|
return True
|
|
241
|
+
# else move to next set of mofifiers
|
|
242
|
+
elif modifiersSetIndex != self.modifiersSetNumber:
|
|
243
|
+
widget.parent().focusNextChild()
|
|
244
|
+
return True
|
|
218
245
|
|
|
219
246
|
if f"({cfg.MULTI_SELECTION})" in widget.objectName():
|
|
220
247
|
if widget.item(index).checkState() == Qt.Checked:
|
|
@@ -231,17 +258,24 @@ class ModifiersList(QDialog):
|
|
|
231
258
|
get modifiers
|
|
232
259
|
returns list of selected modifiers
|
|
233
260
|
"""
|
|
234
|
-
modifiers = []
|
|
235
|
-
for idx in util.sorted_keys(self.modifiers_dict):
|
|
236
261
|
|
|
237
|
-
|
|
262
|
+
for idx in util.sorted_keys(self.modifiers_dict):
|
|
263
|
+
if self.modifiers_dict[idx]["type"] in [
|
|
264
|
+
cfg.SINGLE_SELECTION,
|
|
265
|
+
cfg.MULTI_SELECTION,
|
|
266
|
+
cfg.NUMERIC_MODIFIER,
|
|
267
|
+
]:
|
|
238
268
|
self.modifiers_dict[idx]["selected"] = []
|
|
239
269
|
|
|
240
270
|
if self.modifiers_dict[idx]["type"] == cfg.MULTI_SELECTION:
|
|
241
271
|
for j in range(self.modifiers_dict[idx]["widget"].count()):
|
|
242
272
|
if self.modifiers_dict[idx]["widget"].item(j).checkState() == Qt.Checked:
|
|
243
273
|
self.modifiers_dict[idx]["selected"].append(
|
|
244
|
-
re.sub(
|
|
274
|
+
re.sub(
|
|
275
|
+
r" \(.*\)",
|
|
276
|
+
"",
|
|
277
|
+
self.modifiers_dict[idx]["widget"].item(j).text(),
|
|
278
|
+
)
|
|
245
279
|
)
|
|
246
280
|
|
|
247
281
|
if not self.modifiers_dict[idx]["selected"]:
|
|
@@ -249,38 +283,30 @@ class ModifiersList(QDialog):
|
|
|
249
283
|
|
|
250
284
|
if self.modifiers_dict[idx]["type"] == cfg.SINGLE_SELECTION:
|
|
251
285
|
for item in self.modifiers_dict[idx]["widget"].selectedItems():
|
|
252
|
-
self.modifiers_dict[idx]["selected"].append(re.sub(" \(.*\)", "", item.text()))
|
|
286
|
+
self.modifiers_dict[idx]["selected"].append(re.sub(r" \(.*\)", "", item.text()))
|
|
253
287
|
|
|
254
288
|
if self.modifiers_dict[idx]["type"] == cfg.NUMERIC_MODIFIER:
|
|
255
289
|
self.modifiers_dict[idx]["selected"] = (
|
|
256
290
|
self.modifiers_dict[idx]["widget"].text() if self.modifiers_dict[idx]["widget"].text() else "None"
|
|
257
291
|
)
|
|
258
|
-
"""
|
|
259
|
-
for widget in self.children():
|
|
260
|
-
if widget.objectName() == "lw_modifiers_classic":
|
|
261
|
-
for item in widget.selectedItems():
|
|
262
|
-
modifiers.append(re.sub(" \(.*\)", "", item.text()))
|
|
263
|
-
if widget.objectName() == "lw_modifiers_from_set":
|
|
264
|
-
for idx in range(widget.count()):
|
|
265
|
-
if widget.item(idx).checkState() == Qt.Checked:
|
|
266
|
-
modifiers.append(widget.item(idx).text())
|
|
267
|
-
"""
|
|
268
292
|
|
|
269
293
|
return self.modifiers_dict
|
|
270
294
|
|
|
271
295
|
def pbOK_clicked(self):
|
|
272
|
-
|
|
296
|
+
"""
|
|
297
|
+
OK button is clicked
|
|
298
|
+
"""
|
|
273
299
|
for idx in util.sorted_keys(self.modifiers_dict):
|
|
274
300
|
if self.modifiers_dict[idx]["type"] == cfg.NUMERIC_MODIFIER:
|
|
275
301
|
if self.modifiers_dict[idx]["widget"].text():
|
|
276
302
|
try:
|
|
277
|
-
|
|
303
|
+
_ = float(self.modifiers_dict[idx]["widget"].text())
|
|
278
304
|
except Exception:
|
|
279
305
|
QMessageBox.warning(
|
|
280
306
|
self,
|
|
281
307
|
cfg.programName,
|
|
282
|
-
"<b>{}</b> is not a numeric value"
|
|
308
|
+
f"<b>{self.modifiers_dict[idx]['widget'].text()}</b> is not a numeric value",
|
|
283
309
|
)
|
|
284
|
-
return
|
|
310
|
+
return False
|
|
285
311
|
|
|
286
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
|
-
|
|
25
|
-
from
|
|
23
|
+
|
|
24
|
+
import logging
|
|
25
|
+
from typing import Tuple
|
|
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,38 +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
|
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
# check if observations changed
|
|
68
66
|
if hash(str(self.pj[cfg.OBSERVATIONS])) != self.mem_hash_obs:
|
|
67
|
+
logging.debug("observations changed")
|
|
69
68
|
|
|
70
69
|
for obs in sorted(list(pj[cfg.OBSERVATIONS].keys())):
|
|
71
70
|
date = pj[cfg.OBSERVATIONS][obs]["date"].replace("T", " ")
|
|
72
71
|
descr = util.eol2space(pj[cfg.OBSERVATIONS][obs][cfg.DESCRIPTION])
|
|
73
72
|
|
|
74
73
|
# subjects
|
|
75
|
-
observed_subjects = [
|
|
76
|
-
cfg.NO_FOCAL_SUBJECT if x == "" else x for x in project_functions.extract_observed_subjects(pj, [obs])
|
|
77
|
-
]
|
|
74
|
+
observed_subjects = [cfg.NO_FOCAL_SUBJECT if x == "" else x for x in project_functions.extract_observed_subjects(pj, [obs])]
|
|
78
75
|
|
|
79
76
|
subjectsList = ", ".join(observed_subjects)
|
|
80
77
|
|
|
81
78
|
# observed time interval
|
|
82
79
|
interval = project_functions.observed_interval(pj[cfg.OBSERVATIONS][obs])
|
|
83
|
-
observed_interval_str = str(interval[1] - interval[0])
|
|
80
|
+
observed_interval_str = str(round(interval[1] - interval[0], 3))
|
|
84
81
|
|
|
85
82
|
# media
|
|
86
|
-
media = ""
|
|
83
|
+
media: str = ""
|
|
87
84
|
if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.MEDIA:
|
|
88
|
-
media_list = []
|
|
85
|
+
media_list: list = []
|
|
89
86
|
if pj[cfg.OBSERVATIONS][obs][cfg.FILE]:
|
|
90
87
|
for player in sorted(pj[cfg.OBSERVATIONS][obs][cfg.FILE].keys()):
|
|
91
88
|
for media in pj[cfg.OBSERVATIONS][obs][cfg.FILE][player]:
|
|
@@ -100,13 +97,13 @@ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str,
|
|
|
100
97
|
media = cfg.LIVE
|
|
101
98
|
|
|
102
99
|
if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.IMAGES:
|
|
103
|
-
dir_list = []
|
|
100
|
+
dir_list: list = []
|
|
104
101
|
for dir_path in pj[cfg.OBSERVATIONS][obs].get(cfg.DIRECTORIES_LIST, []):
|
|
105
102
|
dir_list.append(dir_path)
|
|
106
103
|
media = "; ".join(dir_list)
|
|
107
104
|
|
|
108
105
|
# independent variables
|
|
109
|
-
indepvar = []
|
|
106
|
+
indepvar: list = []
|
|
110
107
|
if cfg.INDEPENDENT_VARIABLES in pj[cfg.OBSERVATIONS][obs]:
|
|
111
108
|
for var_label in indep_var_header:
|
|
112
109
|
if var_label in pj[cfg.OBSERVATIONS][obs][cfg.INDEPENDENT_VARIABLES]:
|
|
@@ -115,20 +112,15 @@ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str,
|
|
|
115
112
|
indepvar.append("")
|
|
116
113
|
|
|
117
114
|
# check unpaired events
|
|
118
|
-
ok, _ = project_functions.check_state_events_obs(
|
|
119
|
-
obs, pj[cfg.ETHOGRAM], pj[cfg.OBSERVATIONS][obs], cfg.HHMMSS
|
|
120
|
-
)
|
|
115
|
+
ok, _ = project_functions.check_state_events_obs(obs, pj[cfg.ETHOGRAM], pj[cfg.OBSERVATIONS][obs], cfg.HHMMSS)
|
|
121
116
|
if not ok:
|
|
122
117
|
not_paired.append(obs)
|
|
123
118
|
|
|
124
119
|
# exhaustivity
|
|
125
120
|
if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] in (cfg.MEDIA, cfg.LIVE):
|
|
126
121
|
# check exhaustivity of observation
|
|
127
|
-
exhaustivity = project_functions.check_observation_exhaustivity(
|
|
128
|
-
pj[cfg.OBSERVATIONS][obs][cfg.EVENTS], state_events_list
|
|
129
|
-
)
|
|
122
|
+
exhaustivity = project_functions.check_observation_exhaustivity(pj[cfg.OBSERVATIONS][obs][cfg.EVENTS], state_events_list)
|
|
130
123
|
elif pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.IMAGES:
|
|
131
|
-
# TODO: add exhaustivity for images observation (number of coded images?)
|
|
132
124
|
exhaustivity = project_functions.check_observation_exhaustivity_pictures(pj[cfg.OBSERVATIONS][obs])
|
|
133
125
|
|
|
134
126
|
data.append([obs, date, descr, subjectsList, observed_interval_str, str(exhaustivity), media] + indepvar)
|
|
@@ -137,11 +129,12 @@ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str,
|
|
|
137
129
|
data, header=fields_list + indep_var_header, column_type=column_type, not_paired=not_paired
|
|
138
130
|
)
|
|
139
131
|
self.data = data
|
|
132
|
+
self.not_paired = not_paired
|
|
140
133
|
self.mem_hash_obs = hash(str(self.pj[cfg.OBSERVATIONS]))
|
|
141
134
|
|
|
142
135
|
else:
|
|
143
136
|
obsList = observations_list.observationsList_widget(
|
|
144
|
-
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
|
|
145
138
|
)
|
|
146
139
|
|
|
147
140
|
if windows_title:
|
|
@@ -215,175 +208,3 @@ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str,
|
|
|
215
208
|
resultStr = cfg.VIEW
|
|
216
209
|
|
|
217
210
|
return resultStr, selected_observations
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
def select_observations(pj: dict, mode: str, windows_title: str = "") -> Tuple[str, list]:
|
|
221
|
-
"""
|
|
222
|
-
allow user to select observations
|
|
223
|
-
mode: accepted values: OPEN, EDIT, SINGLE, MULTIPLE, SELECT1
|
|
224
|
-
|
|
225
|
-
Args:
|
|
226
|
-
pj (dict): BORIS project dictionary
|
|
227
|
-
mode (str): mode for selection: OPEN, EDIT, SINGLE, MULTIPLE, SELECT1
|
|
228
|
-
windows_title (str): title for windows
|
|
229
|
-
|
|
230
|
-
Returns:
|
|
231
|
-
str: selected mode: OPEN, EDIT, VIEW
|
|
232
|
-
list: list of selected observations
|
|
233
|
-
"""
|
|
234
|
-
|
|
235
|
-
fields_list = ["id", "date", "description", "subjects", "observation duration", "exhaustivity %", "media"]
|
|
236
|
-
indep_var_header, column_type = [], [cfg.TEXT, cfg.TEXT, cfg.TEXT, cfg.TEXT, cfg.NUMERIC, cfg.NUMERIC, cfg.TEXT]
|
|
237
|
-
|
|
238
|
-
if cfg.INDEPENDENT_VARIABLES in pj:
|
|
239
|
-
for idx in util.sorted_keys(pj[cfg.INDEPENDENT_VARIABLES]):
|
|
240
|
-
indep_var_header.append(pj[cfg.INDEPENDENT_VARIABLES][idx]["label"])
|
|
241
|
-
column_type.append(pj[cfg.INDEPENDENT_VARIABLES][idx]["type"])
|
|
242
|
-
|
|
243
|
-
state_events_list = [
|
|
244
|
-
pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE]
|
|
245
|
-
for x in pj[cfg.ETHOGRAM]
|
|
246
|
-
if cfg.STATE in pj[cfg.ETHOGRAM][x][cfg.TYPE].upper()
|
|
247
|
-
]
|
|
248
|
-
|
|
249
|
-
data: list = []
|
|
250
|
-
not_paired: list = []
|
|
251
|
-
|
|
252
|
-
for obs in sorted(list(pj[cfg.OBSERVATIONS].keys())):
|
|
253
|
-
date = pj[cfg.OBSERVATIONS][obs]["date"].replace("T", " ")
|
|
254
|
-
descr = util.eol2space(pj[cfg.OBSERVATIONS][obs][cfg.DESCRIPTION])
|
|
255
|
-
|
|
256
|
-
# subjects
|
|
257
|
-
observed_subjects = [
|
|
258
|
-
cfg.NO_FOCAL_SUBJECT if x == "" else x for x in project_functions.extract_observed_subjects(pj, [obs])
|
|
259
|
-
]
|
|
260
|
-
|
|
261
|
-
subjectsList = ", ".join(observed_subjects)
|
|
262
|
-
|
|
263
|
-
# observed time interval
|
|
264
|
-
interval = project_functions.observed_interval(pj[cfg.OBSERVATIONS][obs])
|
|
265
|
-
observed_interval_str = str(interval[1] - interval[0])
|
|
266
|
-
|
|
267
|
-
# media
|
|
268
|
-
media = ""
|
|
269
|
-
if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.MEDIA:
|
|
270
|
-
media_list = []
|
|
271
|
-
if pj[cfg.OBSERVATIONS][obs][cfg.FILE]:
|
|
272
|
-
for player in sorted(pj[cfg.OBSERVATIONS][obs][cfg.FILE].keys()):
|
|
273
|
-
for media in pj[cfg.OBSERVATIONS][obs][cfg.FILE][player]:
|
|
274
|
-
media_list.append(f"#{player}: {media}")
|
|
275
|
-
|
|
276
|
-
if len(media_list) > 8:
|
|
277
|
-
media = " ".join(media_list)
|
|
278
|
-
else:
|
|
279
|
-
media = "\n".join(media_list)
|
|
280
|
-
|
|
281
|
-
if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.LIVE:
|
|
282
|
-
media = cfg.LIVE
|
|
283
|
-
|
|
284
|
-
if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.IMAGES:
|
|
285
|
-
dir_list = []
|
|
286
|
-
for dir_path in pj[cfg.OBSERVATIONS][obs].get(cfg.DIRECTORIES_LIST, []):
|
|
287
|
-
dir_list.append(dir_path)
|
|
288
|
-
media = "; ".join(dir_list)
|
|
289
|
-
|
|
290
|
-
# independent variables
|
|
291
|
-
indepvar = []
|
|
292
|
-
if cfg.INDEPENDENT_VARIABLES in pj[cfg.OBSERVATIONS][obs]:
|
|
293
|
-
for var_label in indep_var_header:
|
|
294
|
-
if var_label in pj[cfg.OBSERVATIONS][obs][cfg.INDEPENDENT_VARIABLES]:
|
|
295
|
-
indepvar.append(pj[cfg.OBSERVATIONS][obs][cfg.INDEPENDENT_VARIABLES][var_label])
|
|
296
|
-
else:
|
|
297
|
-
indepvar.append("")
|
|
298
|
-
|
|
299
|
-
# check unpaired events
|
|
300
|
-
ok, _ = project_functions.check_state_events_obs(obs, pj[cfg.ETHOGRAM], pj[cfg.OBSERVATIONS][obs], cfg.HHMMSS)
|
|
301
|
-
if not ok:
|
|
302
|
-
not_paired.append(obs)
|
|
303
|
-
|
|
304
|
-
# exhaustivity
|
|
305
|
-
if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] in (cfg.MEDIA, cfg.LIVE):
|
|
306
|
-
# check exhaustivity of observation
|
|
307
|
-
exhaustivity = project_functions.check_observation_exhaustivity(
|
|
308
|
-
pj[cfg.OBSERVATIONS][obs][cfg.EVENTS], state_events_list
|
|
309
|
-
)
|
|
310
|
-
elif pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.IMAGES:
|
|
311
|
-
# TODO: add exhaustivity for images observation (number of coded images?)
|
|
312
|
-
exhaustivity = project_functions.check_observation_exhaustivity_pictures(pj[cfg.OBSERVATIONS][obs])
|
|
313
|
-
|
|
314
|
-
data.append([obs, date, descr, subjectsList, observed_interval_str, str(exhaustivity), media] + indepvar)
|
|
315
|
-
|
|
316
|
-
obsList = observations_list.observationsList_widget(
|
|
317
|
-
data, header=fields_list + indep_var_header, column_type=column_type, not_paired=not_paired
|
|
318
|
-
)
|
|
319
|
-
if windows_title:
|
|
320
|
-
obsList.setWindowTitle(windows_title)
|
|
321
|
-
|
|
322
|
-
obsList.pbOpen.setVisible(False)
|
|
323
|
-
obsList.pbView.setVisible(False)
|
|
324
|
-
obsList.pbEdit.setVisible(False)
|
|
325
|
-
obsList.pbOk.setVisible(False)
|
|
326
|
-
obsList.pbSelectAll.setVisible(False)
|
|
327
|
-
obsList.pbUnSelectAll.setVisible(False)
|
|
328
|
-
obsList.mode = mode
|
|
329
|
-
|
|
330
|
-
if mode == cfg.OPEN:
|
|
331
|
-
obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
|
|
332
|
-
obsList.pbOpen.setVisible(True)
|
|
333
|
-
|
|
334
|
-
if mode == cfg.VIEW:
|
|
335
|
-
obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
|
|
336
|
-
obsList.pbView.setVisible(True)
|
|
337
|
-
|
|
338
|
-
if mode == cfg.EDIT:
|
|
339
|
-
obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
|
|
340
|
-
obsList.pbEdit.setVisible(True)
|
|
341
|
-
|
|
342
|
-
if mode == cfg.SINGLE:
|
|
343
|
-
obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
|
|
344
|
-
obsList.pbOpen.setVisible(True)
|
|
345
|
-
obsList.pbView.setVisible(True)
|
|
346
|
-
obsList.pbEdit.setVisible(True)
|
|
347
|
-
|
|
348
|
-
if mode == cfg.MULTIPLE:
|
|
349
|
-
obsList.view.setSelectionMode(QAbstractItemView.MultiSelection)
|
|
350
|
-
obsList.pbOk.setVisible(True)
|
|
351
|
-
obsList.pbSelectAll.setVisible(True)
|
|
352
|
-
obsList.pbUnSelectAll.setVisible(True)
|
|
353
|
-
|
|
354
|
-
if mode == cfg.SELECT1:
|
|
355
|
-
obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
|
|
356
|
-
obsList.pbOk.setVisible(True)
|
|
357
|
-
|
|
358
|
-
# restore window geometry
|
|
359
|
-
gui_utilities.restore_geometry(obsList, "observations list", (900, 600))
|
|
360
|
-
|
|
361
|
-
obsList.view.sortItems(0, Qt.AscendingOrder)
|
|
362
|
-
for row in range(obsList.view.rowCount()):
|
|
363
|
-
obsList.view.resizeRowToContents(row)
|
|
364
|
-
|
|
365
|
-
selected_observations = []
|
|
366
|
-
|
|
367
|
-
result = obsList.exec_()
|
|
368
|
-
|
|
369
|
-
# saving window geometry in ini file
|
|
370
|
-
gui_utilities.save_geometry(obsList, "observations list")
|
|
371
|
-
|
|
372
|
-
if result:
|
|
373
|
-
if obsList.view.selectedIndexes():
|
|
374
|
-
for idx in obsList.view.selectedIndexes():
|
|
375
|
-
if idx.column() == 0: # first column
|
|
376
|
-
selected_observations.append(idx.data())
|
|
377
|
-
|
|
378
|
-
if result == 0: # cancel
|
|
379
|
-
resultStr = ""
|
|
380
|
-
if result == 1: # select
|
|
381
|
-
resultStr = "ok"
|
|
382
|
-
if result == 2: # open
|
|
383
|
-
resultStr = cfg.OPEN
|
|
384
|
-
if result == 3: # edit
|
|
385
|
-
resultStr = cfg.EDIT
|
|
386
|
-
if result == 4: # view
|
|
387
|
-
resultStr = cfg.VIEW
|
|
388
|
-
|
|
389
|
-
return resultStr, selected_observations
|