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/select_subj_behav.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
|
|
|
@@ -21,15 +21,16 @@ This file is part of BORIS.
|
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
23
|
import logging
|
|
24
|
-
|
|
25
|
-
from PyQt5.QtCore import Qt
|
|
26
|
-
from PyQt5.QtGui import QFont
|
|
27
|
-
from PyQt5.QtWidgets import QCheckBox, QListWidgetItem, QMessageBox
|
|
28
24
|
from decimal import Decimal as dec
|
|
25
|
+
from typing import Optional
|
|
26
|
+
|
|
27
|
+
from PySide6.QtCore import Qt
|
|
28
|
+
from PySide6.QtGui import QFont
|
|
29
|
+
from PySide6.QtWidgets import QCheckBox, QListWidgetItem, QMessageBox
|
|
30
|
+
|
|
29
31
|
from . import config as cfg
|
|
30
32
|
from . import gui_utilities, param_panel, project_functions
|
|
31
33
|
from . import utilities as util
|
|
32
|
-
from typing import Optional
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
def choose_obs_subj_behav_category(
|
|
@@ -37,33 +38,37 @@ def choose_obs_subj_behav_category(
|
|
|
37
38
|
selected_observations: list,
|
|
38
39
|
start_coding: Optional[dec] = dec("NaN"), # Union[..., None]
|
|
39
40
|
end_coding: Optional[dec] = dec("NaN"),
|
|
41
|
+
start_interval: Optional[dec] = dec("NaN"),
|
|
42
|
+
end_interval: Optional[dec] = dec("NaN"),
|
|
40
43
|
maxTime: Optional[dec] = None,
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
show_include_modifiers: bool = True,
|
|
45
|
+
show_exclude_non_coded_behaviors: bool = True,
|
|
43
46
|
by_category: bool = False,
|
|
44
47
|
n_observations: int = 1,
|
|
45
48
|
show_time_bin_size: bool = False,
|
|
46
49
|
window_title: str = "Select subjects and behaviors",
|
|
47
|
-
|
|
50
|
+
show_exclude_non_coded_modifiers: bool = False,
|
|
51
|
+
) -> dict:
|
|
48
52
|
"""
|
|
49
53
|
show window for:
|
|
50
54
|
- selection of subjects
|
|
51
55
|
- selection of behaviors (based on selected subjects)
|
|
52
56
|
- selection of time interval
|
|
53
57
|
- inclusion/exclusion of modifiers
|
|
54
|
-
- inclusion/exclusion of behaviors without events (
|
|
58
|
+
- inclusion/exclusion of behaviors without events (show_exclude_non_coded_behaviors == True)
|
|
55
59
|
- selection of time bin size (show_time_bin_size == True)
|
|
56
60
|
|
|
57
61
|
Args:
|
|
58
62
|
selected_observations (list): List of selected observations
|
|
59
63
|
...
|
|
64
|
+
show_exclude_non_coded_modifiers (bool): display the Exclude non coded modifiers checkbox
|
|
60
65
|
|
|
61
66
|
Returns:
|
|
62
67
|
dict: {"selected subjects": selectedSubjects,
|
|
63
68
|
"selected behaviors": selectedBehaviors,
|
|
64
69
|
"include modifiers": True/False,
|
|
65
70
|
"exclude behaviors": True/False,
|
|
66
|
-
"time": TIME_FULL_OBS / TIME_EVENTS / TIME_ARBITRARY_INTERVAL
|
|
71
|
+
"time": TIME_FULL_OBS / TIME_EVENTS / TIME_ARBITRARY_INTERVAL / TIME_OBS_INTERVAL
|
|
67
72
|
"start time": startTime,
|
|
68
73
|
"end time": endTime
|
|
69
74
|
}
|
|
@@ -77,34 +82,51 @@ def choose_obs_subj_behav_category(
|
|
|
77
82
|
paramPanelWindow.pj = self.pj
|
|
78
83
|
paramPanelWindow.extract_observed_behaviors = self.extract_observed_behaviors
|
|
79
84
|
|
|
80
|
-
paramPanelWindow.cbIncludeModifiers.setVisible(
|
|
81
|
-
paramPanelWindow.
|
|
85
|
+
paramPanelWindow.cbIncludeModifiers.setVisible(show_include_modifiers)
|
|
86
|
+
paramPanelWindow.cb_exclude_non_coded_modifiers.setVisible(show_exclude_non_coded_modifiers)
|
|
87
|
+
paramPanelWindow.cbExcludeBehaviors.setVisible(show_exclude_non_coded_behaviors)
|
|
82
88
|
# show_time_bin_size:
|
|
83
89
|
paramPanelWindow.frm_time_bin_size.setVisible(show_time_bin_size)
|
|
84
90
|
|
|
85
91
|
if by_category:
|
|
86
92
|
paramPanelWindow.cbIncludeModifiers.setVisible(False)
|
|
87
93
|
paramPanelWindow.cbExcludeBehaviors.setVisible(False)
|
|
94
|
+
paramPanelWindow.cb_exclude_non_coded_modifiers.setVisible(False)
|
|
95
|
+
|
|
96
|
+
# set state of cb_exclude_non_coded_modifiers
|
|
97
|
+
paramPanelWindow.cb_exclude_non_coded_modifiers.setEnabled(paramPanelWindow.cbIncludeModifiers.isChecked())
|
|
88
98
|
|
|
89
99
|
paramPanelWindow.media_duration = maxTime
|
|
90
100
|
paramPanelWindow.start_coding = start_coding
|
|
91
101
|
paramPanelWindow.end_coding = end_coding
|
|
102
|
+
paramPanelWindow.start_interval = start_interval
|
|
103
|
+
paramPanelWindow.end_interval = end_interval
|
|
92
104
|
|
|
93
|
-
|
|
94
|
-
|
|
105
|
+
if self.timeFormat == cfg.S:
|
|
106
|
+
paramPanelWindow.start_time.rb_seconds.setChecked(True)
|
|
107
|
+
paramPanelWindow.end_time.rb_seconds.setChecked(True)
|
|
108
|
+
if self.timeFormat == cfg.HHMMSS:
|
|
109
|
+
paramPanelWindow.start_time.rb_time.setChecked(True)
|
|
110
|
+
paramPanelWindow.end_time.rb_time.setChecked(True)
|
|
95
111
|
|
|
96
112
|
if n_observations > 1:
|
|
97
113
|
paramPanelWindow.frm_time_interval.setVisible(False)
|
|
98
114
|
else:
|
|
99
115
|
if (start_coding is None) or (start_coding.is_nan()):
|
|
116
|
+
paramPanelWindow.rb_observed_events.setEnabled(False)
|
|
100
117
|
paramPanelWindow.frm_time_interval.setVisible(False)
|
|
101
118
|
paramPanelWindow.rb_user_defined.setVisible(False)
|
|
119
|
+
paramPanelWindow.rb_obs_interval.setVisible(False)
|
|
102
120
|
paramPanelWindow.rb_media_duration.setVisible(False)
|
|
103
121
|
else:
|
|
104
122
|
paramPanelWindow.frm_time_interval.setEnabled(False)
|
|
105
123
|
paramPanelWindow.start_time.set_time(start_coding)
|
|
106
124
|
paramPanelWindow.end_time.set_time(end_coding)
|
|
107
125
|
|
|
126
|
+
# check observation time interval
|
|
127
|
+
if start_interval is None or start_interval.is_nan() or end_interval is None or end_interval.is_nan():
|
|
128
|
+
paramPanelWindow.rb_obs_interval.setEnabled(False)
|
|
129
|
+
|
|
108
130
|
if selected_observations:
|
|
109
131
|
observedSubjects = project_functions.extract_observed_subjects(self.pj, selected_observations)
|
|
110
132
|
else:
|
|
@@ -162,7 +184,6 @@ def choose_obs_subj_behav_category(
|
|
|
162
184
|
categories = ["###no category###"]
|
|
163
185
|
|
|
164
186
|
for category in categories:
|
|
165
|
-
|
|
166
187
|
if category != "###no category###":
|
|
167
188
|
if category == "":
|
|
168
189
|
paramPanelWindow.item = QListWidgetItem("No category")
|
|
@@ -180,17 +201,14 @@ def choose_obs_subj_behav_category(
|
|
|
180
201
|
paramPanelWindow.lwBehaviors.addItem(paramPanelWindow.item)
|
|
181
202
|
|
|
182
203
|
for behavior in [self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] for x in util.sorted_keys(self.pj[cfg.ETHOGRAM])]:
|
|
183
|
-
|
|
184
204
|
if (categories == ["###no category###"]) or (
|
|
185
205
|
behavior
|
|
186
206
|
in [
|
|
187
207
|
self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE]
|
|
188
208
|
for x in self.pj[cfg.ETHOGRAM]
|
|
189
|
-
if cfg.BEHAVIOR_CATEGORY in self.pj[cfg.ETHOGRAM][x]
|
|
190
|
-
and self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CATEGORY] == category
|
|
209
|
+
if cfg.BEHAVIOR_CATEGORY in self.pj[cfg.ETHOGRAM][x] and self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CATEGORY] == category
|
|
191
210
|
]
|
|
192
211
|
):
|
|
193
|
-
|
|
194
212
|
paramPanelWindow.item = QListWidgetItem(behavior)
|
|
195
213
|
if behavior in observedBehaviors:
|
|
196
214
|
paramPanelWindow.item.setCheckState(Qt.Checked)
|
|
@@ -219,30 +237,39 @@ def choose_obs_subj_behav_category(
|
|
|
219
237
|
logging.debug(f"selected subjects: {selectedSubjects}")
|
|
220
238
|
logging.debug(f"selected behaviors: {selectedBehaviors}")
|
|
221
239
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
startTime
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
240
|
+
if paramPanelWindow.rb_user_defined.isChecked():
|
|
241
|
+
startTime = paramPanelWindow.start_time.get_time()
|
|
242
|
+
endTime = paramPanelWindow.end_time.get_time()
|
|
243
|
+
|
|
244
|
+
if startTime > endTime:
|
|
245
|
+
QMessageBox.warning(
|
|
246
|
+
None,
|
|
247
|
+
cfg.programName,
|
|
248
|
+
"The start time is after the end time",
|
|
249
|
+
QMessageBox.Ok | QMessageBox.Default,
|
|
250
|
+
QMessageBox.NoButton,
|
|
251
|
+
)
|
|
252
|
+
return {cfg.SELECTED_SUBJECTS: [], cfg.SELECTED_BEHAVIORS: []}
|
|
253
|
+
|
|
254
|
+
elif paramPanelWindow.rb_obs_interval.isChecked() and not ((start_interval is None) or start_interval.is_nan()):
|
|
255
|
+
startTime = paramPanelWindow.start_time.get_time()
|
|
256
|
+
endTime = paramPanelWindow.end_time.get_time()
|
|
257
|
+
|
|
258
|
+
else:
|
|
259
|
+
startTime = None
|
|
260
|
+
endTime = None
|
|
261
|
+
|
|
262
|
+
# if startTime is None:
|
|
263
|
+
# startTime = dec("NaN")
|
|
264
|
+
# if endTime is None:
|
|
265
|
+
# endTime = dec("NaN")
|
|
241
266
|
|
|
242
267
|
if paramPanelWindow.rb_media_duration.isChecked():
|
|
243
268
|
time_param = cfg.TIME_FULL_OBS
|
|
244
269
|
if paramPanelWindow.rb_observed_events.isChecked():
|
|
245
270
|
time_param = cfg.TIME_EVENTS
|
|
271
|
+
if paramPanelWindow.rb_obs_interval.isChecked():
|
|
272
|
+
time_param = cfg.TIME_OBS_INTERVAL
|
|
246
273
|
if paramPanelWindow.rb_user_defined.isChecked():
|
|
247
274
|
time_param = cfg.TIME_ARBITRARY_INTERVAL
|
|
248
275
|
|
|
@@ -251,6 +278,7 @@ def choose_obs_subj_behav_category(
|
|
|
251
278
|
cfg.SELECTED_BEHAVIORS: selectedBehaviors,
|
|
252
279
|
cfg.INCLUDE_MODIFIERS: paramPanelWindow.cbIncludeModifiers.isChecked(),
|
|
253
280
|
cfg.EXCLUDE_BEHAVIORS: paramPanelWindow.cbExcludeBehaviors.isChecked(),
|
|
281
|
+
cfg.EXCLUDE_NON_CODED_MODIFIERS: paramPanelWindow.cb_exclude_non_coded_modifiers.isChecked(),
|
|
254
282
|
"time": time_param,
|
|
255
283
|
cfg.START_TIME: startTime,
|
|
256
284
|
cfg.END_TIME: endTime,
|
boris/state_events.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
|
|
@@ -22,9 +22,10 @@ Module containing functions for state events
|
|
|
22
22
|
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
+
import time
|
|
25
26
|
from decimal import Decimal as dec
|
|
26
27
|
|
|
27
|
-
from
|
|
28
|
+
from PySide6.QtWidgets import QMessageBox, QAbstractItemView
|
|
28
29
|
|
|
29
30
|
from . import config as cfg
|
|
30
31
|
from . import dialog, project_functions, select_observations
|
|
@@ -81,17 +82,16 @@ def check_state_events(self, mode: str = "all") -> None:
|
|
|
81
82
|
results.exec_()
|
|
82
83
|
|
|
83
84
|
|
|
84
|
-
def fix_unpaired_events(self):
|
|
85
|
+
def fix_unpaired_events(self, silent_mode: bool = False):
|
|
85
86
|
"""
|
|
86
87
|
fix unpaired state events
|
|
87
88
|
"""
|
|
88
89
|
|
|
89
90
|
if self.observationId:
|
|
90
|
-
|
|
91
91
|
r, msg = project_functions.check_state_events_obs(
|
|
92
92
|
self.observationId, self.pj[cfg.ETHOGRAM], self.pj[cfg.OBSERVATIONS][self.observationId]
|
|
93
93
|
)
|
|
94
|
-
if "not PAIRED" not in msg:
|
|
94
|
+
if not silent_mode and "not PAIRED" not in msg:
|
|
95
95
|
QMessageBox.information(
|
|
96
96
|
None,
|
|
97
97
|
cfg.programName,
|
|
@@ -101,33 +101,53 @@ def fix_unpaired_events(self):
|
|
|
101
101
|
)
|
|
102
102
|
return
|
|
103
103
|
|
|
104
|
-
w = dialog.Ask_time(
|
|
104
|
+
w = dialog.Ask_time(0)
|
|
105
105
|
w.setWindowTitle("Fix UNPAIRED state events")
|
|
106
|
-
w.label.setText("Fix UNPAIRED events at time")
|
|
106
|
+
w.label.setText("Fix UNPAIRED events at time:")
|
|
107
107
|
|
|
108
|
-
if w.exec_():
|
|
109
|
-
|
|
108
|
+
if not w.exec_():
|
|
109
|
+
return
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
fix_at_time = w.time_widget.get_time()
|
|
112
|
+
if fix_at_time.is_nan():
|
|
113
|
+
QMessageBox.warning(
|
|
114
|
+
self,
|
|
115
|
+
cfg.programName,
|
|
116
|
+
("Select a time format"),
|
|
115
117
|
)
|
|
118
|
+
return
|
|
116
119
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
0
|
|
129
|
-
|
|
130
|
-
|
|
120
|
+
events_to_add = project_functions.fix_unpaired_state_events(
|
|
121
|
+
self.pj[cfg.ETHOGRAM],
|
|
122
|
+
self.pj[cfg.OBSERVATIONS][self.observationId],
|
|
123
|
+
fix_at_time - dec("0.001"),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
if events_to_add:
|
|
127
|
+
# determine the new frame index
|
|
128
|
+
if (self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.MEDIA) and self.playerType == cfg.MEDIA:
|
|
129
|
+
mem_time = self.getLaps()
|
|
130
|
+
for event in events_to_add:
|
|
131
|
+
if not self.seek_mediaplayer(event[0]):
|
|
132
|
+
time.sleep(0.1)
|
|
133
|
+
frame_idx = self.get_frame_index()
|
|
134
|
+
event[cfg.PJ_OBS_FIELDS[cfg.MEDIA][cfg.FRAME_INDEX]] = frame_idx
|
|
135
|
+
self.seek_mediaplayer(mem_time)
|
|
136
|
+
|
|
137
|
+
self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS].extend(events_to_add)
|
|
138
|
+
self.project_changed()
|
|
139
|
+
self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS].sort(key=lambda x: x[:3])
|
|
140
|
+
self.load_tw_events(self.observationId)
|
|
141
|
+
|
|
142
|
+
index = self.tv_events.model().index(
|
|
143
|
+
[
|
|
144
|
+
event_idx
|
|
145
|
+
for event_idx, event in enumerate(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS])
|
|
146
|
+
if event[cfg.PJ_OBS_FIELDS[self.playerType][cfg.TIME]] == fix_at_time
|
|
147
|
+
][0],
|
|
148
|
+
0,
|
|
149
|
+
)
|
|
150
|
+
self.tv_events.scrollTo(index, QAbstractItemView.EnsureVisible)
|
|
131
151
|
|
|
132
152
|
# selected observations
|
|
133
153
|
else:
|
|
@@ -138,11 +158,11 @@ def fix_unpaired_events(self):
|
|
|
138
158
|
# check if state events are paired
|
|
139
159
|
out: str = ""
|
|
140
160
|
for obs_id in selected_observations:
|
|
141
|
-
r, msg = project_functions.check_state_events_obs(
|
|
142
|
-
obs_id, self.pj[cfg.ETHOGRAM], self.pj[cfg.OBSERVATIONS][obs_id]
|
|
143
|
-
)
|
|
161
|
+
r, msg = project_functions.check_state_events_obs(obs_id, self.pj[cfg.ETHOGRAM], self.pj[cfg.OBSERVATIONS][obs_id])
|
|
144
162
|
if "NOT PAIRED" in msg.upper():
|
|
163
|
+
# determine max time of events
|
|
145
164
|
fix_at_time = max(x[0] for x in self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS])
|
|
165
|
+
# list of events to add to fix unpaired events
|
|
146
166
|
events_to_add = project_functions.fix_unpaired_state_events(
|
|
147
167
|
self.pj[cfg.ETHOGRAM], self.pj[cfg.OBSERVATIONS][obs_id], fix_at_time
|
|
148
168
|
)
|
|
@@ -151,9 +171,7 @@ def fix_unpaired_events(self):
|
|
|
151
171
|
self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS].extend(events_to_add)
|
|
152
172
|
|
|
153
173
|
# check if modified obs if fixed
|
|
154
|
-
r, msg = project_functions.check_state_events_obs(
|
|
155
|
-
obs_id, self.pj[cfg.ETHOGRAM], self.pj[cfg.OBSERVATIONS][obs_id]
|
|
156
|
-
)
|
|
174
|
+
r, msg = project_functions.check_state_events_obs(obs_id, self.pj[cfg.ETHOGRAM], self.pj[cfg.OBSERVATIONS][obs_id])
|
|
157
175
|
if "NOT PAIRED" in msg.upper():
|
|
158
176
|
out += f"The observation <b>{obs_id}</b> can not be automatically fixed.<br><br>"
|
|
159
177
|
self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS] = events_backup
|
boris/subjects_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,18 +19,17 @@ Copyright 2012-2023 Olivier Friard
|
|
|
19
19
|
MA 02110-1301, USA.
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
from
|
|
23
|
-
from
|
|
22
|
+
from PySide6.QtCore import Signal, QRect, QEvent, Qt
|
|
23
|
+
from PySide6.QtWidgets import QGridLayout, QPushButton, QHBoxLayout, QWidget
|
|
24
24
|
|
|
25
25
|
from . import config as cfg
|
|
26
26
|
from . import utilities as util
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class SubjectsPad(QWidget):
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
close_signal = pyqtSignal(QRect)
|
|
30
|
+
click_signal = Signal(str)
|
|
31
|
+
sendEventSignal = Signal(QEvent)
|
|
32
|
+
close_signal = Signal(QRect)
|
|
34
33
|
|
|
35
34
|
def __init__(self, pj, filtered_subjects, parent=None):
|
|
36
35
|
super(SubjectsPad, self).__init__(parent)
|
|
@@ -62,7 +61,6 @@ class SubjectsPad(QWidget):
|
|
|
62
61
|
c += 1
|
|
63
62
|
|
|
64
63
|
def addWidget(self, subject, i, j):
|
|
65
|
-
|
|
66
64
|
self.grid.addWidget(Button(), i, j)
|
|
67
65
|
index = self.grid.count() - 1
|
|
68
66
|
widget = self.grid.itemAt(index).widget()
|
|
@@ -79,7 +77,7 @@ class SubjectsPad(QWidget):
|
|
|
79
77
|
widget.pushButton.clicked.connect(lambda: self.click(subject))
|
|
80
78
|
|
|
81
79
|
def click(self, subject):
|
|
82
|
-
self.
|
|
80
|
+
self.click_signal.emit(subject)
|
|
83
81
|
|
|
84
82
|
def eventFilter(self, receiver, event):
|
|
85
83
|
"""
|
boris/synthetic_time_budget.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
|
|
|
@@ -23,8 +23,8 @@ This file is part of BORIS.
|
|
|
23
23
|
import logging
|
|
24
24
|
import pathlib as pl
|
|
25
25
|
|
|
26
|
-
from
|
|
27
|
-
from
|
|
26
|
+
from PySide6.QtWidgets import QFileDialog, QMessageBox
|
|
27
|
+
from PySide6.QtGui import QFont, QTextOption, QTextCursor
|
|
28
28
|
|
|
29
29
|
from . import config as cfg
|
|
30
30
|
from . import (
|
|
@@ -60,21 +60,26 @@ def synthetic_time_budget(self) -> None:
|
|
|
60
60
|
if not_ok or not selected_observations:
|
|
61
61
|
return
|
|
62
62
|
|
|
63
|
-
max_media_duration_all_obs, _ = observation_operations.media_duration(
|
|
64
|
-
self.pj[cfg.OBSERVATIONS], selected_observations
|
|
65
|
-
)
|
|
63
|
+
max_media_duration_all_obs, _ = observation_operations.media_duration(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
66
64
|
|
|
67
65
|
start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
68
66
|
|
|
67
|
+
start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
68
|
+
|
|
69
69
|
synth_tb_param = select_subj_behav.choose_obs_subj_behav_category(
|
|
70
70
|
self,
|
|
71
71
|
selected_observations,
|
|
72
72
|
start_coding=start_coding,
|
|
73
73
|
end_coding=end_coding,
|
|
74
|
+
# start_interval=start_interval,
|
|
75
|
+
# end_interval=end_interval,
|
|
76
|
+
start_interval=None,
|
|
77
|
+
end_interval=None,
|
|
74
78
|
maxTime=max_media_duration_all_obs,
|
|
75
|
-
|
|
79
|
+
show_exclude_non_coded_behaviors=False,
|
|
76
80
|
by_category=False,
|
|
77
81
|
n_observations=len(selected_observations),
|
|
82
|
+
show_exclude_non_coded_modifiers=True,
|
|
78
83
|
)
|
|
79
84
|
|
|
80
85
|
if synth_tb_param == {}:
|
|
@@ -90,7 +95,7 @@ def synthetic_time_budget(self) -> None:
|
|
|
90
95
|
title="Select behaviors to exclude from the total time",
|
|
91
96
|
text="The duration of the selected behaviors will be subtracted from the total time",
|
|
92
97
|
table="",
|
|
93
|
-
behavior_type=
|
|
98
|
+
behavior_type=cfg.STATE_EVENT_TYPES,
|
|
94
99
|
)
|
|
95
100
|
if cancel_pressed:
|
|
96
101
|
return
|
|
@@ -134,21 +139,22 @@ def synthetic_time_budget(self) -> None:
|
|
|
134
139
|
|
|
135
140
|
output_format = cfg.FILE_NAME_SUFFIX[filter_]
|
|
136
141
|
|
|
137
|
-
if
|
|
138
|
-
|
|
142
|
+
if pl.Path(file_name).suffix != "." + output_format:
|
|
143
|
+
if filter_ != cfg.TEXT_FILE:
|
|
144
|
+
file_name = str(pl.Path(file_name)) + "." + output_format
|
|
145
|
+
else:
|
|
146
|
+
file_name = str(pl.Path(file_name))
|
|
139
147
|
if pl.Path(file_name).is_file():
|
|
140
148
|
if (
|
|
141
|
-
dialog.MessageDialog(
|
|
142
|
-
cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE]
|
|
143
|
-
)
|
|
149
|
+
dialog.MessageDialog(cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE])
|
|
144
150
|
== cfg.CANCEL
|
|
145
151
|
):
|
|
146
152
|
return
|
|
147
153
|
|
|
148
154
|
with open(file_name, "wb") as f:
|
|
149
|
-
if filter_ in
|
|
155
|
+
if filter_ in (cfg.TSV, cfg.CSV, cfg.HTML, cfg.TEXT_FILE):
|
|
150
156
|
f.write(str.encode(data_report.export(output_format)))
|
|
151
|
-
if
|
|
157
|
+
if filter_ in (cfg.ODS, cfg.XLSX, cfg.XLS):
|
|
152
158
|
f.write(data_report.export(output_format))
|
|
153
159
|
|
|
154
160
|
|
|
@@ -179,6 +185,8 @@ def synthetic_binned_time_budget(self) -> None:
|
|
|
179
185
|
|
|
180
186
|
start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
181
187
|
|
|
188
|
+
start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
189
|
+
|
|
182
190
|
# exit with message if events do not have timestamp
|
|
183
191
|
if start_coding.is_nan():
|
|
184
192
|
QMessageBox.critical(
|
|
@@ -195,11 +203,16 @@ def synthetic_binned_time_budget(self) -> None:
|
|
|
195
203
|
selected_observations,
|
|
196
204
|
start_coding=start_coding,
|
|
197
205
|
end_coding=end_coding,
|
|
206
|
+
# start_interval=start_interval,
|
|
207
|
+
# end_interval=end_interval,
|
|
208
|
+
start_interval=None,
|
|
209
|
+
end_interval=None,
|
|
198
210
|
maxTime=max_media_duration_all_obs,
|
|
199
|
-
|
|
211
|
+
show_exclude_non_coded_behaviors=False,
|
|
200
212
|
by_category=False,
|
|
201
213
|
n_observations=len(selected_observations),
|
|
202
214
|
show_time_bin_size=True,
|
|
215
|
+
show_exclude_non_coded_modifiers=True,
|
|
203
216
|
)
|
|
204
217
|
|
|
205
218
|
if synth_tb_param == {}:
|
|
@@ -212,9 +225,9 @@ def synthetic_binned_time_budget(self) -> None:
|
|
|
212
225
|
# ask for excluding behaviors durations from total time
|
|
213
226
|
cancel_pressed, synth_tb_param[cfg.EXCLUDED_BEHAVIORS] = self.filter_behaviors(
|
|
214
227
|
title="Select behaviors to exclude",
|
|
215
|
-
text=("The duration of the selected behaviors will
|
|
228
|
+
text=("The duration of the selected behaviors will be subtracted from the total time"),
|
|
216
229
|
table="",
|
|
217
|
-
behavior_type=
|
|
230
|
+
behavior_type=cfg.STATE_EVENT_TYPES,
|
|
218
231
|
)
|
|
219
232
|
if cancel_pressed:
|
|
220
233
|
return
|
|
@@ -251,24 +264,27 @@ def synthetic_binned_time_budget(self) -> None:
|
|
|
251
264
|
]
|
|
252
265
|
|
|
253
266
|
file_name, filter_ = QFileDialog().getSaveFileName(
|
|
254
|
-
self, "Synthetic time budget with time bin", "", ";;".join(file_formats)
|
|
267
|
+
self, "Save the Synthetic time budget with time bin", "", ";;".join(file_formats)
|
|
255
268
|
)
|
|
256
269
|
if not file_name:
|
|
257
270
|
return
|
|
258
271
|
|
|
259
|
-
|
|
260
|
-
|
|
272
|
+
output_format = cfg.FILE_NAME_SUFFIX[filter_]
|
|
273
|
+
|
|
274
|
+
if pl.Path(file_name).suffix != "." + output_format:
|
|
275
|
+
if filter_ != cfg.TEXT_FILE:
|
|
276
|
+
file_name = str(pl.Path(file_name)) + "." + output_format
|
|
277
|
+
else:
|
|
278
|
+
file_name = str(pl.Path(file_name))
|
|
261
279
|
if pl.Path(file_name).is_file():
|
|
262
280
|
if (
|
|
263
|
-
dialog.MessageDialog(
|
|
264
|
-
cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE]
|
|
265
|
-
)
|
|
281
|
+
dialog.MessageDialog(cfg.programName, f"The file {file_name} already exists.", (cfg.CANCEL, cfg.OVERWRITE))
|
|
266
282
|
== cfg.CANCEL
|
|
267
283
|
):
|
|
268
284
|
return
|
|
269
285
|
|
|
270
286
|
with open(file_name, "wb") as f:
|
|
271
287
|
if filter_ in (cfg.TSV, cfg.CSV, cfg.HTML, cfg.TEXT_FILE):
|
|
272
|
-
f.write(str.encode(data_report.export(
|
|
288
|
+
f.write(str.encode(data_report.export(output_format)))
|
|
273
289
|
if filter_ in (cfg.ODS, cfg.XLSX, cfg.XLS):
|
|
274
|
-
f.write(data_report.export(
|
|
290
|
+
f.write(data_report.export(output_format))
|