boris-behav-obs 8.12__py3-none-any.whl → 9.7.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of boris-behav-obs might be problematic. Click here for more details.
- boris/__init__.py +1 -1
- boris/__main__.py +1 -1
- boris/about.py +28 -39
- boris/add_modifier.py +122 -109
- boris/add_modifier_ui.py +239 -135
- boris/advanced_event_filtering.py +81 -45
- boris/analysis_plugins/__init__.py +0 -0
- boris/analysis_plugins/_latency.py +59 -0
- boris/analysis_plugins/irr_cohen_kappa.py +109 -0
- boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
- boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
- boris/analysis_plugins/number_of_occurences.py +22 -0
- boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
- boris/analysis_plugins/time_budget.py +61 -0
- boris/behav_coding_map_creator.py +228 -229
- boris/behavior_binary_table.py +33 -50
- boris/behaviors_coding_map.py +17 -18
- boris/boris_cli.py +6 -25
- boris/cmd_arguments.py +12 -1
- boris/coding_pad.py +42 -49
- boris/config.py +141 -65
- boris/config_file.py +58 -67
- boris/connections.py +107 -61
- boris/converters.py +13 -37
- boris/converters_ui.py +187 -110
- boris/cooccurence.py +250 -0
- boris/core.py +2373 -1786
- boris/core_qrc.py +15895 -10743
- boris/core_ui.py +943 -798
- boris/db_functions.py +17 -42
- boris/dev.py +109 -8
- boris/dialog.py +482 -236
- boris/duration_widget.py +9 -14
- boris/edit_event.py +61 -31
- boris/edit_event_ui.py +208 -97
- boris/event_operations.py +408 -293
- boris/events_cursor.py +25 -17
- boris/events_snapshots.py +36 -82
- boris/exclusion_matrix.py +4 -9
- boris/export_events.py +184 -223
- boris/export_observation.py +74 -100
- boris/external_processes.py +123 -98
- boris/geometric_measurement.py +644 -290
- boris/gui_utilities.py +91 -14
- boris/image_overlay.py +4 -4
- boris/import_observations.py +190 -98
- boris/ipc_mpv.py +325 -0
- boris/irr.py +20 -57
- boris/latency.py +31 -24
- boris/measurement_widget.py +14 -18
- boris/media_file.py +17 -19
- boris/menu_options.py +17 -6
- boris/modifier_coding_map_creator.py +1013 -0
- boris/modifiers_coding_map.py +7 -9
- boris/mpv.py +1 -0
- boris/mpv2.py +732 -705
- boris/observation.py +533 -221
- boris/observation_operations.py +1025 -390
- boris/observation_ui.py +572 -362
- boris/observations_list.py +71 -53
- boris/otx_parser.py +74 -68
- boris/param_panel.py +31 -16
- boris/param_panel_ui.py +254 -138
- boris/player_dock_widget.py +90 -60
- boris/plot_data_module.py +25 -33
- boris/plot_events.py +127 -90
- boris/plot_events_rt.py +17 -31
- boris/plot_spectrogram_rt.py +95 -30
- boris/plot_waveform_rt.py +32 -21
- boris/plugins.py +431 -0
- boris/portion/__init__.py +18 -8
- boris/portion/const.py +35 -18
- boris/portion/dict.py +5 -5
- boris/portion/func.py +2 -2
- boris/portion/interval.py +21 -41
- boris/portion/io.py +41 -32
- boris/preferences.py +306 -83
- boris/preferences_ui.py +684 -227
- boris/project.py +448 -293
- boris/project_functions.py +671 -238
- boris/project_import_export.py +213 -222
- boris/project_ui.py +674 -438
- boris/qrc_boris.py +6 -3
- boris/qrc_boris5.py +6 -3
- boris/select_modifiers.py +74 -48
- boris/select_observations.py +20 -198
- boris/select_subj_behav.py +67 -39
- boris/state_events.py +52 -35
- boris/subjects_pad.py +6 -9
- boris/synthetic_time_budget.py +45 -28
- boris/time_budget_functions.py +171 -171
- boris/time_budget_widget.py +84 -114
- boris/transitions.py +41 -47
- boris/utilities.py +627 -236
- boris/version.py +3 -3
- boris/video_equalizer.py +16 -14
- boris/video_equalizer_ui.py +199 -130
- boris/video_operations.py +95 -29
- boris/view_df.py +104 -0
- boris/view_df_ui.py +75 -0
- boris/write_event.py +538 -0
- boris_behav_obs-9.7.6.dist-info/METADATA +139 -0
- boris_behav_obs-9.7.6.dist-info/RECORD +109 -0
- {boris_behav_obs-8.12.dist-info → boris_behav_obs-9.7.6.dist-info}/WHEEL +1 -1
- boris_behav_obs-9.7.6.dist-info/entry_points.txt +2 -0
- boris/README.TXT +0 -22
- boris/add_modifier.ui +0 -323
- boris/converters.ui +0 -289
- boris/core.qrc +0 -36
- boris/core.ui +0 -1556
- boris/edit_event.ui +0 -233
- boris/icons/logo_eye.ico +0 -0
- boris/map_creator.py +0 -850
- boris/observation.ui +0 -814
- boris/param_panel.ui +0 -379
- boris/preferences.ui +0 -537
- boris/project.ui +0 -1069
- boris/project_server.py +0 -236
- boris/vlc.py +0 -10343
- boris/vlc_local.py +0 -90
- boris_behav_obs-8.12.dist-info/LICENSE.TXT +0 -674
- boris_behav_obs-8.12.dist-info/METADATA +0 -128
- boris_behav_obs-8.12.dist-info/RECORD +0 -108
- boris_behav_obs-8.12.dist-info/entry_points.txt +0 -3
- {boris → boris_behav_obs-9.7.6.dist-info/licenses}/LICENSE.TXT +0 -0
- {boris_behav_obs-8.12.dist-info → boris_behav_obs-9.7.6.dist-info}/top_level.txt +0 -0
boris/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
|
|
@@ -17,15 +17,15 @@ Copyright 2012-2023 Olivier Friard
|
|
|
17
17
|
along with this program; if not, write to the Free Software
|
|
18
18
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
19
19
|
MA 02110-1301, USA.
|
|
20
|
-
|
|
21
|
-
"""
|
|
20
|
+
|
|
22
21
|
Module containing functions for state events
|
|
23
22
|
|
|
24
23
|
"""
|
|
25
24
|
|
|
25
|
+
import time
|
|
26
26
|
from decimal import Decimal as dec
|
|
27
27
|
|
|
28
|
-
from
|
|
28
|
+
from PySide6.QtWidgets import QMessageBox, QAbstractItemView
|
|
29
29
|
|
|
30
30
|
from . import config as cfg
|
|
31
31
|
from . import dialog, project_functions, select_observations
|
|
@@ -82,17 +82,16 @@ def check_state_events(self, mode: str = "all") -> None:
|
|
|
82
82
|
results.exec_()
|
|
83
83
|
|
|
84
84
|
|
|
85
|
-
def fix_unpaired_events(self):
|
|
85
|
+
def fix_unpaired_events(self, silent_mode: bool = False):
|
|
86
86
|
"""
|
|
87
87
|
fix unpaired state events
|
|
88
88
|
"""
|
|
89
89
|
|
|
90
90
|
if self.observationId:
|
|
91
|
-
|
|
92
91
|
r, msg = project_functions.check_state_events_obs(
|
|
93
92
|
self.observationId, self.pj[cfg.ETHOGRAM], self.pj[cfg.OBSERVATIONS][self.observationId]
|
|
94
93
|
)
|
|
95
|
-
if "not PAIRED" not in msg:
|
|
94
|
+
if not silent_mode and "not PAIRED" not in msg:
|
|
96
95
|
QMessageBox.information(
|
|
97
96
|
None,
|
|
98
97
|
cfg.programName,
|
|
@@ -102,33 +101,53 @@ def fix_unpaired_events(self):
|
|
|
102
101
|
)
|
|
103
102
|
return
|
|
104
103
|
|
|
105
|
-
w = dialog.Ask_time(
|
|
104
|
+
w = dialog.Ask_time(0)
|
|
106
105
|
w.setWindowTitle("Fix UNPAIRED state events")
|
|
107
|
-
w.label.setText("Fix UNPAIRED events at time")
|
|
106
|
+
w.label.setText("Fix UNPAIRED events at time:")
|
|
108
107
|
|
|
109
|
-
if w.exec_():
|
|
110
|
-
|
|
108
|
+
if not w.exec_():
|
|
109
|
+
return
|
|
111
110
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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"),
|
|
116
117
|
)
|
|
118
|
+
return
|
|
117
119
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
0
|
|
130
|
-
|
|
131
|
-
|
|
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)
|
|
132
151
|
|
|
133
152
|
# selected observations
|
|
134
153
|
else:
|
|
@@ -139,11 +158,11 @@ def fix_unpaired_events(self):
|
|
|
139
158
|
# check if state events are paired
|
|
140
159
|
out: str = ""
|
|
141
160
|
for obs_id in selected_observations:
|
|
142
|
-
r, msg = project_functions.check_state_events_obs(
|
|
143
|
-
obs_id, self.pj[cfg.ETHOGRAM], self.pj[cfg.OBSERVATIONS][obs_id]
|
|
144
|
-
)
|
|
161
|
+
r, msg = project_functions.check_state_events_obs(obs_id, self.pj[cfg.ETHOGRAM], self.pj[cfg.OBSERVATIONS][obs_id])
|
|
145
162
|
if "NOT PAIRED" in msg.upper():
|
|
163
|
+
# determine max time of events
|
|
146
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
|
|
147
166
|
events_to_add = project_functions.fix_unpaired_state_events(
|
|
148
167
|
self.pj[cfg.ETHOGRAM], self.pj[cfg.OBSERVATIONS][obs_id], fix_at_time
|
|
149
168
|
)
|
|
@@ -152,9 +171,7 @@ def fix_unpaired_events(self):
|
|
|
152
171
|
self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS].extend(events_to_add)
|
|
153
172
|
|
|
154
173
|
# check if modified obs if fixed
|
|
155
|
-
r, msg = project_functions.check_state_events_obs(
|
|
156
|
-
obs_id, self.pj[cfg.ETHOGRAM], self.pj[cfg.OBSERVATIONS][obs_id]
|
|
157
|
-
)
|
|
174
|
+
r, msg = project_functions.check_state_events_obs(obs_id, self.pj[cfg.ETHOGRAM], self.pj[cfg.OBSERVATIONS][obs_id])
|
|
158
175
|
if "NOT PAIRED" in msg.upper():
|
|
159
176
|
out += f"The observation <b>{obs_id}</b> can not be automatically fixed.<br><br>"
|
|
160
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,19 +19,17 @@ Copyright 2012-2023 Olivier Friard
|
|
|
19
19
|
MA 02110-1301, USA.
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
from
|
|
23
|
-
from
|
|
24
|
-
from PyQt5.QtWidgets import *
|
|
22
|
+
from PySide6.QtCore import Signal, QRect, QEvent, Qt
|
|
23
|
+
from PySide6.QtWidgets import QGridLayout, QPushButton, QHBoxLayout, QWidget
|
|
25
24
|
|
|
26
25
|
from . import config as cfg
|
|
27
26
|
from . import utilities as util
|
|
28
27
|
|
|
29
28
|
|
|
30
29
|
class SubjectsPad(QWidget):
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
close_signal = pyqtSignal(QRect)
|
|
30
|
+
clickSignal = Signal(str)
|
|
31
|
+
sendEventSignal = Signal(QEvent)
|
|
32
|
+
close_signal = Signal(QRect)
|
|
35
33
|
|
|
36
34
|
def __init__(self, pj, filtered_subjects, parent=None):
|
|
37
35
|
super(SubjectsPad, self).__init__(parent)
|
|
@@ -63,7 +61,6 @@ class SubjectsPad(QWidget):
|
|
|
63
61
|
c += 1
|
|
64
62
|
|
|
65
63
|
def addWidget(self, subject, i, j):
|
|
66
|
-
|
|
67
64
|
self.grid.addWidget(Button(), i, j)
|
|
68
65
|
index = self.grid.count() - 1
|
|
69
66
|
widget = self.grid.itemAt(index).widget()
|
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
|
|
|
@@ -22,10 +22,9 @@ This file is part of BORIS.
|
|
|
22
22
|
|
|
23
23
|
import logging
|
|
24
24
|
import pathlib as pl
|
|
25
|
-
from decimal import Decimal as dec
|
|
26
25
|
|
|
27
|
-
from
|
|
28
|
-
from
|
|
26
|
+
from PySide6.QtWidgets import QFileDialog, QMessageBox
|
|
27
|
+
from PySide6.QtGui import QFont, QTextOption, QTextCursor
|
|
29
28
|
|
|
30
29
|
from . import config as cfg
|
|
31
30
|
from . import (
|
|
@@ -43,6 +42,8 @@ def synthetic_time_budget(self) -> None:
|
|
|
43
42
|
Synthetic time budget
|
|
44
43
|
"""
|
|
45
44
|
|
|
45
|
+
logging.debug("synthetic time budget function")
|
|
46
|
+
|
|
46
47
|
_, selected_observations = select_observations.select_observations2(
|
|
47
48
|
self, mode=cfg.MULTIPLE, windows_title="Select observations for synthetic time budget"
|
|
48
49
|
)
|
|
@@ -59,21 +60,26 @@ def synthetic_time_budget(self) -> None:
|
|
|
59
60
|
if not_ok or not selected_observations:
|
|
60
61
|
return
|
|
61
62
|
|
|
62
|
-
max_media_duration_all_obs, _ = observation_operations.media_duration(
|
|
63
|
-
self.pj[cfg.OBSERVATIONS], selected_observations
|
|
64
|
-
)
|
|
63
|
+
max_media_duration_all_obs, _ = observation_operations.media_duration(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
65
64
|
|
|
66
65
|
start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
67
66
|
|
|
67
|
+
start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
68
|
+
|
|
68
69
|
synth_tb_param = select_subj_behav.choose_obs_subj_behav_category(
|
|
69
70
|
self,
|
|
70
71
|
selected_observations,
|
|
71
72
|
start_coding=start_coding,
|
|
72
73
|
end_coding=end_coding,
|
|
74
|
+
# start_interval=start_interval,
|
|
75
|
+
# end_interval=end_interval,
|
|
76
|
+
start_interval=None,
|
|
77
|
+
end_interval=None,
|
|
73
78
|
maxTime=max_media_duration_all_obs,
|
|
74
|
-
|
|
79
|
+
show_exclude_non_coded_behaviors=False,
|
|
75
80
|
by_category=False,
|
|
76
81
|
n_observations=len(selected_observations),
|
|
82
|
+
show_exclude_non_coded_modifiers=True,
|
|
77
83
|
)
|
|
78
84
|
|
|
79
85
|
if synth_tb_param == {}:
|
|
@@ -87,9 +93,9 @@ def synthetic_time_budget(self) -> None:
|
|
|
87
93
|
if not start_coding.is_nan():
|
|
88
94
|
cancel_pressed, synth_tb_param[cfg.EXCLUDED_BEHAVIORS] = self.filter_behaviors(
|
|
89
95
|
title="Select behaviors to exclude from the total time",
|
|
90
|
-
text=
|
|
96
|
+
text="The duration of the selected behaviors will be subtracted from the total time",
|
|
91
97
|
table="",
|
|
92
|
-
behavior_type=
|
|
98
|
+
behavior_type=cfg.STATE_EVENT_TYPES,
|
|
93
99
|
)
|
|
94
100
|
if cancel_pressed:
|
|
95
101
|
return
|
|
@@ -133,21 +139,22 @@ def synthetic_time_budget(self) -> None:
|
|
|
133
139
|
|
|
134
140
|
output_format = cfg.FILE_NAME_SUFFIX[filter_]
|
|
135
141
|
|
|
136
|
-
if
|
|
137
|
-
|
|
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))
|
|
138
147
|
if pl.Path(file_name).is_file():
|
|
139
148
|
if (
|
|
140
|
-
dialog.MessageDialog(
|
|
141
|
-
cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE]
|
|
142
|
-
)
|
|
149
|
+
dialog.MessageDialog(cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE])
|
|
143
150
|
== cfg.CANCEL
|
|
144
151
|
):
|
|
145
152
|
return
|
|
146
153
|
|
|
147
154
|
with open(file_name, "wb") as f:
|
|
148
|
-
if filter_ in
|
|
155
|
+
if filter_ in (cfg.TSV, cfg.CSV, cfg.HTML, cfg.TEXT_FILE):
|
|
149
156
|
f.write(str.encode(data_report.export(output_format)))
|
|
150
|
-
if
|
|
157
|
+
if filter_ in (cfg.ODS, cfg.XLSX, cfg.XLS):
|
|
151
158
|
f.write(data_report.export(output_format))
|
|
152
159
|
|
|
153
160
|
|
|
@@ -178,6 +185,8 @@ def synthetic_binned_time_budget(self) -> None:
|
|
|
178
185
|
|
|
179
186
|
start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
180
187
|
|
|
188
|
+
start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
189
|
+
|
|
181
190
|
# exit with message if events do not have timestamp
|
|
182
191
|
if start_coding.is_nan():
|
|
183
192
|
QMessageBox.critical(
|
|
@@ -194,11 +203,16 @@ def synthetic_binned_time_budget(self) -> None:
|
|
|
194
203
|
selected_observations,
|
|
195
204
|
start_coding=start_coding,
|
|
196
205
|
end_coding=end_coding,
|
|
206
|
+
# start_interval=start_interval,
|
|
207
|
+
# end_interval=end_interval,
|
|
208
|
+
start_interval=None,
|
|
209
|
+
end_interval=None,
|
|
197
210
|
maxTime=max_media_duration_all_obs,
|
|
198
|
-
|
|
211
|
+
show_exclude_non_coded_behaviors=False,
|
|
199
212
|
by_category=False,
|
|
200
213
|
n_observations=len(selected_observations),
|
|
201
214
|
show_time_bin_size=True,
|
|
215
|
+
show_exclude_non_coded_modifiers=True,
|
|
202
216
|
)
|
|
203
217
|
|
|
204
218
|
if synth_tb_param == {}:
|
|
@@ -211,9 +225,9 @@ def synthetic_binned_time_budget(self) -> None:
|
|
|
211
225
|
# ask for excluding behaviors durations from total time
|
|
212
226
|
cancel_pressed, synth_tb_param[cfg.EXCLUDED_BEHAVIORS] = self.filter_behaviors(
|
|
213
227
|
title="Select behaviors to exclude",
|
|
214
|
-
text=("The duration of the selected behaviors will
|
|
228
|
+
text=("The duration of the selected behaviors will be subtracted from the total time"),
|
|
215
229
|
table="",
|
|
216
|
-
behavior_type=
|
|
230
|
+
behavior_type=cfg.STATE_EVENT_TYPES,
|
|
217
231
|
)
|
|
218
232
|
if cancel_pressed:
|
|
219
233
|
return
|
|
@@ -250,24 +264,27 @@ def synthetic_binned_time_budget(self) -> None:
|
|
|
250
264
|
]
|
|
251
265
|
|
|
252
266
|
file_name, filter_ = QFileDialog().getSaveFileName(
|
|
253
|
-
self, "Synthetic time budget with time bin", "", ";;".join(file_formats)
|
|
267
|
+
self, "Save the Synthetic time budget with time bin", "", ";;".join(file_formats)
|
|
254
268
|
)
|
|
255
269
|
if not file_name:
|
|
256
270
|
return
|
|
257
271
|
|
|
258
|
-
|
|
259
|
-
|
|
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))
|
|
260
279
|
if pl.Path(file_name).is_file():
|
|
261
280
|
if (
|
|
262
|
-
dialog.MessageDialog(
|
|
263
|
-
cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE]
|
|
264
|
-
)
|
|
281
|
+
dialog.MessageDialog(cfg.programName, f"The file {file_name} already exists.", (cfg.CANCEL, cfg.OVERWRITE))
|
|
265
282
|
== cfg.CANCEL
|
|
266
283
|
):
|
|
267
284
|
return
|
|
268
285
|
|
|
269
286
|
with open(file_name, "wb") as f:
|
|
270
287
|
if filter_ in (cfg.TSV, cfg.CSV, cfg.HTML, cfg.TEXT_FILE):
|
|
271
|
-
f.write(str.encode(data_report.export(
|
|
288
|
+
f.write(str.encode(data_report.export(output_format)))
|
|
272
289
|
if filter_ in (cfg.ODS, cfg.XLSX, cfg.XLS):
|
|
273
|
-
f.write(data_report.export(
|
|
290
|
+
f.write(data_report.export(output_format))
|