boris-behav-obs 8.9.16__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.

Files changed (129) hide show
  1. boris/__init__.py +1 -1
  2. boris/__main__.py +1 -1
  3. boris/about.py +36 -39
  4. boris/add_modifier.py +122 -109
  5. boris/add_modifier_ui.py +239 -135
  6. boris/advanced_event_filtering.py +81 -45
  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 +228 -229
  18. boris/behavior_binary_table.py +33 -50
  19. boris/behaviors_coding_map.py +17 -18
  20. boris/boris_cli.py +6 -25
  21. boris/cmd_arguments.py +12 -1
  22. boris/coding_pad.py +42 -49
  23. boris/config.py +161 -77
  24. boris/config_file.py +63 -83
  25. boris/connections.py +112 -57
  26. boris/converters.py +13 -37
  27. boris/converters_ui.py +187 -110
  28. boris/cooccurence.py +250 -0
  29. boris/core.py +2511 -1824
  30. boris/core_qrc.py +15895 -10185
  31. boris/core_ui.py +946 -792
  32. boris/db_functions.py +21 -41
  33. boris/dev.py +134 -0
  34. boris/dialog.py +505 -244
  35. boris/duration_widget.py +15 -20
  36. boris/edit_event.py +84 -28
  37. boris/edit_event_ui.py +214 -78
  38. boris/event_operations.py +517 -415
  39. boris/events_cursor.py +25 -17
  40. boris/events_snapshots.py +36 -82
  41. boris/exclusion_matrix.py +4 -9
  42. boris/export_events.py +213 -583
  43. boris/export_observation.py +98 -611
  44. boris/external_processes.py +156 -97
  45. boris/geometric_measurement.py +652 -287
  46. boris/gui_utilities.py +91 -14
  47. boris/image_overlay.py +9 -9
  48. boris/import_observations.py +190 -98
  49. boris/ipc_mpv.py +325 -0
  50. boris/irr.py +26 -63
  51. boris/latency.py +34 -25
  52. boris/measurement_widget.py +14 -18
  53. boris/media_file.py +52 -84
  54. boris/menu_options.py +17 -6
  55. boris/modifier_coding_map_creator.py +1013 -0
  56. boris/modifiers_coding_map.py +7 -9
  57. boris/mpv.py +1 -0
  58. boris/mpv2.py +732 -705
  59. boris/observation.py +655 -310
  60. boris/observation_operations.py +1036 -404
  61. boris/observation_ui.py +584 -356
  62. boris/observations_list.py +71 -53
  63. boris/otx_parser.py +74 -80
  64. boris/param_panel.py +31 -16
  65. boris/param_panel_ui.py +254 -138
  66. boris/player_dock_widget.py +90 -60
  67. boris/plot_data_module.py +43 -46
  68. boris/plot_events.py +127 -90
  69. boris/plot_events_rt.py +17 -31
  70. boris/plot_spectrogram_rt.py +95 -30
  71. boris/plot_waveform_rt.py +32 -21
  72. boris/plugins.py +431 -0
  73. boris/portion/__init__.py +18 -8
  74. boris/portion/const.py +35 -18
  75. boris/portion/dict.py +5 -5
  76. boris/portion/func.py +2 -2
  77. boris/portion/interval.py +21 -41
  78. boris/portion/io.py +41 -32
  79. boris/preferences.py +306 -83
  80. boris/preferences_ui.py +685 -228
  81. boris/project.py +448 -293
  82. boris/project_functions.py +689 -254
  83. boris/project_import_export.py +213 -222
  84. boris/project_ui.py +674 -438
  85. boris/qrc_boris.py +6 -3
  86. boris/qrc_boris5.py +6 -3
  87. boris/select_modifiers.py +74 -48
  88. boris/select_observations.py +20 -199
  89. boris/select_subj_behav.py +67 -39
  90. boris/state_events.py +53 -37
  91. boris/subjects_pad.py +6 -9
  92. boris/synthetic_time_budget.py +45 -28
  93. boris/time_budget_functions.py +171 -171
  94. boris/time_budget_widget.py +84 -114
  95. boris/transitions.py +41 -47
  96. boris/utilities.py +766 -266
  97. boris/version.py +3 -3
  98. boris/video_equalizer.py +16 -14
  99. boris/video_equalizer_ui.py +199 -130
  100. boris/video_operations.py +125 -28
  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.6.dist-info/METADATA +139 -0
  105. boris_behav_obs-9.7.6.dist-info/RECORD +109 -0
  106. {boris_behav_obs-8.9.16.dist-info → boris_behav_obs-9.7.6.dist-info}/WHEEL +1 -1
  107. boris_behav_obs-9.7.6.dist-info/entry_points.txt +2 -0
  108. boris/README.TXT +0 -22
  109. boris/add_modifier.ui +0 -323
  110. boris/boris_ui.py +0 -886
  111. boris/converters.ui +0 -289
  112. boris/core.qrc +0 -35
  113. boris/core.ui +0 -1543
  114. boris/edit_event.ui +0 -175
  115. boris/icons/logo_eye.ico +0 -0
  116. boris/map_creator.py +0 -850
  117. boris/observation.ui +0 -773
  118. boris/param_panel.ui +0 -379
  119. boris/preferences.ui +0 -537
  120. boris/project.ui +0 -1069
  121. boris/project_server.py +0 -236
  122. boris/vlc.py +0 -10343
  123. boris/vlc_local.py +0 -90
  124. boris_behav_obs-8.9.16.dist-info/LICENSE.TXT +0 -674
  125. boris_behav_obs-8.9.16.dist-info/METADATA +0 -129
  126. boris_behav_obs-8.9.16.dist-info/RECORD +0 -108
  127. boris_behav_obs-8.9.16.dist-info/entry_points.txt +0 -2
  128. {boris → boris_behav_obs-9.7.6.dist-info/licenses}/LICENSE.TXT +0 -0
  129. {boris_behav_obs-8.9.16.dist-info → boris_behav_obs-9.7.6.dist-info}/top_level.txt +0 -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()