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.

Files changed (109) hide show
  1. boris/__init__.py +26 -0
  2. boris/__main__.py +25 -0
  3. boris/about.py +143 -0
  4. boris/add_modifier.py +635 -0
  5. boris/add_modifier_ui.py +303 -0
  6. boris/advanced_event_filtering.py +455 -0
  7. boris/analysis_plugins/__init__.py +0 -0
  8. boris/analysis_plugins/_latency.py +59 -0
  9. boris/analysis_plugins/irr_cohen_kappa.py +109 -0
  10. boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
  11. boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
  12. boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
  13. boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
  14. boris/analysis_plugins/number_of_occurences.py +22 -0
  15. boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
  16. boris/analysis_plugins/time_budget.py +61 -0
  17. boris/behav_coding_map_creator.py +1110 -0
  18. boris/behavior_binary_table.py +305 -0
  19. boris/behaviors_coding_map.py +239 -0
  20. boris/boris_cli.py +340 -0
  21. boris/cmd_arguments.py +49 -0
  22. boris/coding_pad.py +280 -0
  23. boris/config.py +785 -0
  24. boris/config_file.py +356 -0
  25. boris/connections.py +409 -0
  26. boris/converters.py +333 -0
  27. boris/converters_ui.py +225 -0
  28. boris/cooccurence.py +250 -0
  29. boris/core.py +5901 -0
  30. boris/core_qrc.py +15958 -0
  31. boris/core_ui.py +1107 -0
  32. boris/db_functions.py +324 -0
  33. boris/dev.py +134 -0
  34. boris/dialog.py +1108 -0
  35. boris/duration_widget.py +238 -0
  36. boris/edit_event.py +245 -0
  37. boris/edit_event_ui.py +233 -0
  38. boris/event_operations.py +1040 -0
  39. boris/events_cursor.py +61 -0
  40. boris/events_snapshots.py +596 -0
  41. boris/exclusion_matrix.py +141 -0
  42. boris/export_events.py +1006 -0
  43. boris/export_observation.py +1203 -0
  44. boris/external_processes.py +332 -0
  45. boris/geometric_measurement.py +941 -0
  46. boris/gui_utilities.py +135 -0
  47. boris/image_overlay.py +72 -0
  48. boris/import_observations.py +242 -0
  49. boris/ipc_mpv.py +325 -0
  50. boris/irr.py +634 -0
  51. boris/latency.py +244 -0
  52. boris/measurement_widget.py +161 -0
  53. boris/media_file.py +115 -0
  54. boris/menu_options.py +213 -0
  55. boris/modifier_coding_map_creator.py +1013 -0
  56. boris/modifiers_coding_map.py +157 -0
  57. boris/mpv.py +2016 -0
  58. boris/mpv2.py +2193 -0
  59. boris/observation.py +1453 -0
  60. boris/observation_operations.py +2538 -0
  61. boris/observation_ui.py +679 -0
  62. boris/observations_list.py +337 -0
  63. boris/otx_parser.py +442 -0
  64. boris/param_panel.py +201 -0
  65. boris/param_panel_ui.py +305 -0
  66. boris/player_dock_widget.py +198 -0
  67. boris/plot_data_module.py +536 -0
  68. boris/plot_events.py +634 -0
  69. boris/plot_events_rt.py +237 -0
  70. boris/plot_spectrogram_rt.py +316 -0
  71. boris/plot_waveform_rt.py +230 -0
  72. boris/plugins.py +431 -0
  73. boris/portion/__init__.py +31 -0
  74. boris/portion/const.py +95 -0
  75. boris/portion/dict.py +365 -0
  76. boris/portion/func.py +52 -0
  77. boris/portion/interval.py +581 -0
  78. boris/portion/io.py +181 -0
  79. boris/preferences.py +510 -0
  80. boris/preferences_ui.py +770 -0
  81. boris/project.py +2007 -0
  82. boris/project_functions.py +2041 -0
  83. boris/project_import_export.py +1096 -0
  84. boris/project_ui.py +794 -0
  85. boris/qrc_boris.py +10389 -0
  86. boris/qrc_boris5.py +2579 -0
  87. boris/select_modifiers.py +312 -0
  88. boris/select_observations.py +210 -0
  89. boris/select_subj_behav.py +286 -0
  90. boris/state_events.py +197 -0
  91. boris/subjects_pad.py +106 -0
  92. boris/synthetic_time_budget.py +290 -0
  93. boris/time_budget_functions.py +1136 -0
  94. boris/time_budget_widget.py +1039 -0
  95. boris/transitions.py +365 -0
  96. boris/utilities.py +1810 -0
  97. boris/version.py +24 -0
  98. boris/video_equalizer.py +159 -0
  99. boris/video_equalizer_ui.py +248 -0
  100. boris/video_operations.py +310 -0
  101. boris/view_df.py +104 -0
  102. boris/view_df_ui.py +75 -0
  103. boris/write_event.py +538 -0
  104. boris_behav_obs-9.7.7.dist-info/METADATA +139 -0
  105. boris_behav_obs-9.7.7.dist-info/RECORD +109 -0
  106. boris_behav_obs-9.7.7.dist-info/WHEEL +5 -0
  107. boris_behav_obs-9.7.7.dist-info/entry_points.txt +2 -0
  108. boris_behav_obs-9.7.7.dist-info/licenses/LICENSE.TXT +674 -0
  109. 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)}")