boris-behav-obs 9.7.7__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 +26 -0
- boris/__main__.py +25 -0
- boris/about.py +143 -0
- boris/add_modifier.py +635 -0
- boris/add_modifier_ui.py +303 -0
- boris/advanced_event_filtering.py +455 -0
- 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 +1110 -0
- boris/behavior_binary_table.py +305 -0
- boris/behaviors_coding_map.py +239 -0
- boris/boris_cli.py +340 -0
- boris/cmd_arguments.py +49 -0
- boris/coding_pad.py +280 -0
- boris/config.py +785 -0
- boris/config_file.py +356 -0
- boris/connections.py +409 -0
- boris/converters.py +333 -0
- boris/converters_ui.py +225 -0
- boris/cooccurence.py +250 -0
- boris/core.py +5901 -0
- boris/core_qrc.py +15958 -0
- boris/core_ui.py +1107 -0
- boris/db_functions.py +324 -0
- boris/dev.py +134 -0
- boris/dialog.py +1108 -0
- boris/duration_widget.py +238 -0
- boris/edit_event.py +245 -0
- boris/edit_event_ui.py +233 -0
- boris/event_operations.py +1040 -0
- boris/events_cursor.py +61 -0
- boris/events_snapshots.py +596 -0
- boris/exclusion_matrix.py +141 -0
- boris/export_events.py +1006 -0
- boris/export_observation.py +1203 -0
- boris/external_processes.py +332 -0
- boris/geometric_measurement.py +941 -0
- boris/gui_utilities.py +135 -0
- boris/image_overlay.py +72 -0
- boris/import_observations.py +242 -0
- boris/ipc_mpv.py +325 -0
- boris/irr.py +634 -0
- boris/latency.py +244 -0
- boris/measurement_widget.py +161 -0
- boris/media_file.py +115 -0
- boris/menu_options.py +213 -0
- boris/modifier_coding_map_creator.py +1013 -0
- boris/modifiers_coding_map.py +157 -0
- boris/mpv.py +2016 -0
- boris/mpv2.py +2193 -0
- boris/observation.py +1453 -0
- boris/observation_operations.py +2538 -0
- boris/observation_ui.py +679 -0
- boris/observations_list.py +337 -0
- boris/otx_parser.py +442 -0
- boris/param_panel.py +201 -0
- boris/param_panel_ui.py +305 -0
- boris/player_dock_widget.py +198 -0
- boris/plot_data_module.py +536 -0
- boris/plot_events.py +634 -0
- boris/plot_events_rt.py +237 -0
- boris/plot_spectrogram_rt.py +316 -0
- boris/plot_waveform_rt.py +230 -0
- boris/plugins.py +431 -0
- boris/portion/__init__.py +31 -0
- boris/portion/const.py +95 -0
- boris/portion/dict.py +365 -0
- boris/portion/func.py +52 -0
- boris/portion/interval.py +581 -0
- boris/portion/io.py +181 -0
- boris/preferences.py +510 -0
- boris/preferences_ui.py +770 -0
- boris/project.py +2007 -0
- boris/project_functions.py +2041 -0
- boris/project_import_export.py +1096 -0
- boris/project_ui.py +794 -0
- boris/qrc_boris.py +10389 -0
- boris/qrc_boris5.py +2579 -0
- boris/select_modifiers.py +312 -0
- boris/select_observations.py +210 -0
- boris/select_subj_behav.py +286 -0
- boris/state_events.py +197 -0
- boris/subjects_pad.py +106 -0
- boris/synthetic_time_budget.py +290 -0
- boris/time_budget_functions.py +1136 -0
- boris/time_budget_widget.py +1039 -0
- boris/transitions.py +365 -0
- boris/utilities.py +1810 -0
- boris/version.py +24 -0
- boris/video_equalizer.py +159 -0
- boris/video_equalizer_ui.py +248 -0
- boris/video_operations.py +310 -0
- boris/view_df.py +104 -0
- boris/view_df_ui.py +75 -0
- boris/write_event.py +538 -0
- boris_behav_obs-9.7.7.dist-info/METADATA +139 -0
- boris_behav_obs-9.7.7.dist-info/RECORD +109 -0
- boris_behav_obs-9.7.7.dist-info/WHEEL +5 -0
- boris_behav_obs-9.7.7.dist-info/entry_points.txt +2 -0
- boris_behav_obs-9.7.7.dist-info/licenses/LICENSE.TXT +674 -0
- boris_behav_obs-9.7.7.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BORIS
|
|
3
|
+
Behavioral Observation Research Interactive Software
|
|
4
|
+
Copyright 2012-2025 Olivier Friard
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
This program is free software; you can redistribute it and/or modify
|
|
8
|
+
it under the terms of the GNU General Public License as published by
|
|
9
|
+
the Free Software Foundation; either version 2 of the License, or
|
|
10
|
+
(at your option) any later version.
|
|
11
|
+
|
|
12
|
+
This program is distributed in the hope that it will be useful,
|
|
13
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
GNU General Public License for more details.
|
|
16
|
+
|
|
17
|
+
You should have received a copy of the GNU General Public License
|
|
18
|
+
along with this program; if not, write to the Free Software
|
|
19
|
+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
20
|
+
MA 02110-1301, USA.
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from PySide6.QtGui import QColor
|
|
25
|
+
from PySide6.QtWidgets import (
|
|
26
|
+
QTableWidgetItem,
|
|
27
|
+
QLabel,
|
|
28
|
+
QLineEdit,
|
|
29
|
+
QTableWidget,
|
|
30
|
+
QAbstractItemView,
|
|
31
|
+
QComboBox,
|
|
32
|
+
QGridLayout,
|
|
33
|
+
QHBoxLayout,
|
|
34
|
+
QSpacerItem,
|
|
35
|
+
QPushButton,
|
|
36
|
+
QDialog,
|
|
37
|
+
QSizePolicy,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
from . import config as cfg
|
|
41
|
+
from . import dialog
|
|
42
|
+
|
|
43
|
+
commands_index = {"Start": 2, "Edit": 3, "View": 4}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class MyTableWidgetItem(QTableWidgetItem):
|
|
47
|
+
def __init__(self, text, sortKey):
|
|
48
|
+
QTableWidgetItem.__init__(self, text, QTableWidgetItem.UserType)
|
|
49
|
+
self.sortKey = sortKey
|
|
50
|
+
|
|
51
|
+
# Qt uses a simple < check for sorting items, override this to use the sortKey
|
|
52
|
+
def __lt__(self, other):
|
|
53
|
+
if isinstance(self.sortKey, str) and isinstance(other.sortKey, str):
|
|
54
|
+
return self.sortKey.lower() < other.sortKey.lower()
|
|
55
|
+
else:
|
|
56
|
+
return self.sortKey < other.sortKey
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class observationsList_widget(QDialog):
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
data: list,
|
|
63
|
+
header: list,
|
|
64
|
+
column_type: list,
|
|
65
|
+
not_paired: list = [],
|
|
66
|
+
parent=None,
|
|
67
|
+
):
|
|
68
|
+
super(observationsList_widget, self).__init__(parent)
|
|
69
|
+
|
|
70
|
+
self.data = data
|
|
71
|
+
self.not_paired = not_paired
|
|
72
|
+
self.column_type = column_type
|
|
73
|
+
|
|
74
|
+
self.setWindowTitle(f"Observations list - {cfg.programName}")
|
|
75
|
+
self.label = QLabel("")
|
|
76
|
+
|
|
77
|
+
self.mode = cfg.SINGLE
|
|
78
|
+
|
|
79
|
+
self.lineEdit = QLineEdit(self)
|
|
80
|
+
self.lineEdit.textChanged.connect(self.view_filter)
|
|
81
|
+
self.view = QTableWidget(self)
|
|
82
|
+
self.view.setSelectionBehavior(QAbstractItemView.SelectRows)
|
|
83
|
+
self.view.setSortingEnabled(True)
|
|
84
|
+
|
|
85
|
+
self.comboBox = QComboBox(self)
|
|
86
|
+
self.comboBox.currentIndexChanged.connect(self.view_filter)
|
|
87
|
+
|
|
88
|
+
self.cbLogic = QComboBox(self)
|
|
89
|
+
self.cbLogic.addItems(
|
|
90
|
+
[
|
|
91
|
+
"contains",
|
|
92
|
+
"does not contain",
|
|
93
|
+
"=",
|
|
94
|
+
"!=",
|
|
95
|
+
">",
|
|
96
|
+
"<",
|
|
97
|
+
">=",
|
|
98
|
+
"<=",
|
|
99
|
+
"between (use and to separate terms)",
|
|
100
|
+
]
|
|
101
|
+
)
|
|
102
|
+
self.cbLogic.currentIndexChanged.connect(self.view_filter)
|
|
103
|
+
|
|
104
|
+
self.label = QLabel(self)
|
|
105
|
+
|
|
106
|
+
self.gridLayout = QGridLayout(self)
|
|
107
|
+
self.gridLayout.addWidget(self.label, 0, 0, 1, 3)
|
|
108
|
+
self.gridLayout.addWidget(self.comboBox, 1, 0, 1, 1)
|
|
109
|
+
self.gridLayout.addWidget(self.cbLogic, 1, 1, 1, 1)
|
|
110
|
+
self.gridLayout.addWidget(self.lineEdit, 1, 2, 1, 1)
|
|
111
|
+
|
|
112
|
+
self.gridLayout.addWidget(self.view, 2, 0, 1, 3)
|
|
113
|
+
|
|
114
|
+
hbox2 = QHBoxLayout()
|
|
115
|
+
|
|
116
|
+
spacerItem = QSpacerItem(241, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
|
|
117
|
+
hbox2.addItem(spacerItem)
|
|
118
|
+
|
|
119
|
+
self.pbSelectAll = QPushButton("Select all")
|
|
120
|
+
self.pbSelectAll.clicked.connect(lambda: self.pbSelection_clicked("select"))
|
|
121
|
+
hbox2.addWidget(self.pbSelectAll)
|
|
122
|
+
|
|
123
|
+
self.pbUnSelectAll = QPushButton("Unselect all")
|
|
124
|
+
self.pbUnSelectAll.clicked.connect(lambda: self.pbSelection_clicked("unselect"))
|
|
125
|
+
hbox2.addWidget(self.pbUnSelectAll)
|
|
126
|
+
|
|
127
|
+
self.pbCancel = QPushButton(cfg.CANCEL, clicked=self.pbCancel_clicked)
|
|
128
|
+
hbox2.addWidget(self.pbCancel)
|
|
129
|
+
|
|
130
|
+
self.pbOpen = QPushButton("Start", clicked=self.pbOpen_clicked)
|
|
131
|
+
hbox2.addWidget(self.pbOpen)
|
|
132
|
+
|
|
133
|
+
self.pbView = QPushButton("View", clicked=self.pbView_clicked)
|
|
134
|
+
hbox2.addWidget(self.pbView)
|
|
135
|
+
|
|
136
|
+
self.pbEdit = QPushButton("Edit", clicked=self.pbEdit_clicked)
|
|
137
|
+
hbox2.addWidget(self.pbEdit)
|
|
138
|
+
|
|
139
|
+
self.pbOk = QPushButton(cfg.OK, clicked=self.pbOk_clicked)
|
|
140
|
+
hbox2.addWidget(self.pbOk)
|
|
141
|
+
|
|
142
|
+
self.gridLayout.addLayout(hbox2, 3, 0, 1, 3)
|
|
143
|
+
|
|
144
|
+
self.view.doubleClicked.connect(self.view_doubleClicked)
|
|
145
|
+
|
|
146
|
+
self.view.setRowCount(len(self.data))
|
|
147
|
+
if self.data:
|
|
148
|
+
self.view.setColumnCount(len(self.data[0]))
|
|
149
|
+
|
|
150
|
+
self.view.setHorizontalHeaderLabels(header)
|
|
151
|
+
|
|
152
|
+
for r in range(len(self.data)):
|
|
153
|
+
for c in range(len(self.data[0])):
|
|
154
|
+
self.view.setItem(r, c, self.set_item(r, c))
|
|
155
|
+
|
|
156
|
+
self.view.resizeColumnsToContents()
|
|
157
|
+
|
|
158
|
+
self.comboBox.addItems(header)
|
|
159
|
+
|
|
160
|
+
self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
|
161
|
+
self.label.setText(f"{self.view.rowCount()} observation{'s' * (self.view.rowCount() > 1)}")
|
|
162
|
+
|
|
163
|
+
def view_doubleClicked(self, index):
|
|
164
|
+
if self.mode == cfg.MULTIPLE:
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
if self.mode == cfg.OPEN or self.mode == cfg.EDIT:
|
|
168
|
+
self.done(2)
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
if self.mode == cfg.SELECT1:
|
|
172
|
+
self.done(2)
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
response = dialog.MessageDialog(
|
|
176
|
+
cfg.programName,
|
|
177
|
+
"What do you want to do with this observation?",
|
|
178
|
+
list(commands_index.keys()) + [cfg.CANCEL],
|
|
179
|
+
)
|
|
180
|
+
if response == cfg.CANCEL:
|
|
181
|
+
return
|
|
182
|
+
else:
|
|
183
|
+
self.done(commands_index[response])
|
|
184
|
+
|
|
185
|
+
def pbSelection_clicked(self, mode):
|
|
186
|
+
"""
|
|
187
|
+
select or unselect all filtered observations
|
|
188
|
+
"""
|
|
189
|
+
if mode == "select":
|
|
190
|
+
self.view.selectAll()
|
|
191
|
+
if mode == "unselect":
|
|
192
|
+
self.view.clearSelection()
|
|
193
|
+
|
|
194
|
+
def pbCancel_clicked(self):
|
|
195
|
+
self.close()
|
|
196
|
+
|
|
197
|
+
def pbOk_clicked(self):
|
|
198
|
+
self.done(1)
|
|
199
|
+
|
|
200
|
+
def pbOpen_clicked(self):
|
|
201
|
+
self.done(2)
|
|
202
|
+
|
|
203
|
+
def pbEdit_clicked(self):
|
|
204
|
+
self.done(3)
|
|
205
|
+
|
|
206
|
+
def pbView_clicked(self):
|
|
207
|
+
self.done(4)
|
|
208
|
+
|
|
209
|
+
def set_item(self, r, c):
|
|
210
|
+
if self.column_type[c] == cfg.NUMERIC:
|
|
211
|
+
try:
|
|
212
|
+
item = MyTableWidgetItem(self.data[r][c], float(self.data[r][c]))
|
|
213
|
+
except Exception:
|
|
214
|
+
item = MyTableWidgetItem(self.data[r][c], 0)
|
|
215
|
+
else:
|
|
216
|
+
item = MyTableWidgetItem(self.data[r][c], self.data[r][c])
|
|
217
|
+
|
|
218
|
+
# if obs_id in not_paired -> set background color to red
|
|
219
|
+
if c == 0 and self.data[r][c] in self.not_paired:
|
|
220
|
+
item.setBackground(QColor(255, 0, 0, 128))
|
|
221
|
+
# item.setForeground(QBrush(QColor(0, 255, 0)))
|
|
222
|
+
|
|
223
|
+
return item
|
|
224
|
+
|
|
225
|
+
def view_filter(self):
|
|
226
|
+
"""
|
|
227
|
+
filter
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
def str2float(s: str):
|
|
231
|
+
"""
|
|
232
|
+
convert str in float or return str
|
|
233
|
+
"""
|
|
234
|
+
try:
|
|
235
|
+
return float(s)
|
|
236
|
+
except Exception:
|
|
237
|
+
return s
|
|
238
|
+
|
|
239
|
+
def in_(s, lst):
|
|
240
|
+
return s in lst
|
|
241
|
+
|
|
242
|
+
def not_in(s, lst):
|
|
243
|
+
return s not in lst
|
|
244
|
+
|
|
245
|
+
def equal(s, x):
|
|
246
|
+
x_num, s_num = str2float(x), str2float(s)
|
|
247
|
+
if type(x_num) == type(s_num):
|
|
248
|
+
return x_num == s_num
|
|
249
|
+
else:
|
|
250
|
+
return x == s
|
|
251
|
+
|
|
252
|
+
def not_equal(s, x):
|
|
253
|
+
x_num, s_num = str2float(x), str2float(s)
|
|
254
|
+
if type(x_num) == type(s_num):
|
|
255
|
+
return x_num != s_num
|
|
256
|
+
else:
|
|
257
|
+
return x != s
|
|
258
|
+
|
|
259
|
+
def gt(s, x):
|
|
260
|
+
x_num, s_num = str2float(x), str2float(s)
|
|
261
|
+
if type(x_num) == type(s_num):
|
|
262
|
+
return x_num > s_num
|
|
263
|
+
else:
|
|
264
|
+
return x > s
|
|
265
|
+
|
|
266
|
+
def lt(s, x):
|
|
267
|
+
x_num, s_num = str2float(x), str2float(s)
|
|
268
|
+
if type(x_num) == type(s_num):
|
|
269
|
+
return x_num < s_num
|
|
270
|
+
else:
|
|
271
|
+
return x < s
|
|
272
|
+
|
|
273
|
+
def gt_or_equal(s, x):
|
|
274
|
+
x_num, s_num = str2float(x), str2float(s)
|
|
275
|
+
if type(x_num) == type(s_num):
|
|
276
|
+
return x_num >= s_num
|
|
277
|
+
else:
|
|
278
|
+
return x >= s
|
|
279
|
+
|
|
280
|
+
def lt_or_equal(s, x):
|
|
281
|
+
x_num, s_num = str2float(x), str2float(s)
|
|
282
|
+
if type(x_num) == type(s_num):
|
|
283
|
+
return x_num <= s_num
|
|
284
|
+
else:
|
|
285
|
+
return x <= s
|
|
286
|
+
|
|
287
|
+
def between(s, x):
|
|
288
|
+
if len(s.split(" AND ")) != 2:
|
|
289
|
+
return None
|
|
290
|
+
s1, s2 = s.split(" AND ")
|
|
291
|
+
s1_num, s2_num = str2float(s1), str2float(s2)
|
|
292
|
+
if type(s1_num) != type(s2_num):
|
|
293
|
+
return None
|
|
294
|
+
x_num = str2float(x)
|
|
295
|
+
if type(s1_num) == type(x_num):
|
|
296
|
+
return s1_num <= x_num <= s2_num
|
|
297
|
+
else:
|
|
298
|
+
return s1 <= x <= s2
|
|
299
|
+
|
|
300
|
+
if not self.lineEdit.text():
|
|
301
|
+
self.view.setRowCount(len(self.data))
|
|
302
|
+
|
|
303
|
+
for r in range(len(self.data)):
|
|
304
|
+
for c in range(len(self.data[0])):
|
|
305
|
+
self.view.setItem(r, c, self.set_item(r, c))
|
|
306
|
+
|
|
307
|
+
else:
|
|
308
|
+
if self.cbLogic.currentText() == "contains":
|
|
309
|
+
logic = in_
|
|
310
|
+
if self.cbLogic.currentText() == "does not contain":
|
|
311
|
+
logic = not_in
|
|
312
|
+
if self.cbLogic.currentText() == "=":
|
|
313
|
+
logic = equal
|
|
314
|
+
if self.cbLogic.currentText() == "!=":
|
|
315
|
+
logic = not_equal
|
|
316
|
+
if self.cbLogic.currentText() == ">":
|
|
317
|
+
logic = gt
|
|
318
|
+
if self.cbLogic.currentText() == "<":
|
|
319
|
+
logic = lt
|
|
320
|
+
if self.cbLogic.currentText() == ">=":
|
|
321
|
+
logic = gt_or_equal
|
|
322
|
+
if self.cbLogic.currentText() == "<=":
|
|
323
|
+
logic = lt_or_equal
|
|
324
|
+
if "between" in self.cbLogic.currentText():
|
|
325
|
+
logic = between
|
|
326
|
+
|
|
327
|
+
self.view.setRowCount(0)
|
|
328
|
+
search = self.lineEdit.text().upper()
|
|
329
|
+
try:
|
|
330
|
+
for r, row in enumerate(self.data):
|
|
331
|
+
if logic(search, row[self.comboBox.currentIndex()].upper()):
|
|
332
|
+
self.view.setRowCount(self.view.rowCount() + 1)
|
|
333
|
+
for c, _ in enumerate(row):
|
|
334
|
+
self.view.setItem(self.view.rowCount() - 1, c, self.set_item(r, c))
|
|
335
|
+
except Exception:
|
|
336
|
+
pass
|
|
337
|
+
self.label.setText(f"{self.view.rowCount()} observation{'s' * (self.view.rowCount() > 1)}")
|