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.
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
boris/transitions.py ADDED
@@ -0,0 +1,365 @@
1
+ """
2
+ BORIS
3
+ Behavioral Observation Research Interactive Software
4
+ Copyright 2012-2025 Olivier Friard
5
+
6
+ This file is part of BORIS.
7
+
8
+ BORIS is free software; you can redistribute it and/or modify
9
+ it under the terms of the GNU General Public License as published by
10
+ the Free Software Foundation; either version 3 of the License, or
11
+ any later version.
12
+
13
+ BORIS is distributed in the hope that it will be useful,
14
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ GNU General Public License for more details.
17
+
18
+ You should have received a copy of the GNU General Public License
19
+ along with this program; if not see <http://www.gnu.org/licenses/>.
20
+
21
+ """
22
+
23
+ import logging
24
+ import os
25
+ import subprocess
26
+ import tempfile
27
+ from pathlib import Path
28
+
29
+ from PySide6.QtWidgets import QFileDialog, QMessageBox
30
+
31
+ from . import config as cfg
32
+ from . import dialog, export_observation, select_subj_behav
33
+ from . import select_observations
34
+
35
+
36
+ def behavioral_strings_analysis(strings, behav_seq_separator):
37
+ """
38
+ Analyze behavioral strings
39
+ """
40
+
41
+ rows = strings[:]
42
+ sequences = []
43
+ for row in rows:
44
+ if behav_seq_separator:
45
+ r = row.strip().split(behav_seq_separator)
46
+ else:
47
+ r = list(row.strip())
48
+
49
+ sequences.append(r)
50
+
51
+ # extract unique behaviors
52
+ unique_behaviors = []
53
+ for seq in sequences:
54
+ for c in seq:
55
+ if c not in unique_behaviors:
56
+ unique_behaviors.append(c)
57
+
58
+ unique_behaviors.sort()
59
+
60
+ return sequences, unique_behaviors
61
+
62
+
63
+ def observed_transitions_matrix(sequences, behaviours, mode="frequency") -> str:
64
+ """
65
+ create the normalized matrix of observed transitions
66
+ mode:
67
+ * frequency:
68
+ * number
69
+ * frequencies_after_behaviors
70
+ """
71
+
72
+ logging.debug("function: observed_transitions_matrix")
73
+ logging.debug(f"behaviours: {behaviours}")
74
+
75
+ if "" in behaviours:
76
+ behaviours.remove("")
77
+
78
+ transitions = {}
79
+ for behaviour in behaviours:
80
+ if not behaviour:
81
+ continue
82
+ transitions[behaviour] = {}
83
+ for behaviour2 in behaviours:
84
+ transitions[behaviour][behaviour2] = 0
85
+
86
+ for seq in sequences:
87
+ for i in range(len(seq) - 1):
88
+ if not seq[i]:
89
+ continue
90
+ if seq[i] in behaviours and seq[i + 1] in behaviours:
91
+ transitions[seq[i]][seq[i + 1]] += 1
92
+
93
+ transitions_total_number = sum([sum(transitions[x].values()) for x in transitions])
94
+
95
+ if not transitions_total_number:
96
+ return False
97
+
98
+ out = "\t" + "\t".join(list(behaviours)) + "\n"
99
+ for behaviour in behaviours:
100
+ out += "{}\t".format(behaviour)
101
+ for behaviour2 in behaviours:
102
+ if mode == "frequency":
103
+ out += "{}\t".format(transitions[behaviour][behaviour2] / transitions_total_number)
104
+ elif mode == "number":
105
+ out += "{}\t".format(transitions[behaviour][behaviour2])
106
+ elif mode == "frequencies_after_behaviors":
107
+ if sum(transitions[behaviour].values()):
108
+ out += "{}\t".format(transitions[behaviour][behaviour2] / sum(transitions[behaviour].values()))
109
+ else:
110
+ out += "{}\t".format(transitions[behaviour][behaviour2])
111
+ out = out[:-1] + "\n"
112
+
113
+ return out
114
+
115
+
116
+ def create_transitions_gv_from_matrix(matrix, cutoff_all=0, cutoff_behavior=0, edge_label="percent_node") -> tuple:
117
+ """
118
+ create code for GraphViz
119
+ matrix: matrix of frequency
120
+ edge_label: (percent_node, fraction_node)
121
+ return string containing graphviz code
122
+ """
123
+
124
+ behaviours = matrix.split("\n")[0].strip().split("\t")
125
+ transitions = {}
126
+
127
+ for row in matrix.split("\n")[1:]:
128
+ if not row:
129
+ continue
130
+
131
+ transitions[row.split("\t")[0]] = {}
132
+ for idx, r in enumerate(row.split("\t")[1:]):
133
+ if "." in r:
134
+ transitions[row.split("\t")[0]][behaviours[idx]] = float(r)
135
+ else:
136
+ transitions[row.split("\t")[0]][behaviours[idx]] = int(r)
137
+
138
+ """transitions_total_number = sum([sum(transitions[x].values()) for x in transitions])"""
139
+
140
+ out = "digraph G { \n"
141
+
142
+ for behaviour1 in behaviours:
143
+ for behaviour2 in behaviours:
144
+ if behaviour1 not in transitions or behaviour2 not in transitions:
145
+ return True, "Error: the file does not seem a transition matrix"
146
+ if transitions[behaviour1][behaviour2]:
147
+ if edge_label == "percent_node":
148
+ if transitions[behaviour1][behaviour2] > cutoff_all:
149
+ out += '"{behaviour1}" -> "{behaviour2}" [label="{label:0.3f}"];\n'.format(
150
+ behaviour1=behaviour1, behaviour2=behaviour2, label=transitions[behaviour1][behaviour2]
151
+ )
152
+
153
+ if edge_label == "fraction_node":
154
+ transition_sum = sum(transitions[behaviour1].values())
155
+ if transitions[behaviour1][behaviour2] / transition_sum > cutoff_behavior:
156
+ out += """"{behaviour1}" -> "{behaviour2}" [label="{label}%"];\n""".format(
157
+ behaviour1=behaviour1,
158
+ behaviour2=behaviour2,
159
+ label=round(transitions[behaviour1][behaviour2] / transition_sum * 100, 1),
160
+ )
161
+
162
+ out += "\n}"
163
+ return False, out
164
+
165
+
166
+ def transitions_matrix(self, mode):
167
+ """
168
+ create transitions frequencies matrix with selected observations, subjects and behaviors
169
+ mode:
170
+ * frequency
171
+ * number
172
+ * frequencies_after_behaviors
173
+ """
174
+ logging.debug("flag transitions_matrix function")
175
+
176
+ # ask user observations to analyze
177
+ _, selected_observations = select_observations.select_observations2(
178
+ self, cfg.MULTIPLE, windows_title="Select observations for transitions matrix"
179
+ )
180
+
181
+ if not selected_observations:
182
+ return
183
+
184
+ parameters = select_subj_behav.choose_obs_subj_behav_category(
185
+ self,
186
+ selected_observations,
187
+ show_include_modifiers=True,
188
+ show_exclude_non_coded_behaviors=False,
189
+ n_observations=len(selected_observations),
190
+ )
191
+
192
+ if parameters == {}:
193
+ return
194
+
195
+ if not parameters[cfg.SELECTED_SUBJECTS] or not parameters[cfg.SELECTED_BEHAVIORS]:
196
+ return
197
+
198
+ flagMulti = False
199
+ if len(parameters[cfg.SELECTED_SUBJECTS]) == 1:
200
+ file_name, _ = QFileDialog().getSaveFileName(
201
+ None,
202
+ "Create matrix of transitions " + mode,
203
+ "",
204
+ "Transitions matrix files (*.txt *.tsv);;All files (*)",
205
+ )
206
+ if not file_name:
207
+ return
208
+ else:
209
+ exportDir = QFileDialog.getExistingDirectory(
210
+ self,
211
+ "Choose a directory to save the transitions matrices",
212
+ str(Path.home()),
213
+ options=QFileDialog.ShowDirsOnly,
214
+ )
215
+ if not exportDir:
216
+ return
217
+ flagMulti = True
218
+
219
+ flag_overwrite_all = False
220
+ for subject in parameters[cfg.SELECTED_SUBJECTS]:
221
+ logging.debug(f"subjects: {subject}")
222
+
223
+ strings_list = []
224
+ for obs_id in selected_observations:
225
+ strings_list.append(
226
+ export_observation.events_to_behavioral_sequences(self.pj, obs_id, subject, parameters, self.behav_seq_separator)
227
+ )
228
+
229
+ sequences, observed_behaviors = behavioral_strings_analysis(strings_list, self.behav_seq_separator)
230
+
231
+ observed_matrix = observed_transitions_matrix(
232
+ sequences, sorted(list(set(observed_behaviors + parameters[cfg.SELECTED_BEHAVIORS]))), mode=mode
233
+ )
234
+
235
+ if not observed_matrix:
236
+ QMessageBox.warning(self, cfg.programName, f"No transitions found for <b>{subject}</b>")
237
+ continue
238
+
239
+ logging.debug(f"observed_matrix {mode}:\n{observed_matrix}")
240
+
241
+ if flagMulti:
242
+ try:
243
+ nf = f"{exportDir}{os.sep}{subject}_transitions_{mode}_matrix.tsv"
244
+
245
+ if os.path.isfile(nf) and not flag_overwrite_all:
246
+ answer = dialog.MessageDialog(
247
+ cfg.programName,
248
+ f"A file with same name already exists.<br><b>{nf}</b>",
249
+ ["Overwrite", "Overwrite all", cfg.CANCEL],
250
+ )
251
+ if answer == cfg.CANCEL:
252
+ continue
253
+ if answer == "Overwrite all":
254
+ flag_overwrite_all = True
255
+
256
+ with open(nf, "w") as outfile:
257
+ outfile.write(observed_matrix)
258
+ except Exception:
259
+ QMessageBox.critical(self, cfg.programName, f"The file {nf} can not be saved")
260
+ else:
261
+ try:
262
+ with open(file_name, "w") as outfile:
263
+ outfile.write(observed_matrix)
264
+
265
+ except Exception:
266
+ QMessageBox.critical(self, cfg.programName, f"The file {file_name} can not be saved")
267
+
268
+
269
+ def transitions_dot_script():
270
+ """
271
+ create dot script (graphviz language) from transitions frequencies matrix
272
+ """
273
+
274
+ file_names, _ = QFileDialog().getOpenFileNames(
275
+ None,
276
+ "Select one or more transitions matrix files",
277
+ "",
278
+ "Transitions matrix files (*.txt *.tsv);;All files (*)",
279
+ )
280
+
281
+ out = ""
282
+
283
+ for file_name in file_names:
284
+ with open(file_name, "r") as infile:
285
+ result, gv = create_transitions_gv_from_matrix(infile.read(), cutoff_all=0, cutoff_behavior=0, edge_label="percent_node")
286
+ if result:
287
+ QMessageBox.critical(
288
+ None,
289
+ cfg.programName,
290
+ gv,
291
+ )
292
+ return
293
+
294
+ try:
295
+ with open(file_name + ".gv", "w") as file_out:
296
+ file_out.write(gv)
297
+ except Exception:
298
+ QMessageBox.critical(
299
+ None,
300
+ cfg.programName,
301
+ ("Error saving the file"),
302
+ )
303
+ return
304
+
305
+ out += f"<b>{file_name}.gv</b> created<br>"
306
+
307
+ if out:
308
+ QMessageBox.information(
309
+ None,
310
+ cfg.programName,
311
+ (f"{out}<br><br>The DOT scripts can be used with the Graphviz package or WebGraphviz to generate diagram"),
312
+ )
313
+
314
+
315
+ def transitions_flow_diagram():
316
+ """
317
+ create flow diagram with graphviz (if installed) from transitions matrix
318
+ """
319
+
320
+ # check if dot present in path
321
+ result = subprocess.getoutput("dot -V")
322
+ if "graphviz" not in result:
323
+ QMessageBox.critical(
324
+ None,
325
+ cfg.programName,
326
+ (
327
+ "The GraphViz package is not installed.<br>"
328
+ "The <b>dot</b> program was not found in the path.<br><br>"
329
+ 'Go to <a href="http://www.graphviz.org">'
330
+ "http://www.graphviz.org</a> for information"
331
+ ),
332
+ )
333
+ return
334
+
335
+ file_names, _ = QFileDialog().getOpenFileNames(
336
+ None,
337
+ "Select one or more transitions matrix files",
338
+ "",
339
+ "Transitions matrix files (*.txt *.tsv);;All files (*)",
340
+ )
341
+
342
+ out = ""
343
+ for file_name in file_names:
344
+ with open(file_name, "r") as infile:
345
+ result, gv = create_transitions_gv_from_matrix(infile.read(), cutoff_all=0, cutoff_behavior=0, edge_label="percent_node")
346
+ if result:
347
+ QMessageBox.critical(
348
+ None,
349
+ cfg.programName,
350
+ gv,
351
+ )
352
+ return
353
+
354
+ with open(tempfile.gettempdir() + os.sep + os.path.basename(file_name) + ".tmp.gv", "w") as f:
355
+ f.write(gv)
356
+ result = subprocess.getoutput(
357
+ (f'dot -Tpng -o "{file_name}.png" "{tempfile.gettempdir() + os.sep + os.path.basename(file_name)}.tmp.gv"')
358
+ )
359
+ if not result:
360
+ out += f"<b>{file_name}.png</b> created<br>"
361
+ else:
362
+ out += f"Problem with <b>{file_name}</b><br>"
363
+
364
+ if out:
365
+ QMessageBox.information(None, cfg.programName, out)