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
boris/cooccurence.py ADDED
@@ -0,0 +1,250 @@
1
+ """
2
+ BORIS
3
+ Behavioral Observation Research Interactive Software
4
+ Copyright 2012-2025 Olivier Friard
5
+
6
+ This program is free software; you can redistribute it and/or modify
7
+ it under the terms of the GNU General Public License as published by
8
+ the Free Software Foundation; either version 2 of the License, or
9
+ (at your option) any later version.
10
+
11
+ This program is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU General Public License for more details.
15
+
16
+ You should have received a copy of the GNU General Public License
17
+ along with this program; if not, write to the Free Software
18
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
+ MA 02110-1301, USA.
20
+
21
+
22
+ Module for analyzing the co-occurence of behaviors
23
+
24
+ """
25
+
26
+ from . import config as cfg
27
+ from . import select_subj_behav
28
+ from . import dialog
29
+ from . import utilities as util
30
+ from . import select_observations
31
+
32
+ from . import project_functions, observation_operations
33
+
34
+ from PySide6.QtWidgets import QMessageBox
35
+ from PySide6.QtGui import QFont, QTextOption
36
+ from . import portion as I
37
+ import itertools
38
+ import logging
39
+ from decimal import Decimal as dec
40
+
41
+
42
+ def get_cooccurence(self):
43
+ """
44
+ get co-occurence of selected behaviors
45
+ """
46
+
47
+ QMessageBox.warning(
48
+ None,
49
+ cfg.programName,
50
+ (
51
+ "This function is experimental. Please test it and report any bug and suggestions at <br>"
52
+ '<a href="https://github.com/olivierfriard/BORIS/issues">'
53
+ "https://github.com/olivierfriard/BORIS/issues</a><br>"
54
+ "Thank you for your collaboration!"
55
+ ),
56
+ QMessageBox.Ok | QMessageBox.Default,
57
+ QMessageBox.NoButton,
58
+ )
59
+
60
+ def interval_len(interval: I) -> dec:
61
+ """ "
62
+ returns duration of an interval or a set of intervals
63
+ """
64
+ if interval.empty:
65
+ return dec(0)
66
+ else:
67
+ return dec(sum([x.upper - x.lower for x in interval]))
68
+
69
+ _, selected_observations = select_observations.select_observations2(
70
+ self, cfg.MULTIPLE, windows_title="Select the observations for behaviors co-occurence analysis"
71
+ )
72
+
73
+ if not selected_observations:
74
+ return
75
+
76
+ # check if coded behaviors are defined in ethogram
77
+ if project_functions.check_coded_behaviors_in_obs_list(self.pj, selected_observations):
78
+ return
79
+
80
+ # check if state events are paired
81
+ not_ok, selected_observations = project_functions.check_state_events(self.pj, selected_observations)
82
+ if not_ok or not selected_observations:
83
+ return
84
+
85
+ max_media_duration_all_obs, _ = observation_operations.media_duration(self.pj[cfg.OBSERVATIONS], selected_observations)
86
+
87
+ start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
88
+ # exit with message if events do not have timestamp
89
+ if start_coding.is_nan():
90
+ QMessageBox.critical(
91
+ None,
92
+ cfg.programName,
93
+ ("This function is not available for observations with events that do not have timestamp"),
94
+ QMessageBox.Ok | QMessageBox.Default,
95
+ QMessageBox.NoButton,
96
+ )
97
+ return
98
+
99
+ start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
100
+
101
+ # loop on choose subjects /behaviors until parameters are OK
102
+ while True:
103
+ flag_ok: bool = True
104
+ parameters = select_subj_behav.choose_obs_subj_behav_category(
105
+ self,
106
+ selected_observations,
107
+ start_coding=start_coding,
108
+ end_coding=end_coding,
109
+ # start_interval=start_interval,
110
+ # end_interval=end_interval,
111
+ start_interval=None,
112
+ end_interval=None,
113
+ maxTime=max_media_duration_all_obs,
114
+ n_observations=len(selected_observations),
115
+ show_include_modifiers=False,
116
+ show_exclude_non_coded_behaviors=True,
117
+ )
118
+
119
+ if not parameters: # cancel button pressed
120
+ return
121
+
122
+ if not parameters[cfg.SELECTED_SUBJECTS]:
123
+ QMessageBox.warning(None, cfg.programName, "Select the subject(s) to analyze")
124
+ flag_ok = False
125
+
126
+ # check number of behaviors (must be <=4)
127
+ if flag_ok and len(parameters[cfg.SELECTED_BEHAVIORS]) > 4:
128
+ QMessageBox.warning(None, cfg.programName, "You cannot select more than 4 behaviors")
129
+ flag_ok = False
130
+
131
+ # check number of behaviors (must be > 1)
132
+ if flag_ok and len(parameters[cfg.SELECTED_BEHAVIORS]) < 2:
133
+ QMessageBox.warning(None, cfg.programName, "You must select almost 2 behaviors")
134
+ flag_ok = False
135
+
136
+ if flag_ok:
137
+ break
138
+
139
+ logging.debug(f"{parameters[cfg.SELECTED_BEHAVIORS]}")
140
+
141
+ state_events_list = util.state_behavior_codes(self.pj[cfg.ETHOGRAM])
142
+
143
+ events_interval: dict = {}
144
+ mem_events_interval: dict = {}
145
+
146
+ for obs_id in selected_observations:
147
+ events_interval[obs_id] = {}
148
+ mem_events_interval[obs_id] = {}
149
+
150
+ for event in self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]:
151
+ if event[cfg.EVENT_SUBJECT_FIELD_IDX] not in events_interval[obs_id]:
152
+ events_interval[obs_id][event[cfg.EVENT_SUBJECT_FIELD_IDX]] = {}
153
+ mem_events_interval[obs_id][event[cfg.EVENT_SUBJECT_FIELD_IDX]] = {}
154
+
155
+ if event[cfg.EVENT_BEHAVIOR_FIELD_IDX] not in events_interval[obs_id][event[cfg.EVENT_SUBJECT_FIELD_IDX]]:
156
+ events_interval[obs_id][event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]] = I.empty()
157
+ mem_events_interval[obs_id][event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]] = []
158
+
159
+ # state event
160
+ if event[cfg.EVENT_BEHAVIOR_FIELD_IDX] in state_events_list:
161
+ mem_events_interval[obs_id][event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]].append(
162
+ event[cfg.EVENT_TIME_FIELD_IDX]
163
+ )
164
+ if len(mem_events_interval[obs_id][event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]]) == 2:
165
+ events_interval[obs_id][event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]] |= I.closedopen(
166
+ mem_events_interval[obs_id][event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]][0],
167
+ mem_events_interval[obs_id][event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]][1],
168
+ )
169
+ mem_events_interval[obs_id][event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]] = []
170
+ # point event
171
+ else:
172
+ events_interval[obs_id][event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]] |= I.singleton(
173
+ event[cfg.EVENT_TIME_FIELD_IDX]
174
+ )
175
+
176
+ logging.debug(f"events_interval: {events_interval}")
177
+
178
+ cooccurence_results: dict = {}
179
+
180
+ for obs_id in selected_observations:
181
+ logging.debug(f"obs_id: {obs_id}")
182
+
183
+ for subject in parameters[cfg.SELECTED_SUBJECTS]:
184
+ if subject == "No focal subject":
185
+ subj = ""
186
+ else:
187
+ subj = subject
188
+
189
+ if subject not in cooccurence_results:
190
+ cooccurence_results[subject] = {}
191
+
192
+ logging.debug(f"subject {subject}")
193
+
194
+ for n_combinations in range(2, len(parameters[cfg.SELECTED_BEHAVIORS]) + 1):
195
+ union = I.empty()
196
+
197
+ logging.debug(f"{n_combinations=}")
198
+
199
+ for combination in itertools.combinations(parameters[cfg.SELECTED_BEHAVIORS], n_combinations):
200
+ logging.debug(f"{combination=}")
201
+ if subj in events_interval[obs_id]:
202
+ # init
203
+ if combination[0] in events_interval[obs_id][subj]:
204
+ union = events_interval[obs_id][subj][combination[0]]
205
+ else:
206
+ union = I.empty()
207
+
208
+ logging.debug(f"{combination[0]=} {union=}")
209
+
210
+ for combination2 in combination[1:]:
211
+ if combination2 in events_interval[obs_id][subj]:
212
+ inter2 = events_interval[obs_id][subj][combination2]
213
+ else:
214
+ inter2 = I.empty()
215
+
216
+ logging.debug(f"{combination2=} {inter2=}")
217
+
218
+ union &= inter2
219
+
220
+ if combination not in cooccurence_results[subject]:
221
+ cooccurence_results[subject][combination] = 0
222
+
223
+ logging.debug(f"{combination=} {union=}")
224
+ cooccurence_results[subject][combination] += interval_len(union)
225
+ else:
226
+ if combination not in cooccurence_results[subject]:
227
+ cooccurence_results[subject][combination] = 0
228
+ cooccurence_results[subject][combination] += 0
229
+
230
+ logging.debug(f"{cooccurence_results[subject][combination]=}")
231
+
232
+ logging.debug(cooccurence_results)
233
+
234
+ out = f"<b>Co-occurence of behaviors: {','.join(parameters[cfg.SELECTED_BEHAVIORS])}</b><br><br>"
235
+ for subject in parameters[cfg.SELECTED_SUBJECTS]:
236
+ out += f"<br>Subject <b>{subject}</b><br><br>"
237
+ for combination in cooccurence_results[subject]:
238
+ if parameters[cfg.EXCLUDE_BEHAVIORS] and not cooccurence_results[subject][combination]:
239
+ continue
240
+ duration = f"<b>{cooccurence_results[subject][combination]}</b>" if cooccurence_results[subject][combination] else "0"
241
+ out += f"<b>{'</b> and <b>'.join(combination)}</b>: {duration} s<br>"
242
+
243
+ self.results = dialog.Results_dialog()
244
+ self.results.setWindowTitle("Behaviors co-occurence")
245
+ self.results.ptText.setFont(QFont("Courier", 12))
246
+ self.results.ptText.setWordWrapMode(QTextOption.NoWrap)
247
+ self.results.ptText.setReadOnly(True)
248
+ self.results.ptText.clear()
249
+ self.results.ptText.appendHtml(out)
250
+ self.results.show()