boris-behav-obs 8.12__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 (128) hide show
  1. boris/__init__.py +1 -1
  2. boris/__main__.py +1 -1
  3. boris/about.py +28 -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 +141 -65
  24. boris/config_file.py +58 -67
  25. boris/connections.py +107 -61
  26. boris/converters.py +13 -37
  27. boris/converters_ui.py +187 -110
  28. boris/cooccurence.py +250 -0
  29. boris/core.py +2373 -1786
  30. boris/core_qrc.py +15895 -10743
  31. boris/core_ui.py +943 -798
  32. boris/db_functions.py +17 -42
  33. boris/dev.py +109 -8
  34. boris/dialog.py +482 -236
  35. boris/duration_widget.py +9 -14
  36. boris/edit_event.py +61 -31
  37. boris/edit_event_ui.py +208 -97
  38. boris/event_operations.py +408 -293
  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 +184 -223
  43. boris/export_observation.py +74 -100
  44. boris/external_processes.py +123 -98
  45. boris/geometric_measurement.py +644 -290
  46. boris/gui_utilities.py +91 -14
  47. boris/image_overlay.py +4 -4
  48. boris/import_observations.py +190 -98
  49. boris/ipc_mpv.py +325 -0
  50. boris/irr.py +20 -57
  51. boris/latency.py +31 -24
  52. boris/measurement_widget.py +14 -18
  53. boris/media_file.py +17 -19
  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 +533 -221
  60. boris/observation_operations.py +1025 -390
  61. boris/observation_ui.py +572 -362
  62. boris/observations_list.py +71 -53
  63. boris/otx_parser.py +74 -68
  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 +25 -33
  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 +684 -227
  81. boris/project.py +448 -293
  82. boris/project_functions.py +671 -238
  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 -198
  89. boris/select_subj_behav.py +67 -39
  90. boris/state_events.py +52 -35
  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 +627 -236
  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 +95 -29
  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.12.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/converters.ui +0 -289
  111. boris/core.qrc +0 -36
  112. boris/core.ui +0 -1556
  113. boris/edit_event.ui +0 -233
  114. boris/icons/logo_eye.ico +0 -0
  115. boris/map_creator.py +0 -850
  116. boris/observation.ui +0 -814
  117. boris/param_panel.ui +0 -379
  118. boris/preferences.ui +0 -537
  119. boris/project.ui +0 -1069
  120. boris/project_server.py +0 -236
  121. boris/vlc.py +0 -10343
  122. boris/vlc_local.py +0 -90
  123. boris_behav_obs-8.12.dist-info/LICENSE.TXT +0 -674
  124. boris_behav_obs-8.12.dist-info/METADATA +0 -128
  125. boris_behav_obs-8.12.dist-info/RECORD +0 -108
  126. boris_behav_obs-8.12.dist-info/entry_points.txt +0 -3
  127. {boris → boris_behav_obs-9.7.6.dist-info/licenses}/LICENSE.TXT +0 -0
  128. {boris_behav_obs-8.12.dist-info → boris_behav_obs-9.7.6.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2023 Olivier Friard
4
+ Copyright 2012-2025 Olivier Friard
5
5
 
6
6
  This program is free software; you can redistribute it and/or modify
7
7
  it under the terms of the GNU General Public License as published by
@@ -24,6 +24,9 @@ import statistics
24
24
  from decimal import Decimal as dec
25
25
  from typing import Tuple
26
26
  import tablib
27
+ import logging
28
+ import itertools
29
+ import re
27
30
 
28
31
  from . import config as cfg
29
32
  from . import db_functions
@@ -32,14 +35,14 @@ from . import project_functions
32
35
  from . import observation_operations
33
36
 
34
37
 
35
- def default_value(ethogram: dict, behav: str, param):
38
+ def default_value(ethogram: dict, behavior_code: str, param):
36
39
  """
37
40
  return value for duration in case of point event
38
41
  """
39
42
  default_value_ = 0.0
40
- behav_type = project_functions.event_type(behav, ethogram)
43
+ behavior_type = project_functions.event_type(behavior_code, ethogram)
41
44
 
42
- if behav_type == "POINT EVENT" and param in (
45
+ if behavior_type in cfg.POINT_EVENT_TYPES and param in (
43
46
  "duration",
44
47
  "duration mean",
45
48
  "duration stdev",
@@ -47,7 +50,7 @@ def default_value(ethogram: dict, behav: str, param):
47
50
  ):
48
51
  default_value_ = cfg.NA
49
52
 
50
- if behav_type == "STATE EVENT" and param in (
53
+ if behavior_type in cfg.STATE_EVENT_TYPES and param in (
51
54
  "duration mean",
52
55
  "duration stdev",
53
56
  ):
@@ -64,7 +67,6 @@ def init_behav_modif(ethogram: dict, selected_subjects: list, distinct_behav_mod
64
67
  for subj in selected_subjects:
65
68
  behaviors[subj] = {}
66
69
  for behav_modif in distinct_behav_modif:
67
-
68
70
  behav, modif = behav_modif
69
71
  behav_modif_str = "|".join(behav_modif) if modif else behav
70
72
 
@@ -85,7 +87,6 @@ def init_behav_modif_bin(ethogram: dict, selected_subjects: list, distinct_behav
85
87
  for subj in selected_subjects:
86
88
  behaviors[subj] = {}
87
89
  for behav_modif in distinct_behav_modif:
88
-
89
90
  if behav_modif not in behaviors[subj]:
90
91
  behaviors[subj][behav_modif] = {}
91
92
 
@@ -135,35 +136,24 @@ def synthetic_time_budget_bin(pj: dict, selected_observations: list, parameters_
135
136
  """
136
137
 
137
138
  def interval_len(interval):
138
- if interval.empty:
139
- return dec(0)
140
- else:
141
- return sum([x.upper - x.lower for x in interval])
139
+ return dec(0) if interval.empty else sum([x.upper - x.lower for x in interval])
142
140
 
143
141
  def interval_number(interval):
144
- if interval.empty:
145
- return dec(0)
146
- else:
147
- return len(interval)
142
+ return dec(0) if interval.empty else len(interval)
148
143
 
149
144
  def interval_mean(interval):
150
- if interval.empty:
151
- return dec(0)
152
- else:
153
- return sum([x.upper - x.lower for x in interval]) / len(interval)
145
+ return dec(0) if interval.empty else sum([x.upper - x.lower for x in interval]) / len(interval)
154
146
 
155
147
  def interval_std_dev(interval) -> str:
156
148
  if interval.empty:
157
- return "NA"
149
+ return cfg.NA
158
150
  else:
159
151
  try:
160
152
  return f"{statistics.stdev([x.upper - x.lower for x in interval]):.3f}"
161
- except:
162
- return "NA"
153
+ except Exception:
154
+ return cfg.NA
163
155
 
164
- selected_subjects = parameters_obs[cfg.SELECTED_SUBJECTS]
165
156
  selected_behaviors = parameters_obs[cfg.SELECTED_BEHAVIORS]
166
- include_modifiers = parameters_obs[cfg.INCLUDE_MODIFIERS]
167
157
  time_interval = parameters_obs["time"]
168
158
  start_time = parameters_obs[cfg.START_TIME]
169
159
  end_time = parameters_obs[cfg.END_TIME]
@@ -183,14 +173,26 @@ def synthetic_time_budget_bin(pj: dict, selected_observations: list, parameters_
183
173
  distinct_behav_modif = []
184
174
  for obs_id in selected_observations:
185
175
  for event in pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]:
186
- if include_modifiers:
187
- if (
188
- event[cfg.EVENT_BEHAVIOR_FIELD_IDX],
189
- event[cfg.EVENT_MODIFIER_FIELD_IDX],
190
- ) not in distinct_behav_modif:
191
- distinct_behav_modif.append(
192
- (event[cfg.EVENT_BEHAVIOR_FIELD_IDX], event[cfg.EVENT_MODIFIER_FIELD_IDX])
193
- )
176
+ if parameters_obs[cfg.INCLUDE_MODIFIERS]:
177
+ if parameters_obs[cfg.EXCLUDE_NON_CODED_MODIFIERS]:
178
+ # get coded modifiers
179
+ if (
180
+ event[cfg.EVENT_BEHAVIOR_FIELD_IDX],
181
+ event[cfg.EVENT_MODIFIER_FIELD_IDX],
182
+ ) not in distinct_behav_modif:
183
+ distinct_behav_modif.append((event[cfg.EVENT_BEHAVIOR_FIELD_IDX], event[cfg.EVENT_MODIFIER_FIELD_IDX]))
184
+ else:
185
+ # get all modifiers combination
186
+ ms: list = []
187
+ modifiers_list = project_functions.get_modifiers_of_behavior(pj[cfg.ETHOGRAM], event[cfg.EVENT_BEHAVIOR_FIELD_IDX])
188
+ if modifiers_list:
189
+ for modif_set in modifiers_list[0]:
190
+ modif_set.append("None")
191
+ ms.append([re.sub(r" \(.*\)", "", x) for x in modif_set])
192
+ distinct_modifiers = ["|".join(x) for x in itertools.product(*ms)]
193
+ for modifier in distinct_modifiers:
194
+ distinct_behav_modif.append((event[cfg.EVENT_BEHAVIOR_FIELD_IDX], modifier))
195
+
194
196
  else:
195
197
  if (event[cfg.EVENT_BEHAVIOR_FIELD_IDX], "") not in distinct_behav_modif:
196
198
  distinct_behav_modif.append((event[cfg.EVENT_BEHAVIOR_FIELD_IDX], ""))
@@ -202,8 +204,6 @@ def synthetic_time_budget_bin(pj: dict, selected_observations: list, parameters_
202
204
  if [x for x in distinct_behav_modif if x[0] == behav] == []:
203
205
  distinct_behav_modif.append((behav, ""))
204
206
 
205
- behaviors = init_behav_modif_bin(pj[cfg.ETHOGRAM], selected_subjects, distinct_behav_modif, parameters)
206
-
207
207
  param_header = ["Observations id", "Total length (s)", "Time interval (s)"]
208
208
  subj_header, behav_header, modif_header = (
209
209
  [""] * len(param_header),
@@ -214,7 +214,7 @@ def synthetic_time_budget_bin(pj: dict, selected_observations: list, parameters_
214
214
  behav_header[1] = "Behaviors:"
215
215
  modif_header[1] = "Modifiers:"
216
216
 
217
- for subj in selected_subjects:
217
+ for subj in parameters_obs[cfg.SELECTED_SUBJECTS]:
218
218
  for behavior_modifiers in distinct_behav_modif:
219
219
  behavior, modifiers = behavior_modifiers
220
220
  behavior_modifiers_str = "|".join(behavior_modifiers) if modifiers else behavior
@@ -226,17 +226,16 @@ def synthetic_time_budget_bin(pj: dict, selected_observations: list, parameters_
226
226
 
227
227
  data_report.append(subj_header)
228
228
  data_report.append(behav_header)
229
- if include_modifiers:
229
+ if parameters_obs[cfg.INCLUDE_MODIFIERS]:
230
230
  data_report.append(modif_header)
231
231
  data_report.append(param_header)
232
232
 
233
233
  state_events_list = [
234
- pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE]
235
- for x in pj[cfg.ETHOGRAM]
236
- if cfg.STATE in pj[cfg.ETHOGRAM][x][cfg.TYPE].upper()
234
+ pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] for x in pj[cfg.ETHOGRAM] if cfg.STATE in pj[cfg.ETHOGRAM][x][cfg.TYPE].upper()
237
235
  ]
238
236
  # select time interval
239
237
  for obs_id in selected_observations:
238
+ behaviors = init_behav_modif_bin(pj[cfg.ETHOGRAM], parameters_obs[cfg.SELECTED_SUBJECTS], distinct_behav_modif, parameters)
240
239
 
241
240
  obs_length = observation_operations.observation_total_length(pj[cfg.OBSERVATIONS][obs_id])
242
241
 
@@ -260,6 +259,13 @@ def synthetic_time_budget_bin(pj: dict, selected_observations: list, parameters_
260
259
  min_time = dec(start_time)
261
260
  max_time = dec(end_time)
262
261
 
262
+ if time_interval == cfg.TIME_OBS_INTERVAL:
263
+ obs_interval = pj[cfg.OBSERVATIONS][obs_id].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0])
264
+ offset = pj[cfg.OBSERVATIONS][obs_id][cfg.TIME_OFFSET]
265
+ min_time = dec(obs_interval[0]) + offset
266
+ # Use max media duration for max time if no interval is defined (=0)
267
+ max_time = dec(obs_interval[1]) + offset if obs_interval[1] != 0 else dec(obs_length)
268
+
263
269
  events_interval = {}
264
270
  mem_events_interval = {}
265
271
 
@@ -269,13 +275,13 @@ def synthetic_time_budget_bin(pj: dict, selected_observations: list, parameters_
269
275
  else:
270
276
  current_subject = event[cfg.EVENT_SUBJECT_FIELD_IDX]
271
277
 
272
- if current_subject not in selected_subjects:
278
+ if current_subject not in parameters_obs[cfg.SELECTED_SUBJECTS]:
273
279
  continue
274
280
  if current_subject not in events_interval:
275
281
  events_interval[current_subject] = {}
276
282
  mem_events_interval[current_subject] = {}
277
283
 
278
- if include_modifiers:
284
+ if parameters_obs[cfg.INCLUDE_MODIFIERS]:
279
285
  modif = event[cfg.EVENT_MODIFIER_FIELD_IDX]
280
286
  else:
281
287
  modif = ""
@@ -287,9 +293,7 @@ def synthetic_time_budget_bin(pj: dict, selected_observations: list, parameters_
287
293
  mem_events_interval[current_subject][(event[cfg.EVENT_BEHAVIOR_FIELD_IDX], modif)] = []
288
294
 
289
295
  if event[cfg.EVENT_BEHAVIOR_FIELD_IDX] in state_events_list:
290
- mem_events_interval[current_subject][(event[cfg.EVENT_BEHAVIOR_FIELD_IDX], modif)].append(
291
- event[cfg.EVENT_TIME_FIELD_IDX]
292
- )
296
+ mem_events_interval[current_subject][(event[cfg.EVENT_BEHAVIOR_FIELD_IDX], modif)].append(event[cfg.EVENT_TIME_FIELD_IDX])
293
297
  if len(mem_events_interval[current_subject][(event[cfg.EVENT_BEHAVIOR_FIELD_IDX], modif)]) == 2:
294
298
  events_interval[current_subject][(event[cfg.EVENT_BEHAVIOR_FIELD_IDX], modif)] |= I.closedopen(
295
299
  mem_events_interval[current_subject][(event[cfg.EVENT_BEHAVIOR_FIELD_IDX], modif)][0],
@@ -311,9 +315,7 @@ def synthetic_time_budget_bin(pj: dict, selected_observations: list, parameters_
311
315
  time_bin_end = max_time
312
316
 
313
317
  while True:
314
-
315
318
  for subject in events_interval:
316
-
317
319
  # check behavior to exclude from total time
318
320
  time_to_subtract = 0
319
321
  if cfg.EXCLUDED_BEHAVIORS in parameters_obs:
@@ -323,33 +325,32 @@ def synthetic_time_budget_bin(pj: dict, selected_observations: list, parameters_
323
325
  time_to_subtract += interval_len(interval_intersec)
324
326
 
325
327
  for behav in events_interval[subject]:
326
-
327
328
  interval_intersec = events_interval[subject][behav] & I.closed(time_bin_start, time_bin_end)
328
329
 
329
330
  nocc = interval_number(interval_intersec)
330
331
  behaviors[subject][behav]["number"] = nocc
331
332
 
332
333
  behav_type = project_functions.event_type(behav[0], pj[cfg.ETHOGRAM])
333
- if behav_type == "STATE EVENT":
334
+ if behav_type in cfg.STATE_EVENT_TYPES:
334
335
  dur = interval_len(interval_intersec)
335
336
  behaviors[subject][behav]["duration"] = f"{dur:.3f}"
336
337
  behaviors[subject][behav]["duration mean"] = f"{interval_mean(interval_intersec):.3f}"
337
338
  behaviors[subject][behav]["duration stdev"] = interval_std_dev(interval_intersec)
338
339
 
339
340
  if behav[0] in parameters_obs.get(cfg.EXCLUDED_BEHAVIORS, []):
340
- proportion = dur / ((time_bin_end - time_bin_start))
341
+ proportion = dur / (time_bin_end - time_bin_start)
341
342
  else:
342
343
  proportion = dur / ((time_bin_end - time_bin_start) - time_to_subtract)
343
344
  behaviors[subject][behav]["proportion of time"] = f"{proportion:.3f}"
344
345
 
345
- if behav_type == "POINT EVENT":
346
+ if behav_type in cfg.POINT_EVENT_TYPES:
346
347
  behaviors[subject][behav]["duration"] = cfg.NA
347
348
  behaviors[subject][behav]["duration mean"] = cfg.NA
348
349
  behaviors[subject][behav]["duration stdev"] = cfg.NA
349
350
  behaviors[subject][behav]["proportion of time"] = cfg.NA
350
351
 
351
352
  columns = [obs_id, f"{max_time - min_time:.3f}", f"{time_bin_start:.3f}-{time_bin_end:.3f}"]
352
- for subject in selected_subjects:
353
+ for subject in parameters_obs[cfg.SELECTED_SUBJECTS]:
353
354
  for behavior_modifiers in distinct_behav_modif:
354
355
  behavior, modifiers = behavior_modifiers
355
356
  behavior_modifiers_str = behavior_modifiers
@@ -385,12 +386,9 @@ def synthetic_time_budget(pj: dict, selected_observations: list, parameters_obs:
385
386
  tablib.Dataset: dataset containing synthetic time budget data
386
387
  """
387
388
 
388
- selected_subjects = parameters_obs[cfg.SELECTED_SUBJECTS]
389
- selected_behaviors = parameters_obs[cfg.SELECTED_BEHAVIORS]
390
- include_modifiers = parameters_obs[cfg.INCLUDE_MODIFIERS]
391
389
  interval = parameters_obs["time"]
392
- start_time = parameters_obs["start time"]
393
- end_time = parameters_obs["end time"]
390
+ start_time = parameters_obs[cfg.START_TIME]
391
+ end_time = parameters_obs[cfg.END_TIME]
394
392
 
395
393
  parameters = [
396
394
  ["duration", "Total duration"],
@@ -404,31 +402,41 @@ def synthetic_time_budget(pj: dict, selected_observations: list, parameters_obs:
404
402
  data_report.title = "Synthetic time budget"
405
403
 
406
404
  ok, msg, db_connector = db_functions.load_aggregated_events_in_db(
407
- pj, selected_subjects, selected_observations, selected_behaviors
405
+ pj, parameters_obs[cfg.SELECTED_SUBJECTS], selected_observations, parameters_obs[cfg.SELECTED_BEHAVIORS]
408
406
  )
409
407
 
410
- # return
411
-
412
408
  if not ok:
413
409
  return False, msg, None
414
410
 
415
411
  db_connector.create_aggregate("stdev", 1, StdevFunc)
416
412
  cursor = db_connector.cursor()
417
413
 
418
- # modifiers
419
- if include_modifiers:
420
- cursor.execute("SELECT distinct behavior, modifiers FROM aggregated_events")
421
- distinct_behav_modif = [[rows["behavior"], rows["modifiers"]] for rows in cursor.fetchall()]
422
- else:
423
- cursor.execute("SELECT distinct behavior FROM aggregated_events")
424
- distinct_behav_modif = [[rows["behavior"], ""] for rows in cursor.fetchall()]
425
-
426
414
  # add selected behaviors that are not observed
427
- for behav in selected_behaviors:
428
- if [x for x in distinct_behav_modif if x[0] == behav] == []:
429
- distinct_behav_modif.append([behav, ""])
415
+ distinct_behav_modif: list = []
416
+ for behavior in parameters_obs[cfg.SELECTED_BEHAVIORS]:
417
+ # modifiers
418
+ if parameters_obs[cfg.INCLUDE_MODIFIERS]:
419
+ if parameters_obs[cfg.EXCLUDE_NON_CODED_MODIFIERS]:
420
+ # get coded modifiers
421
+ cursor.execute("SELECT DISTINCT modifiers FROM aggregated_events WHERE behavior = ?", (behavior,))
422
+ for row in cursor.fetchall():
423
+ distinct_behav_modif.append((behavior, row["modifiers"]))
424
+ else:
425
+ # get all modifiers combination
426
+ ms: list = []
427
+ modifiers_list = project_functions.get_modifiers_of_behavior(pj[cfg.ETHOGRAM], behavior)
428
+ if modifiers_list:
429
+ for modif_set in modifiers_list[0]:
430
+ modif_set.append("None")
431
+ ms.append([re.sub(r" \(.*\)", "", x) for x in modif_set])
432
+ distinct_modifiers = ["|".join(x) for x in itertools.product(*ms)]
433
+ for modifier in distinct_modifiers:
434
+ distinct_behav_modif.append((behavior, modifier))
435
+
436
+ else:
437
+ distinct_behav_modif.append((behavior, ""))
430
438
 
431
- behaviors = init_behav_modif(pj[cfg.ETHOGRAM], selected_subjects, distinct_behav_modif, parameters)
439
+ # print(f"{distinct_behav_modif=}")
432
440
 
433
441
  param_header = ["Observations id", "Total length (s)"]
434
442
  subj_header, behav_header, modif_header = (
@@ -440,10 +448,10 @@ def synthetic_time_budget(pj: dict, selected_observations: list, parameters_obs:
440
448
  behav_header[1] = "Behaviors:"
441
449
  modif_header[1] = "Modifiers:"
442
450
 
443
- for subj in selected_subjects:
444
- for behavior_modifiers in distinct_behav_modif:
445
- behavior, modifiers = behavior_modifiers
446
- behavior_modifiers_str = "|".join(behavior_modifiers) if modifiers else behavior
451
+ for subj in parameters_obs[cfg.SELECTED_SUBJECTS]:
452
+ for behavior, modifiers in distinct_behav_modif:
453
+ """behavior, modifiers = behavior_modifiers"""
454
+ """behavior_modifiers_str = "|".join(behavior_modifiers) if modifiers else behavior"""
447
455
  for param in parameters:
448
456
  subj_header.append(subj)
449
457
  behav_header.append(behavior)
@@ -452,15 +460,16 @@ def synthetic_time_budget(pj: dict, selected_observations: list, parameters_obs:
452
460
 
453
461
  data_report.append(subj_header)
454
462
  data_report.append(behav_header)
455
- if include_modifiers:
463
+ if parameters_obs[cfg.INCLUDE_MODIFIERS]:
456
464
  data_report.append(modif_header)
457
465
  data_report.append(param_header)
458
466
 
459
467
  # select time interval
460
468
  for obs_id in selected_observations:
469
+ behaviors = init_behav_modif(pj[cfg.ETHOGRAM], parameters_obs[cfg.SELECTED_SUBJECTS], distinct_behav_modif, parameters)
461
470
 
462
471
  ok, msg, db_connector = db_functions.load_aggregated_events_in_db(
463
- pj, selected_subjects, [obs_id], selected_behaviors
472
+ pj, parameters_obs[cfg.SELECTED_SUBJECTS], [obs_id], parameters_obs[cfg.SELECTED_BEHAVIORS]
464
473
  )
465
474
 
466
475
  if not ok:
@@ -469,7 +478,7 @@ def synthetic_time_budget(pj: dict, selected_observations: list, parameters_obs:
469
478
  db_connector.create_aggregate("stdev", 1, StdevFunc)
470
479
  cursor = db_connector.cursor()
471
480
  # if modifiers not to be included set modifiers to ""
472
- if not include_modifiers:
481
+ if not parameters_obs[cfg.INCLUDE_MODIFIERS]:
473
482
  cursor.execute("UPDATE aggregated_events SET modifiers = ''")
474
483
 
475
484
  # time
@@ -544,19 +553,14 @@ def synthetic_time_budget(pj: dict, selected_observations: list, parameters_obs:
544
553
  ),
545
554
  )
546
555
 
547
- for subject in selected_subjects:
548
-
556
+ for subject in parameters_obs[cfg.SELECTED_SUBJECTS]:
549
557
  # check if behaviors are to exclude from total time
550
558
  time_to_subtract = 0
551
559
  if obs_length != dec(-2): # obs not an images obs without time
552
560
  if cfg.EXCLUDED_BEHAVIORS in parameters_obs:
553
561
  for excluded_behav in parameters_obs[cfg.EXCLUDED_BEHAVIORS]:
554
562
  cursor.execute(
555
- (
556
- "SELECT SUM(stop-start) "
557
- "FROM aggregated_events "
558
- "WHERE observation = ? AND subject = ? AND behavior = ? "
559
- ),
563
+ ("SELECT SUM(stop-start) FROM aggregated_events WHERE observation = ? AND subject = ? AND behavior = ? "),
560
564
  (
561
565
  obs_id,
562
566
  subject,
@@ -589,20 +593,15 @@ def synthetic_time_budget(pj: dict, selected_observations: list, parameters_obs:
589
593
  )
590
594
 
591
595
  for row in cursor.fetchall():
592
-
593
- behaviors[subject][behavior_modifiers_str]["number"] = (
594
- 0 if row["n_occurences"] is None else row["n_occurences"]
595
- )
596
+ behaviors[subject][behavior_modifiers_str]["number"] = 0 if row["n_occurences"] is None else row["n_occurences"]
596
597
 
597
598
  if obs_length == dec(-2): # images obs without time
598
-
599
599
  behaviors[subject][behavior_modifiers_str]["duration"] = cfg.NA
600
600
  behaviors[subject][behavior_modifiers_str]["duration mean"] = cfg.NA
601
601
  behaviors[subject][behavior_modifiers_str]["duration stdev"] = cfg.NA
602
602
  behaviors[subject][behavior_modifiers_str]["proportion of time"] = cfg.NA
603
603
 
604
604
  else:
605
-
606
605
  if row["type"] == cfg.POINT:
607
606
  behaviors[subject][behavior_modifiers_str]["duration"] = cfg.NA
608
607
  behaviors[subject][behavior_modifiers_str]["duration mean"] = cfg.NA
@@ -632,16 +631,14 @@ def synthetic_time_budget(pj: dict, selected_observations: list, parameters_obs:
632
631
  else:
633
632
  # behavior subtracted
634
633
  behaviors[subject][behavior_modifiers_str]["proportion of time"] = (
635
- cfg.NA
636
- if row["duration"] is None
637
- else f"{row['duration'] / (max_time - min_time):.3f}"
634
+ cfg.NA if row["duration"] is None else f"{row['duration'] / (max_time - min_time):.3f}"
638
635
  )
639
636
 
640
637
  if obs_length == dec(-2):
641
638
  columns = [obs_id, cfg.NA]
642
639
  else:
643
640
  columns = [obs_id, f"{max_time - min_time:0.3f}"]
644
- for subj in selected_subjects:
641
+ for subj in parameters_obs[cfg.SELECTED_SUBJECTS]:
645
642
  for behavior_modifiers in distinct_behav_modif:
646
643
  behavior, modifiers = behavior_modifiers
647
644
  behavior_modifiers_str = "|".join(behavior_modifiers) if modifiers else behavior
@@ -666,39 +663,54 @@ def time_budget_analysis(
666
663
  cursor: cursor on temporary database
667
664
  selected_observations (list): selected observations
668
665
  parameters (dict): parameters for analysis
669
- by_category (bool): True for grouping in category else False
666
+ by_category (bool): True for grouping in behavioral category else False
670
667
 
671
668
  Returns:
672
669
  list: results
673
670
  dict:
674
671
  """
675
672
 
673
+ logging.debug("time_budget_analysis function")
674
+
675
+ logging.debug(f"{selected_observations=}")
676
+ logging.debug(f"{parameters=}")
677
+
676
678
  categories: dict = {}
677
679
  out: list = []
678
680
  for subject in parameters[cfg.SELECTED_SUBJECTS]:
679
- out_cat, categories[subject] = [], {}
681
+ logging.debug(f"{subject=}")
682
+
683
+ out_cat: list = []
684
+ categories[subject] = {}
680
685
 
681
686
  for behavior in parameters[cfg.SELECTED_BEHAVIORS]:
687
+ logging.debug(f"{behavior=}")
682
688
 
683
689
  if parameters[cfg.INCLUDE_MODIFIERS]: # with modifiers
684
-
685
- cursor.execute(
686
- "SELECT DISTINCT modifiers FROM events WHERE subject = ? AND code = ?", (subject, behavior)
687
- )
688
- distinct_modifiers = list(cursor.fetchall())
690
+ if parameters[cfg.EXCLUDE_NON_CODED_MODIFIERS]:
691
+ # get coded modifiers
692
+ cursor.execute("SELECT DISTINCT modifiers FROM events WHERE subject = ? AND code = ?", (subject, behavior))
693
+ distinct_modifiers = [x[0] for x in cursor.fetchall()]
694
+ else:
695
+ # get all modifiers combinations
696
+ ms: list = []
697
+ modifiers_list = project_functions.get_modifiers_of_behavior(ethogram, behavior)
698
+ if modifiers_list:
699
+ for modif_set in modifiers_list[0]:
700
+ modif_set.append("None")
701
+ ms.append([re.sub(r" \(.*\)", "", x) for x in modif_set])
702
+ distinct_modifiers = ["|".join(x) for x in itertools.product(*ms)]
689
703
 
690
704
  if not distinct_modifiers:
691
705
  if not parameters[cfg.EXCLUDE_BEHAVIORS]:
692
-
693
- if cfg.STATE in project_functions.event_type(behavior, ethogram):
694
-
706
+ if project_functions.event_type(behavior, ethogram) in cfg.STATE_EVENT_TYPES:
695
707
  # check if observation from pictures
696
708
  if parameters["start time"] == dec("0.000") and parameters["end time"] == dec("0.000"):
697
709
  duration = cfg.NA
698
710
  else:
699
711
  duration = 0.000
700
712
 
701
- out.append(
713
+ out_cat.append(
702
714
  {
703
715
  "subject": subject,
704
716
  "behavior": behavior,
@@ -712,7 +724,7 @@ def time_budget_analysis(
712
724
  }
713
725
  )
714
726
  else: # point
715
- out.append(
727
+ out_cat.append(
716
728
  {
717
729
  "subject": subject,
718
730
  "behavior": behavior,
@@ -727,8 +739,7 @@ def time_budget_analysis(
727
739
  )
728
740
  continue
729
741
 
730
- if cfg.POINT in project_functions.event_type(behavior, ethogram):
731
-
742
+ if project_functions.event_type(behavior, ethogram) in cfg.POINT_EVENT_TYPES:
732
743
  for modifier in distinct_modifiers:
733
744
  cursor.execute(
734
745
  (
@@ -738,26 +749,24 @@ def time_budget_analysis(
738
749
  "AND modifiers = ? "
739
750
  "ORDER BY observation, occurence"
740
751
  ),
741
- (subject, behavior, modifier[0]),
752
+ (subject, behavior, modifier),
742
753
  )
743
754
 
744
755
  rows = cursor.fetchall()
745
756
 
746
757
  if len(selected_observations) == 1:
747
- new_rows = []
758
+ new_rows: list = []
748
759
  for occurence, observation in rows:
749
- if occurence is not None:
750
- new_occurence = max(float(parameters["start time"]), occurence)
751
- new_occurence = min(new_occurence, float(parameters["end time"]))
760
+ if occurence is None:
761
+ new_rows.append([float("NaN"), observation])
752
762
  else:
753
- new_occurence = float("NaN")
754
- new_rows.append([new_occurence, observation])
763
+ new_rows.append([occurence, observation])
755
764
  rows = list(new_rows)
756
765
 
757
766
  # include behaviors without events
758
767
  if len(rows) == 0:
759
768
  if not parameters[cfg.EXCLUDE_BEHAVIORS]:
760
- out.append(
769
+ out_cat.append(
761
770
  {
762
771
  "subject": subject,
763
772
  "behavior": behavior,
@@ -792,7 +801,7 @@ def time_budget_analysis(
792
801
  {
793
802
  "subject": subject,
794
803
  "behavior": behavior,
795
- "modifiers": modifier[0],
804
+ "modifiers": modifier,
796
805
  "duration": cfg.NA,
797
806
  "duration_mean": cfg.NA,
798
807
  "duration_stdev": cfg.NA,
@@ -802,8 +811,7 @@ def time_budget_analysis(
802
811
  }
803
812
  )
804
813
 
805
- if cfg.STATE in project_functions.event_type(behavior, ethogram):
806
-
814
+ if project_functions.event_type(behavior, ethogram) in cfg.STATE_EVENT_TYPES:
807
815
  for modifier in distinct_modifiers:
808
816
  cursor.execute(
809
817
  (
@@ -813,24 +821,23 @@ def time_budget_analysis(
813
821
  "AND modifiers = ? "
814
822
  "ORDER BY observation, occurence"
815
823
  ),
816
- (subject, behavior, modifier[0]),
824
+ (subject, behavior, modifier),
817
825
  )
818
826
 
819
827
  rows = list(cursor.fetchall())
820
828
 
821
829
  if len(rows) == 0:
822
-
823
830
  if not parameters[cfg.EXCLUDE_BEHAVIORS]: # include behaviors without events
824
831
  # check if observation from pictures
825
832
  if parameters["start time"] == dec("0.000") and parameters["end time"] == dec("0.000"):
826
833
  duration = cfg.NA
827
834
  else:
828
- duration = 0.000
829
- out.append(
835
+ duration: float = 0.000
836
+ out_cat.append(
830
837
  {
831
838
  "subject": subject,
832
839
  "behavior": behavior,
833
- "modifiers": modifier[0],
840
+ "modifiers": modifier,
834
841
  "duration": duration,
835
842
  "duration_mean": cfg.NA,
836
843
  "duration_stdev": cfg.NA,
@@ -842,11 +849,11 @@ def time_budget_analysis(
842
849
  continue
843
850
 
844
851
  if len(rows) % 2:
845
- out.append(
852
+ out_cat.append(
846
853
  {
847
854
  "subject": subject,
848
855
  "behavior": behavior,
849
- "modifiers": modifier[0],
856
+ "modifiers": modifier,
850
857
  "duration": cfg.UNPAIRED,
851
858
  "duration_mean": cfg.UNPAIRED,
852
859
  "duration_stdev": cfg.UNPAIRED,
@@ -856,7 +863,8 @@ def time_budget_analysis(
856
863
  }
857
864
  )
858
865
  else:
859
- all_event_durations, all_event_interdurations = [], []
866
+ all_event_durations: list = []
867
+ all_event_interdurations: list = []
860
868
  for idx, row in enumerate(rows):
861
869
  # event
862
870
  if idx % 2 == 0:
@@ -868,10 +876,11 @@ def time_budget_analysis(
868
876
 
869
877
  # inter event if same observation
870
878
  if idx % 2 and idx != len(rows) - 1 and row[1] == rows[idx + 1][1]:
871
- if (row[0] is not None and rows[idx + 1][0] is not None) and (
872
- parameters["start time"] <= row[0] <= parameters["end time"]
873
- and parameters["start time"] <= rows[idx + 1][0] <= parameters["end time"]
874
- ):
879
+ if row[0] is not None and rows[idx + 1][0] is not None:
880
+ # and (
881
+ # parameters["start time"] <= row[0] <= parameters["end time"]
882
+ # and parameters["start time"] <= rows[idx + 1][0] <= parameters["end time"]
883
+ # ):
875
884
  all_event_interdurations.append(float(rows[idx + 1][0]) - float(row[0]))
876
885
  else:
877
886
  all_event_interdurations.append(float("NaN"))
@@ -891,9 +900,7 @@ def time_budget_analysis(
891
900
  else:
892
901
  duration_stdev = cfg.NA
893
902
  # interduration
894
- if [x for x in all_event_interdurations if math.isnan(x)] or len(
895
- all_event_interdurations
896
- ) == 0:
903
+ if [x for x in all_event_interdurations if math.isnan(x)] or len(all_event_interdurations) == 0:
897
904
  inter_duration_mean = cfg.NA
898
905
  inter_duration_stdev = cfg.NA
899
906
  else:
@@ -907,7 +914,7 @@ def time_budget_analysis(
907
914
  {
908
915
  "subject": subject,
909
916
  "behavior": behavior,
910
- "modifiers": modifier[0],
917
+ "modifiers": modifier,
911
918
  "duration": total_duration,
912
919
  "duration_mean": duration_mean,
913
920
  "duration_stdev": duration_stdev,
@@ -918,34 +925,27 @@ def time_budget_analysis(
918
925
  )
919
926
 
920
927
  else: # no modifiers
921
-
922
- if cfg.POINT in project_functions.event_type(behavior, ethogram):
923
-
928
+ if project_functions.event_type(behavior, ethogram) in cfg.POINT_EVENT_TYPES:
924
929
  cursor.execute(
925
- (
926
- "SELECT occurence,observation FROM events "
927
- "WHERE subject = ? AND code = ? ORDER BY observation, occurence"
928
- ),
930
+ ("SELECT occurence, observation FROM events WHERE subject = ? AND code = ? ORDER BY observation, occurence"),
929
931
  (subject, behavior),
930
932
  )
931
933
 
932
934
  rows = list(cursor.fetchall())
933
935
 
934
936
  if len(selected_observations) == 1:
935
- new_rows = []
937
+ new_rows: list = []
936
938
  for occurence, observation in rows:
937
- if occurence is not None:
938
- new_occurence = max(float(parameters["start time"]), occurence)
939
- new_occurence = min(new_occurence, float(parameters["end time"]))
939
+ if occurence is None:
940
+ new_rows.append([float("NaN"), observation])
940
941
  else:
941
- new_occurence = float("NaN")
942
- new_rows.append([new_occurence, observation])
942
+ new_rows.append([occurence, observation])
943
943
  rows = list(new_rows)
944
944
 
945
945
  # include behaviors without events
946
946
  if len(rows) == 0:
947
947
  if not parameters[cfg.EXCLUDE_BEHAVIORS]:
948
- out.append(
948
+ out_cat.append(
949
949
  {
950
950
  "subject": subject,
951
951
  "behavior": behavior,
@@ -990,13 +990,9 @@ def time_budget_analysis(
990
990
  }
991
991
  )
992
992
 
993
- if cfg.STATE in project_functions.event_type(behavior, ethogram):
994
-
993
+ if project_functions.event_type(behavior, ethogram) in cfg.STATE_EVENT_TYPES:
995
994
  cursor.execute(
996
- (
997
- "SELECT occurence, observation FROM events "
998
- "WHERE subject = ? AND code = ? ORDER BY observation, occurence"
999
- ),
995
+ ("SELECT occurence, observation FROM events WHERE subject = ? AND code = ? ORDER BY observation, occurence"),
1000
996
  (subject, behavior),
1001
997
  )
1002
998
 
@@ -1009,7 +1005,7 @@ def time_budget_analysis(
1009
1005
  duration = cfg.NA
1010
1006
  else:
1011
1007
  duration = 0.000
1012
- out.append(
1008
+ out_cat.append(
1013
1009
  {
1014
1010
  "subject": subject,
1015
1011
  "behavior": behavior,
@@ -1024,8 +1020,8 @@ def time_budget_analysis(
1024
1020
  )
1025
1021
  continue
1026
1022
 
1027
- if len(rows) % 2:
1028
- out.append(
1023
+ if len(rows) % 2: # unpaired events
1024
+ out_cat.append(
1029
1025
  {
1030
1026
  "subject": subject,
1031
1027
  "behavior": behavior,
@@ -1039,7 +1035,8 @@ def time_budget_analysis(
1039
1035
  }
1040
1036
  )
1041
1037
  else:
1042
- all_event_durations, all_event_interdurations = [], []
1038
+ all_event_durations: list = []
1039
+ all_event_interdurations: list = []
1043
1040
  for idx, row in enumerate(rows):
1044
1041
  # event
1045
1042
  if idx % 2 == 0:
@@ -1051,10 +1048,11 @@ def time_budget_analysis(
1051
1048
 
1052
1049
  # inter event if same observation
1053
1050
  if idx % 2 and idx != len(rows) - 1 and row[1] == rows[idx + 1][1]:
1054
- if (row[0] is not None and rows[idx + 1][0] is not None) and (
1055
- parameters["start time"] <= row[0] <= parameters["end time"]
1056
- and parameters["start time"] <= rows[idx + 1][0] <= parameters["end time"]
1057
- ):
1051
+ if row[0] is not None and rows[idx + 1][0] is not None:
1052
+ # and (
1053
+ # parameters["start time"] <= row[0] <= parameters["end time"]
1054
+ # and parameters["start time"] <= rows[idx + 1][0] <= parameters["end time"]
1055
+ # ):
1058
1056
  all_event_interdurations.append(float(rows[idx + 1][0]) - float(row[0]))
1059
1057
  else:
1060
1058
  all_event_interdurations.append(float("NaN"))
@@ -1101,9 +1099,7 @@ def time_budget_analysis(
1101
1099
  out += out_cat
1102
1100
 
1103
1101
  if by_category: # and flagCategories:
1104
-
1105
1102
  for behav in out_cat:
1106
-
1107
1103
  try:
1108
1104
  category = [
1109
1105
  ethogram[x][cfg.BEHAVIOR_CATEGORY]
@@ -1113,19 +1109,23 @@ def time_budget_analysis(
1113
1109
  except Exception:
1114
1110
  category = ""
1115
1111
 
1116
- if category in categories[subject]:
1112
+ if category not in categories[subject]:
1113
+ categories[subject][category] = {"duration": 0, "number": 0}
1114
+
1115
+ if project_functions.event_type(behav["behavior"], ethogram) in cfg.STATE_EVENT_TYPES:
1117
1116
  if behav["duration"] not in ("-", cfg.NA) and categories[subject][category]["duration"] not in (
1118
1117
  "-",
1119
1118
  cfg.NA,
1120
1119
  ):
1120
+ # print(f"{categories[subject][category]["duration"]=}")
1121
+ # print(f"{behav["duration"]=}")
1121
1122
  categories[subject][category]["duration"] += behav["duration"]
1122
1123
  else:
1123
1124
  categories[subject][category]["duration"] = cfg.NA
1124
- categories[subject][category]["number"] += behav["number"]
1125
- else:
1126
- categories[subject][category] = {"duration": behav["duration"], "number": behav["number"]}
1127
1125
 
1128
- out_sorted = []
1126
+ categories[subject][category]["number"] += behav["number"]
1127
+
1128
+ out_sorted: list = []
1129
1129
  for subject in parameters[cfg.SELECTED_SUBJECTS]:
1130
1130
  for behavior in parameters[cfg.SELECTED_BEHAVIORS]:
1131
1131
  for row in out: