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
@@ -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
@@ -29,7 +29,7 @@ import math
29
29
  import pathlib
30
30
  from io import StringIO
31
31
  import pandas as pd
32
- from typing import List, Tuple, Dict
32
+ from typing import Tuple
33
33
 
34
34
  try:
35
35
  import pyreadr
@@ -44,6 +44,7 @@ from . import utilities as util
44
44
  from . import project_functions
45
45
  from . import observation_operations
46
46
  from . import db_functions
47
+ from . import event_operations
47
48
 
48
49
 
49
50
  def export_events_jwatcher(
@@ -65,7 +66,6 @@ def export_events_jwatcher(
65
66
  str: error message
66
67
  """
67
68
  for subject in parameters[cfg.SELECTED_SUBJECTS]:
68
-
69
69
  # select events for current subject
70
70
  events = []
71
71
  for event in observation[cfg.EVENTS]:
@@ -79,9 +79,7 @@ def export_events_jwatcher(
79
79
 
80
80
  total_length = 0 # in seconds
81
81
  if observation[cfg.EVENTS]:
82
- total_length = (
83
- observation[cfg.EVENTS][-1][0] - observation[cfg.EVENTS][0][0]
84
- ) # last event time - first event time
82
+ total_length = observation[cfg.EVENTS][-1][0] - observation[cfg.EVENTS][0][0] # last event time - first event time
85
83
 
86
84
  file_name_subject = str(pathlib.Path(file_name).parent / pathlib.Path(file_name).stem) + "_" + subject + ".dat"
87
85
 
@@ -119,14 +117,13 @@ def export_events_jwatcher(
119
117
  behav_code = event[cfg.EVENT_BEHAVIOR_FIELD_IDX]
120
118
 
121
119
  try:
122
- behavior_key = [
123
- ethogram[k][cfg.BEHAVIOR_KEY] for k in ethogram if ethogram[k][cfg.BEHAVIOR_CODE] == behav_code
124
- ][0]
120
+ behavior_key = [ethogram[k][cfg.BEHAVIOR_KEY] for k in ethogram if ethogram[k][cfg.BEHAVIOR_CODE] == behav_code][0]
125
121
  except Exception:
126
122
  # coded behavior not defined in ethogram
127
123
  continue
128
- if [ethogram[k][cfg.TYPE] for k in ethogram if ethogram[k][cfg.BEHAVIOR_CODE] == behav_code] == [
129
- cfg.STATE_EVENT
124
+ if [ethogram[k][cfg.TYPE] for k in ethogram if ethogram[k][cfg.BEHAVIOR_CODE] == behav_code] in [
125
+ [cfg.STATE_EVENT],
126
+ [cfg.STATE_EVENT_WITH_CODING_MAP],
130
127
  ]:
131
128
  if behav_code in mem_number_of_state_events:
132
129
  mem_number_of_state_events[behav_code] += 1
@@ -154,7 +151,7 @@ def export_events_jwatcher(
154
151
  if fmf_file_path.exists():
155
152
  fmf_creation_answer = dialog.MessageDialog(
156
153
  cfg.programName,
157
- (f"The {fmf_file_path} file already exists.<br>" "What do you want to do?"),
154
+ (f"The {fmf_file_path} file already exists.<br>What do you want to do?"),
158
155
  [cfg.OVERWRITE, "Skip file creation", cfg.CANCEL],
159
156
  )
160
157
 
@@ -167,11 +164,9 @@ def export_events_jwatcher(
167
164
  rows.append("# Format: Focal Master File 1.0")
168
165
  rows.append(f"# Updated: {dt.datetime.now().isoformat()}")
169
166
  rows.append("#-----------------------------------------------------------")
170
- for (behav, key) in all_observed_behaviors:
167
+ for behav, key in all_observed_behaviors:
171
168
  rows.append(f"Behaviour.name.{key}={behav}")
172
- behav_description = [
173
- ethogram[k][cfg.DESCRIPTION] for k in ethogram if ethogram[k][cfg.BEHAVIOR_CODE] == behav
174
- ][0]
169
+ behav_description = [ethogram[k][cfg.DESCRIPTION] for k in ethogram if ethogram[k][cfg.BEHAVIOR_CODE] == behav][0]
175
170
  rows.append(f"Behaviour.description.{key}={behav_description}")
176
171
 
177
172
  rows.append(f"DurationMilliseconds={int(float(total_length) * 1000)}")
@@ -198,7 +193,7 @@ def export_events_jwatcher(
198
193
  if faf_file_path.exists():
199
194
  faf_creation_answer = dialog.MessageDialog(
200
195
  cfg.programName,
201
- (f"The {faf_file_path} file already exists.<br>" "What do you want to do?"),
196
+ (f"The {faf_file_path} file already exists.<br>What do you want to do?"),
202
197
  [cfg.OVERWRITE, "Skip file creation", cfg.CANCEL],
203
198
  )
204
199
  if faf_creation_answer == cfg.CANCEL:
@@ -275,7 +270,7 @@ def export_events_jwatcher(
275
270
  rows.append("AllCodesMutuallyExclusive=true")
276
271
  rows.append("")
277
272
 
278
- for (behav, key) in all_observed_behaviors:
273
+ for behav, key in all_observed_behaviors:
279
274
  rows.append(f"Behavior.isModified.{key}=false")
280
275
  rows.append(f"Behavior.isSubtracted.{key}=false")
281
276
  rows.append(f"Behavior.isIgnored.{key}=false")
@@ -294,14 +289,14 @@ def export_events_jwatcher(
294
289
 
295
290
 
296
291
  def export_tabular_events(
297
- pj: dict, parameters: dict, obsId: str, observation: dict, ethogram: dict, file_name: str, output_format: str
292
+ pj: dict, parameters: dict, obs_id: str, observation: dict, ethogram: dict, file_name: str, output_format: str
298
293
  ) -> Tuple[bool, str]:
299
294
  """
300
- export events for one observation (obsId)
295
+ export events for one observation (obs_id)
301
296
 
302
297
  Args:
303
298
  parameters (dict): subjects, behaviors
304
- obsId (str): observation id
299
+ obs_id (str): observation id
305
300
  observation (dict): observation
306
301
  ethogram (dict): ethogram of project
307
302
  file_name (str): file name for exporting events
@@ -312,25 +307,21 @@ def export_tabular_events(
312
307
  str: error message
313
308
  """
314
309
 
315
- logging.debug(f"function: export tabular events for {obsId}")
310
+ logging.debug(f"function: export tabular events for {obs_id}")
316
311
  logging.debug(f"parameters: {parameters}")
317
312
 
318
313
  interval = parameters["time"]
319
314
 
320
- start_coding, end_coding, coding_duration = observation_operations.coding_time(pj[cfg.OBSERVATIONS], [obsId])
321
- """
322
- if start_coding is None and end_coding is None: # no events
323
- return data, 0
324
- """
315
+ start_coding, end_coding, coding_duration = observation_operations.coding_time(pj[cfg.OBSERVATIONS], [obs_id])
316
+ start_interval, end_interval = observation_operations.time_intervals_range(pj[cfg.OBSERVATIONS], [obs_id])
325
317
 
326
318
  if interval == cfg.TIME_EVENTS:
327
319
  min_time = start_coding
328
320
  max_time = end_coding
329
321
 
330
322
  if interval == cfg.TIME_FULL_OBS:
331
-
332
323
  if observation[cfg.TYPE] == cfg.MEDIA:
333
- max_media_duration, _ = observation_operations.media_duration(pj[cfg.OBSERVATIONS], [obsId])
324
+ max_media_duration, _ = observation_operations.media_duration(pj[cfg.OBSERVATIONS], [obs_id])
334
325
  min_time = dec("0")
335
326
  max_time = max_media_duration
336
327
  coding_duration = max_media_duration
@@ -343,13 +334,19 @@ def export_tabular_events(
343
334
  min_time = parameters[cfg.START_TIME]
344
335
  max_time = parameters[cfg.END_TIME]
345
336
 
337
+ if interval == cfg.TIME_OBS_INTERVAL:
338
+ max_media_duration, _ = observation_operations.media_duration(pj[cfg.OBSERVATIONS], [obs_id])
339
+ min_time = start_interval
340
+ # Use max media duration for max time if no interval is defined (=0)
341
+ max_time = end_interval if end_interval != 0 else max_media_duration
342
+
346
343
  logging.debug(f"min_time: {min_time} max_time: {max_time}")
347
344
 
348
- eventsWithStatus = project_functions.events_start_stop(ethogram, observation[cfg.EVENTS])
345
+ events_with_status = project_functions.events_start_stop(ethogram, observation[cfg.EVENTS], observation[cfg.TYPE])
349
346
 
350
347
  # check max number of modifiers
351
348
  max_modifiers = 0
352
- for event in eventsWithStatus:
349
+ for event in events_with_status:
353
350
  if not math.isnan(min_time) and not math.isnan(max_time): # obs not from pictures
354
351
  if min_time <= event[cfg.EVENT_TIME_FIELD_IDX] <= max_time:
355
352
  if event[cfg.EVENT_MODIFIER_FIELD_IDX]:
@@ -359,10 +356,7 @@ def export_tabular_events(
359
356
  max_modifiers = max(max_modifiers, len(event[cfg.EVENT_MODIFIER_FIELD_IDX].split("|")))
360
357
 
361
358
  # media file number
362
- mediaNb = 0
363
- if observation[cfg.TYPE] == cfg.MEDIA:
364
- for player in observation[cfg.FILE]:
365
- mediaNb += len(observation[cfg.FILE][player])
359
+ media_nb = util.count_media_file(observation[cfg.FILE])
366
360
 
367
361
  rows: list = []
368
362
 
@@ -374,9 +368,14 @@ def export_tabular_events(
374
368
  "Observation duration": float,
375
369
  "Observation type": str,
376
370
  "Source": str,
377
- "Media duration (s)": str,
378
- "FPS": float,
371
+ "Time offset (s)": str,
379
372
  }
373
+ if media_nb == 1:
374
+ fields_type["Media duration (s)"] = float
375
+ fields_type["FPS"] = float
376
+ else:
377
+ fields_type["Media duration (s)"] = str
378
+ fields_type["FPS"] = str
380
379
 
381
380
  # independent variables
382
381
  if cfg.INDEPENDENT_VARIABLES in observation:
@@ -414,16 +413,15 @@ def export_tabular_events(
414
413
  except KeyError:
415
414
  pass
416
415
 
417
- for event in eventsWithStatus:
416
+ for event in events_with_status:
418
417
  if (not math.isnan(min_time)) and not (min_time <= event[cfg.EVENT_TIME_FIELD_IDX] <= max_time):
419
418
  continue
420
419
  if (
421
420
  (event[cfg.EVENT_SUBJECT_FIELD_IDX] in parameters[cfg.SELECTED_SUBJECTS])
422
421
  or (event[cfg.EVENT_SUBJECT_FIELD_IDX] == "" and cfg.NO_FOCAL_SUBJECT in parameters[cfg.SELECTED_SUBJECTS])
423
422
  ) and (event[cfg.EVENT_BEHAVIOR_FIELD_IDX] in parameters[cfg.SELECTED_BEHAVIORS]):
424
-
425
423
  fields: list = []
426
- fields.append(obsId)
424
+ fields.append(obs_id)
427
425
  fields.append(observation.get("date", "").replace("T", " "))
428
426
  fields.append(util.eol2space(observation.get(cfg.DESCRIPTION, "")))
429
427
  # total length
@@ -439,9 +437,7 @@ def export_tabular_events(
439
437
  for media_file in observation[cfg.FILE][player]:
440
438
  media_file_lst.append(media_file)
441
439
  fps_lst.append(f"{observation[cfg.MEDIA_INFO][cfg.FPS].get(media_file, cfg.NA):.3f}")
442
- media_durations_lst.append(
443
- f"{observation[cfg.MEDIA_INFO][cfg.LENGTH].get(media_file, cfg.NA):.3f}"
444
- )
440
+ media_durations_lst.append(f"{observation[cfg.MEDIA_INFO][cfg.LENGTH].get(media_file, cfg.NA):.3f}")
445
441
  if player > "1":
446
442
  media_file_str += "|"
447
443
  fps_str += "|"
@@ -491,6 +487,9 @@ def export_tabular_events(
491
487
  else:
492
488
  fields.append("")
493
489
 
490
+ # time offset
491
+ fields.append(observation[cfg.TIME_OFFSET])
492
+
494
493
  # media duration
495
494
  fields.append(media_durations_str)
496
495
 
@@ -525,14 +524,11 @@ def export_tabular_events(
525
524
  fields.append(event[-1])
526
525
 
527
526
  # time
528
- # if event[cfg.EVENT_TIME_FIELD_IDX]
529
527
  fields.append(util.convertTime(time_format=cfg.S, sec=event[cfg.EVENT_TIME_FIELD_IDX]))
530
528
 
531
529
  # check video file name containing the event
532
530
  if observation[cfg.TYPE] == cfg.MEDIA:
533
- video_file_name = observation_operations.event2media_file_name(
534
- observation, event[cfg.EVENT_TIME_FIELD_IDX]
535
- )
531
+ video_file_name = observation_operations.event2media_file_name(observation, event[cfg.EVENT_TIME_FIELD_IDX])
536
532
  if video_file_name is None:
537
533
  video_file_name = "Not found"
538
534
 
@@ -540,292 +536,17 @@ def export_tabular_events(
540
536
  video_file_name = cfg.NA
541
537
  fields.append(video_file_name)
542
538
 
543
- # image file path
544
- if observation[cfg.TYPE] == cfg.IMAGES:
545
- fields.append(event[cfg.PJ_OBS_FIELDS[cfg.IMAGES][cfg.IMAGE_PATH]]) # image file path
546
- elif observation[cfg.TYPE] in (cfg.LIVE, cfg.MEDIA):
547
- fields.append(cfg.NA)
548
- else:
549
- fields.append("")
550
-
551
539
  # image file index
552
540
  if observation[cfg.TYPE] == cfg.IMAGES:
553
541
  fields.append(event[cfg.PJ_OBS_FIELDS[cfg.IMAGES][cfg.IMAGE_INDEX]]) # image file index
554
- elif observation[cfg.TYPE] in (cfg.LIVE, cfg.MEDIA):
555
- fields.append(cfg.NA)
556
- else:
557
- fields.append("")
558
-
559
- # comment
560
- fields.append(event[cfg.PJ_OBS_FIELDS[observation[cfg.TYPE]][cfg.COMMENT]].replace(os.linesep, " "))
561
-
562
- rows.append(fields)
563
-
564
- max_len = max([len(r) for r in rows])
565
- data = tablib.Dataset()
566
-
567
- data.title = util.safe_xl_worksheet_title(obsId, output_format)
568
-
569
- for row in rows:
570
- data.append(util.complete(row, max_len))
571
-
572
- r, msg = dataset_write(data, file_name, output_format, dtype=fields_type)
573
-
574
- return r, msg
575
-
576
-
577
- '''
578
- def export_tabular_events_old(
579
- parameters: dict, obsId: str, observation: dict, ethogram: dict, file_name: str, output_format: str
580
- ) -> Tuple[bool, str]:
581
- """
582
- export events for one observation (obsId)
583
-
584
- Args:
585
- parameters (dict): subjects, behaviors
586
- obsId (str): observation id
587
- observation (dict): observation
588
- ethogram (dict): ethogram of project
589
- file_name (str): file name for exporting events
590
- output_format (str): output for exporting events
591
-
592
- Returns:
593
- bool: result: True if OK else False
594
- str: error message
595
- """
596
-
597
- logging.debug(f"function: export tabular events for {obsId} parameters: {parameters} ")
598
-
599
- interval = parameters["time"]
600
- start_time = parameters[cfg.START_TIME]
601
- end_time = parameters[cfg.END_TIME]
602
-
603
- total_length = observation_operations.observation_total_length(observation)
604
-
605
- logging.debug(f"total_length: {total_length}")
606
-
607
- if total_length == dec(-1): # media length not available
608
- interval = cfg.TIME_EVENTS
609
-
610
- if total_length == dec(-2): # obs without timestamp
611
- interval = cfg.TIME_EVENTS
612
-
613
- if interval == cfg.TIME_FULL_OBS:
614
- min_time = float(0)
615
- max_time = float(total_length)
616
- total_length_str = f"{max_time - min_time:.3f}"
617
-
618
- if interval == cfg.TIME_EVENTS:
619
- start_coding, end_coding, _ = observation_operations.coding_time({obsId: observation}, [obsId])
620
- if start_coding.is_nan():
621
- total_length_str = cfg.NA
622
- else:
623
- min_time = float(start_coding)
624
- max_time = float(end_coding)
625
- total_length_str = f"{max_time - min_time:.3f}"
626
- """
627
- try:
628
- min_time = float(observation[cfg.EVENTS][0][0])
629
- except Exception:
630
- min_time = float(0)
631
- try:
632
- max_time = float(observation[cfg.EVENTS][-1][0])
633
- except Exception:
634
- max_time = float(total_length)
635
- """
636
-
637
- if interval == cfg.TIME_ARBITRARY_INTERVAL:
638
- min_time = float(start_time)
639
- max_time = float(end_time)
640
- total_length_str = f"{max_time - min_time:.3f}"
641
-
642
- """
643
- if total_length == -2: # obs from pictures
644
- total_length_str = cfg.NA
645
- else:
646
- total_length_str = f"{parameters[cfg.END_TIME] - parameters[cfg.START_TIME]:.3f}"
647
- """
648
-
649
- eventsWithStatus = project_functions.events_start_stop(ethogram, observation[cfg.EVENTS])
650
-
651
- # check max number of modifiers
652
- max_modifiers = 0
653
- for event in eventsWithStatus:
654
- if total_length != dec(-2): # obs not from pictures
655
- if min_time <= event[cfg.EVENT_TIME_FIELD_IDX] <= max_time:
656
- if event[cfg.EVENT_MODIFIER_FIELD_IDX]:
657
- max_modifiers = max(max_modifiers, len(event[cfg.EVENT_MODIFIER_FIELD_IDX].split("|")))
658
- else:
659
- if event[cfg.EVENT_MODIFIER_FIELD_IDX]:
660
- max_modifiers = max(max_modifiers, len(event[cfg.EVENT_MODIFIER_FIELD_IDX].split("|")))
661
-
662
- # media file number
663
- mediaNb = 0
664
- if observation[cfg.TYPE] == cfg.MEDIA:
665
- for player in observation[cfg.FILE]:
666
- mediaNb += len(observation[cfg.FILE][player])
667
-
668
- rows: list = []
669
-
670
- # fields and type
671
- fields_type: dict = {
672
- "Observation id": str,
673
- "Observation date": dt.datetime,
674
- "Description": str,
675
- "Observation duration": float,
676
- "Observation type": str,
677
- "Source": str,
678
- "FPS": float,
679
- }
680
-
681
- # independent variables
682
- if cfg.INDEPENDENT_VARIABLES in observation:
683
- for variable in observation[cfg.INDEPENDENT_VARIABLES]:
684
- # TODO check variable type
685
- fields_type[variable] = str
686
-
687
- fields_type.update({"Subject": str, "Behavior": str, "Behavioral category": str})
688
-
689
- # modifiers
690
- for idx in range(max_modifiers):
691
- fields_type[f"Modifier #{idx + 1}"] = str
692
-
693
- fields_type.update(
694
- {
695
- "Behavior type": str,
696
- "Time": float,
697
- "Media file name": str,
698
- "Image index": float, # add image index and image file path to header
699
- "Image file path": str,
700
- "Comment": str,
701
- }
702
- )
703
-
704
- # add header
705
- rows.append(list(fields_type.keys()))
706
-
707
- behavioral_category = project_functions.behavior_category(ethogram)
708
-
709
- duration1 = [] # in seconds
710
- if observation[cfg.TYPE] == cfg.MEDIA:
711
- try:
712
- for mediaFile in observation[cfg.FILE][cfg.PLAYER1]:
713
- duration1.append(observation[cfg.MEDIA_INFO][cfg.LENGTH][mediaFile])
714
- except KeyError:
715
- pass
716
-
717
- for event in eventsWithStatus:
718
- if (total_length != dec(-2)) and not (min_time <= event[cfg.EVENT_TIME_FIELD_IDX] <= max_time):
719
- continue
720
- if (
721
- (event[cfg.EVENT_SUBJECT_FIELD_IDX] in parameters[cfg.SELECTED_SUBJECTS])
722
- or (event[cfg.EVENT_SUBJECT_FIELD_IDX] == "" and cfg.NO_FOCAL_SUBJECT in parameters[cfg.SELECTED_SUBJECTS])
723
- ) and (event[cfg.EVENT_BEHAVIOR_FIELD_IDX] in parameters[cfg.SELECTED_BEHAVIORS]):
724
-
725
- fields: list = []
726
- fields.append(obsId)
727
- fields.append(observation.get("date", "").replace("T", " "))
728
- fields.append(util.eol2space(observation.get("description", "")))
729
- # total length
730
- fields.append(total_length_str)
731
-
732
- if observation[cfg.TYPE] == cfg.MEDIA:
733
- fields.append("Media file(s)")
734
-
735
- media_file_str, fps_str = "", ""
736
- # number of players
737
- n_players = len([x for x in observation[cfg.FILE] if observation[cfg.FILE][x]])
738
- for player in observation[cfg.FILE]:
739
- if observation[cfg.FILE][player]:
740
- if media_file_str:
741
- media_file_str += " "
742
- if fps_str:
743
- fps_str += " "
744
- if n_players > 1:
745
- media_file_str += f"player #{player}: "
746
- fps_str += f"player #{player}: "
747
- media_list, fps_list = [], []
748
- for media_file in observation[cfg.FILE][player]:
749
- media_list.append(media_file)
750
- fps_list.append(f"{observation[cfg.MEDIA_INFO][cfg.FPS].get(media_file, cfg.NA):.3f}")
751
- media_file_str += ";".join(media_list)
752
- fps_str += ";".join(fps_list)
753
-
754
- fields.append(media_file_str)
755
-
542
+ elif observation[cfg.TYPE] == cfg.MEDIA:
543
+ frame_idx = event_operations.read_event_field(event, cfg.MEDIA, cfg.FRAME_INDEX)
544
+ fields.append(frame_idx) # frame index
756
545
  elif observation[cfg.TYPE] == cfg.LIVE:
757
- fields.append("Live observation")
758
546
  fields.append(cfg.NA)
759
- fps_str = cfg.NA
760
-
761
- elif observation[cfg.TYPE] == cfg.IMAGES:
762
- fields.append("From directories of images")
763
- dir_list = []
764
- for dir in observation[cfg.DIRECTORIES_LIST]:
765
- dir_list.append(dir)
766
- fields.append(";".join(dir_list))
767
- fps_str = cfg.NA
768
-
769
547
  else:
770
548
  fields.append("")
771
549
 
772
- # FPS
773
- fields.append(fps_str)
774
-
775
- # indep var
776
- if cfg.INDEPENDENT_VARIABLES in observation:
777
- for variable in observation[cfg.INDEPENDENT_VARIABLES]:
778
- fields.append(observation[cfg.INDEPENDENT_VARIABLES][variable])
779
-
780
- fields.append(event[cfg.PJ_OBS_FIELDS[observation[cfg.TYPE]][cfg.SUBJECT]])
781
- fields.append(event[cfg.PJ_OBS_FIELDS[observation[cfg.TYPE]][cfg.BEHAVIOR_CODE]])
782
-
783
- # behavioral category
784
- try:
785
- behav_category = behavioral_category[event[cfg.PJ_OBS_FIELDS[observation[cfg.TYPE]][cfg.BEHAVIOR_CODE]]]
786
- except Exception:
787
- behav_category = ""
788
- fields.append(behav_category)
789
-
790
- # modifiers
791
- if max_modifiers:
792
- modifiers = event[cfg.PJ_OBS_FIELDS[observation[cfg.TYPE]][cfg.MODIFIER]].split("|")
793
- while len(modifiers) < max_modifiers:
794
- modifiers.append("")
795
-
796
- for m in modifiers:
797
- fields.append(m)
798
-
799
- # status (START/STOP)
800
- fields.append(event[-1])
801
-
802
- # time
803
- # if event[cfg.EVENT_TIME_FIELD_IDX]
804
- fields.append(util.convertTime(time_format=cfg.S, sec=event[cfg.EVENT_TIME_FIELD_IDX]))
805
-
806
- # check video file name containing the event
807
- if observation[cfg.TYPE] == cfg.MEDIA:
808
- cumul_media_durations = [0]
809
- for media_file in observation[cfg.FILE]["1"]:
810
- media_duration = observation[cfg.MEDIA_INFO][cfg.LENGTH][media_file]
811
- cumul_media_durations.append(cumul_media_durations[-1] + media_duration)
812
-
813
- player_idx_list = [
814
- idx
815
- for idx, x in enumerate(cumul_media_durations)
816
- if cumul_media_durations[idx - 1] < event[cfg.EVENT_TIME_FIELD_IDX] <= x
817
- ]
818
- if len(player_idx_list):
819
- player_idx = player_idx_list[0] - 1
820
- video_file_name = observation[cfg.FILE]["1"][player_idx]
821
- else:
822
- player_idx = -1
823
- video_file_name = "Not found"
824
-
825
- elif observation[cfg.TYPE] in (cfg.LIVE, cfg.IMAGES):
826
- video_file_name = cfg.NA
827
- fields.append(video_file_name)
828
-
829
550
  # image file path
830
551
  if observation[cfg.TYPE] == cfg.IMAGES:
831
552
  fields.append(event[cfg.PJ_OBS_FIELDS[cfg.IMAGES][cfg.IMAGE_PATH]]) # image file path
@@ -834,14 +555,6 @@ def export_tabular_events_old(
834
555
  else:
835
556
  fields.append("")
836
557
 
837
- # image file index
838
- if observation[cfg.TYPE] == cfg.IMAGES:
839
- fields.append(event[cfg.PJ_OBS_FIELDS[cfg.IMAGES][cfg.IMAGE_INDEX]]) # image file index
840
- elif observation[cfg.TYPE] in (cfg.LIVE, cfg.MEDIA):
841
- fields.append(cfg.NA)
842
- else:
843
- fields.append("")
844
-
845
558
  # comment
846
559
  fields.append(event[cfg.PJ_OBS_FIELDS[observation[cfg.TYPE]][cfg.COMMENT]].replace(os.linesep, " "))
847
560
 
@@ -850,7 +563,7 @@ def export_tabular_events_old(
850
563
  max_len = max([len(r) for r in rows])
851
564
  data = tablib.Dataset()
852
565
 
853
- data.title = util.safe_xl_worksheet_title(obsId, output_format)
566
+ data.title = util.safe_xl_worksheet_title(obs_id, output_format)
854
567
 
855
568
  for row in rows:
856
569
  data.append(util.complete(row, max_len))
@@ -858,213 +571,9 @@ def export_tabular_events_old(
858
571
  r, msg = dataset_write(data, file_name, output_format, dtype=fields_type)
859
572
 
860
573
  return r, msg
861
- '''
862
-
863
- '''
864
- def export_tabular_events_long_format(
865
- parameters, obsId: str, observation: dict, ethogram: dict, file_name: str, output_format: str
866
- ) -> tuple: # -> tuple[bool, str]:
867
- """
868
- export events
869
-
870
- Args:
871
- parameters (dict): subjects, behaviors
872
- obsId (str): observation id
873
- observation (dict): observation
874
- ethogram (dict): ethogram of project
875
- file_name (str): file name for exporting events
876
- output_format (str): output for exporting events
877
-
878
- Returns:
879
- bool: result: True if OK else False
880
- str: error message
881
- """
882
-
883
- total_length = f"{observation_operations.observation_total_length(observation):.3f}"
884
-
885
- eventsWithStatus = project_functions.events_start_stop(ethogram, observation[cfg.EVENTS])
886
-
887
- # check max number of modifiers
888
- max_modifiers = 0
889
- for event in eventsWithStatus:
890
- if event[cfg.EVENT_MODIFIER_FIELD_IDX]:
891
- max_modifiers = max(max_modifiers, len(event[cfg.EVENT_MODIFIER_FIELD_IDX].split("|")))
892
-
893
- # media file number
894
- mediaNb = 0
895
- if observation[cfg.TYPE] == cfg.MEDIA:
896
- for player in observation[cfg.FILE]:
897
- mediaNb += len(observation[cfg.FILE][player])
898
-
899
- rows = []
900
-
901
- # observation id
902
- rows.append(["Observation id", obsId])
903
- rows.append([""])
904
-
905
- # media file name
906
- if observation[cfg.TYPE] == cfg.MEDIA:
907
- rows.append(["Media file(s)"])
908
- elif observation[cfg.TYPE] == cfg.LIVE:
909
- rows.append(["Live observation"])
910
- elif observation[cfg.TYPE] == cfg.IMAGES:
911
- rows.append(["From directories of images"])
912
- else:
913
- rows.append([""])
914
- rows.append([""])
915
-
916
- if observation[cfg.TYPE] == cfg.MEDIA:
917
- for player in sorted(list(observation[cfg.FILE].keys())):
918
- for media in observation[cfg.FILE][player]:
919
- rows.append([f"Player #{player}", media])
920
-
921
- if observation[cfg.TYPE] == cfg.IMAGES:
922
- for dir in observation[cfg.DIRECTORIES_LIST]:
923
- rows.append([f"Directory", dir])
924
-
925
- rows.append([""])
926
-
927
- # date
928
- rows.append(["Observation date", observation.get("date", "").replace("T", " ")])
929
- rows.append([""])
930
-
931
- # description
932
- rows.append(["Description", util.eol2space(observation.get("description", ""))])
933
- rows.append([""])
934
-
935
- # time offset
936
- rows.append(["Time offset (s)", observation.get(cfg.TIME_OFFSET, 0)])
937
- rows.append([""])
938
-
939
- # independent variables
940
- if cfg.INDEPENDENT_VARIABLES in observation:
941
- rows.extend([["independent variables"], ["variable", "value"]])
942
-
943
- for variable in observation[cfg.INDEPENDENT_VARIABLES]:
944
- rows.append([variable, observation[cfg.INDEPENDENT_VARIABLES][variable]])
945
-
946
- rows.append([""])
947
-
948
- # write table header
949
- col = 0
950
-
951
- header = ["Time"]
952
- if observation[cfg.TYPE] == cfg.MEDIA:
953
- header.extend(["Media file path", "Total length", "FPS"])
954
- if observation[cfg.TYPE] == cfg.IMAGES:
955
- header.extend(
956
- [
957
- "Image file path",
958
- "Image index",
959
- ]
960
- )
961
- if observation[cfg.TYPE] == cfg.LIVE:
962
- header.extend(
963
- [
964
- "Total length",
965
- ]
966
- )
967
-
968
- header.extend(["Subject", "Behavior", "Behavioral category"])
969
-
970
- behavioral_category = project_functions.behavior_category(ethogram)
971
-
972
- for x in range(1, max_modifiers + 1):
973
- header.append(f"Modifier {x}")
974
- header.extend(["Comment", "Status"])
975
-
976
- rows.append(header)
977
-
978
- duration1 = [] # in seconds
979
- if observation[cfg.TYPE] == cfg.MEDIA:
980
- try:
981
- for mediaFile in observation[cfg.FILE][cfg.PLAYER1]:
982
- duration1.append(observation[cfg.MEDIA_INFO][cfg.LENGTH][mediaFile])
983
- except KeyError:
984
- pass
985
-
986
- for event in eventsWithStatus:
987
- if (
988
- (event[cfg.EVENT_SUBJECT_FIELD_IDX] in parameters[cfg.SELECTED_SUBJECTS])
989
- or (event[cfg.EVENT_SUBJECT_FIELD_IDX] == "" and cfg.NO_FOCAL_SUBJECT in parameters[cfg.SELECTED_SUBJECTS])
990
- ) and (event[cfg.EVENT_BEHAVIOR_FIELD_IDX] in parameters[cfg.SELECTED_BEHAVIORS]):
991
-
992
- fields = []
993
- fields.append(util.intfloatstr(str(event[cfg.EVENT_TIME_FIELD_IDX])))
994
-
995
- if observation[cfg.TYPE] == cfg.MEDIA:
996
-
997
- time_ = event[cfg.EVENT_TIME_FIELD_IDX] - observation[cfg.TIME_OFFSET]
998
- if time_ < 0:
999
- time_ = 0
1000
-
1001
- if duration1:
1002
- mediaFileIdx = [idx1 for idx1, x in enumerate(duration1) if time_ >= sum(duration1[0:idx1])][-1]
1003
- fields.append(observation[cfg.FILE][cfg.PLAYER1][mediaFileIdx])
1004
- fields.append(total_length)
1005
- # FPS
1006
- try:
1007
- fields.append(
1008
- observation[cfg.MEDIA_INFO][cfg.FPS][observation[cfg.FILE][cfg.PLAYER1][mediaFileIdx]]
1009
- ) # fps
1010
- except KeyError:
1011
- fields.append(cfg.NA)
1012
- else:
1013
- fields.append(cfg.NA) # media file
1014
- fields.append(cfg.NA) # FPS
1015
-
1016
- if observation[cfg.TYPE] == cfg.LIVE:
1017
- fields.append(total_length) # total length
1018
-
1019
- if observation[cfg.TYPE] == cfg.IMAGES:
1020
- fields.append(event[cfg.PJ_OBS_FIELDS[cfg.IMAGES][cfg.IMAGE_PATH]]) # image file path
1021
- fields.append(event[cfg.PJ_OBS_FIELDS[cfg.IMAGES][cfg.IMAGE_INDEX]]) # image file index
1022
-
1023
- fields.append(event[cfg.PJ_OBS_FIELDS[observation[cfg.TYPE]][cfg.SUBJECT]])
1024
- fields.append(event[cfg.PJ_OBS_FIELDS[observation[cfg.TYPE]][cfg.BEHAVIOR_CODE]])
1025
-
1026
- # behavioral category
1027
-
1028
- try:
1029
- behav_category = behavioral_category[event[cfg.PJ_OBS_FIELDS[observation[cfg.TYPE]][cfg.BEHAVIOR_CODE]]]
1030
- except Exception:
1031
- behav_category = ""
1032
- fields.append(behav_category)
1033
-
1034
- # modifiers
1035
- if max_modifiers:
1036
- modifiers = event[cfg.PJ_OBS_FIELDS[observation[cfg.TYPE]][cfg.MODIFIER]].split("|")
1037
- while len(modifiers) < max_modifiers:
1038
- modifiers.append("")
1039
-
1040
- for m in modifiers:
1041
- fields.append(m)
1042
-
1043
- # comment
1044
- fields.append(event[cfg.PJ_OBS_FIELDS[observation[cfg.TYPE]][cfg.COMMENT]].replace(os.linesep, " "))
1045
- # status
1046
- fields.append(event[-1])
1047
-
1048
- rows.append(fields)
1049
-
1050
- maxLen = max([len(r) for r in rows])
1051
- data = tablib.Dataset()
1052
-
1053
- data.title = util.safe_xl_worksheet_title(obsId, output_format)
1054
-
1055
- for row in rows:
1056
- data.append(util.complete(row, maxLen))
1057
-
1058
- r, msg = dataset_write(data, file_name, output_format)
1059
-
1060
- return r, msg
1061
-
1062
- '''
1063
574
 
1064
575
 
1065
- def dataset_write(
1066
- dataset: tablib.Dataset, file_name: str, output_format: str, dtype: dict = {}
1067
- ) -> tuple: # -> tuple[bool, str]:
576
+ def dataset_write(dataset: tablib.Dataset, file_name: str, output_format: str, dtype: dict = {}) -> tuple: # -> tuple[bool, str]:
1068
577
  """
1069
578
  write a tablib dataset with aggregated events or tabular events to file in specified format (output_format)
1070
579
 
@@ -1072,6 +581,7 @@ def dataset_write(
1072
581
  dataset (tablib.dataset): dataset to write
1073
582
  file_name (str): file name
1074
583
  output_format (str): format of output
584
+ dtype (dict): type of field
1075
585
 
1076
586
  Returns:
1077
587
  bool: result. True if OK else False
@@ -1081,11 +591,9 @@ def dataset_write(
1081
591
  logging.debug("function: dataset_write")
1082
592
 
1083
593
  try:
1084
-
1085
- if output_format in ("pkl", "rds"):
1086
-
594
+ if output_format in (cfg.PANDAS_DF_EXT, cfg.RDS_EXT):
1087
595
  # build pandas dataframe from the tsv export of tablib dataset
1088
- date_type = []
596
+ date_type: list = []
1089
597
  for field_name in dtype:
1090
598
  if dtype[field_name] == dt.datetime:
1091
599
  date_type.append(field_name)
@@ -1094,27 +602,26 @@ def dataset_write(
1094
602
  del dtype[field_name]
1095
603
 
1096
604
  df = pd.read_csv(
1097
- StringIO(dataset.export("tsv")),
605
+ StringIO(dataset.export(cfg.TSV_EXT)),
1098
606
  sep="\t",
1099
607
  dtype=dtype,
1100
608
  parse_dates=date_type,
1101
609
  )
1102
610
 
1103
- if output_format == "pkl":
611
+ if output_format == cfg.PANDAS_DF_EXT:
1104
612
  df.to_pickle(file_name)
1105
613
 
1106
- if flag_pyreadr_loaded and output_format == "rds":
614
+ if output_format == cfg.RDS_EXT and flag_pyreadr_loaded:
1107
615
  pyreadr.write_rds(file_name, df)
1108
616
 
1109
617
  return True, ""
1110
618
 
1111
- if output_format in ("csv", "tsv", "html"):
619
+ if output_format in (cfg.CSV_EXT, cfg.TSV_EXT, cfg.HTML_EXT):
1112
620
  with open(file_name, "wb") as f:
1113
621
  f.write(str.encode(dataset.export(output_format)))
1114
622
  return True, ""
1115
623
 
1116
- if output_format in ("ods", "xls", "xlsx"):
1117
-
624
+ if output_format in (cfg.ODS_EXT, cfg.XLS_EXT, cfg.XLSX_EXT):
1118
625
  dataset.title = util.safe_xl_worksheet_title(dataset.title, output_format)
1119
626
 
1120
627
  with open(file_name, "wb") as f:
@@ -1127,7 +634,7 @@ def dataset_write(
1127
634
  return False, str(sys.exc_info()[1])
1128
635
 
1129
636
 
1130
- def export_aggregated_events(pj: dict, parameters: dict, obsId: str) -> Tuple[tablib.Dataset, int]:
637
+ def export_aggregated_events(pj: dict, parameters: dict, obsId: str, force_number_modifiers: int = 0) -> Tuple[tablib.Dataset, int]:
1131
638
  """
1132
639
  export aggregated events of one observation
1133
640
 
@@ -1135,6 +642,7 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str) -> Tuple[ta
1135
642
  pj (dict): BORIS project
1136
643
  parameters (dict): subjects, behaviors
1137
644
  obsId (str): observation id
645
+ force_number_modifiers (int): force the number of modifiers to return
1138
646
 
1139
647
  Returns:
1140
648
  tablib.Dataset:
@@ -1149,6 +657,8 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str) -> Tuple[ta
1149
657
  data = tablib.Dataset()
1150
658
 
1151
659
  start_coding, end_coding, coding_duration = observation_operations.coding_time(pj[cfg.OBSERVATIONS], [obsId])
660
+ start_interval, end_interval = observation_operations.time_intervals_range(pj[cfg.OBSERVATIONS], [obsId])
661
+
1152
662
  if start_coding is None and end_coding is None: # no events
1153
663
  return data, 0
1154
664
 
@@ -1170,21 +680,22 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str) -> Tuple[ta
1170
680
  min_time = float(parameters[cfg.START_TIME])
1171
681
  max_time = float(parameters[cfg.END_TIME])
1172
682
 
683
+ if interval == cfg.TIME_OBS_INTERVAL:
684
+ max_media_duration, _ = observation_operations.media_duration(pj[cfg.OBSERVATIONS], [obsId])
685
+ min_time = float(start_interval)
686
+ # Use max media duration for max time if no interval is defined (=0)
687
+ max_time = float(end_interval) if end_interval != 0 else float(max_media_duration)
688
+
1173
689
  logging.debug(f"min_time: {min_time} max_time: {max_time}")
1174
690
 
1175
691
  # obs description
1176
692
  obs_description = util.eol2space(observation[cfg.DESCRIPTION])
1177
693
 
1178
- """
1179
- obs_length = observation_operations.observation_total_length(pj[cfg.OBSERVATIONS][obsId])
1180
- logging.debug(f"obs_length: {obs_length}")
1181
- """
1182
-
1183
694
  _, _, connector = db_functions.load_aggregated_events_in_db(
1184
695
  pj, parameters[cfg.SELECTED_SUBJECTS], [obsId], parameters[cfg.SELECTED_BEHAVIORS]
1185
696
  )
1186
697
  if connector is None:
1187
- logging.critical(f"error when loading aggregated events in DB")
698
+ logging.critical("error when loading aggregated events in DB")
1188
699
  return data, 0
1189
700
 
1190
701
  cursor = connector.cursor()
@@ -1238,13 +749,16 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str) -> Tuple[ta
1238
749
  behavioral_category = project_functions.behavior_category(pj[cfg.ETHOGRAM])
1239
750
 
1240
751
  cursor.execute("SELECT DISTINCT modifiers FROM aggregated_events")
1241
- max_modifiers = 0
1242
- for row in cursor.fetchall():
1243
- if row["modifiers"]:
1244
- max_modifiers = max(max_modifiers, row["modifiers"].count("|") + 1)
1245
752
 
1246
- for subject in parameters[cfg.SELECTED_SUBJECTS]:
753
+ if not force_number_modifiers:
754
+ max_modifiers: int = 0
755
+ for row in cursor.fetchall():
756
+ if row["modifiers"]:
757
+ max_modifiers = max(max_modifiers, row["modifiers"].count("|") + 1)
758
+ else:
759
+ max_modifiers = force_number_modifiers
1247
760
 
761
+ for subject in parameters[cfg.SELECTED_SUBJECTS]:
1248
762
  # calculate observation duration by subject (by obs)
1249
763
  cursor.execute(("SELECT SUM(stop - start) AS duration FROM aggregated_events WHERE subject = ? "), (subject,))
1250
764
  duration_by_subject_by_obs = cursor.fetchone()["duration"]
@@ -1252,7 +766,6 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str) -> Tuple[ta
1252
766
  duration_by_subject_by_obs = round(duration_by_subject_by_obs, 3)
1253
767
 
1254
768
  for behavior in parameters[cfg.SELECTED_BEHAVIORS]:
1255
-
1256
769
  cursor.execute(
1257
770
  "SELECT DISTINCT modifiers FROM aggregated_events WHERE subject=? AND behavior=? ORDER BY modifiers",
1258
771
  (
@@ -1264,7 +777,6 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str) -> Tuple[ta
1264
777
  rows_distinct_modifiers = list(x[0] for x in cursor.fetchall())
1265
778
 
1266
779
  for distinct_modifiers in rows_distinct_modifiers:
1267
-
1268
780
  cursor.execute(
1269
781
  (
1270
782
  "SELECT start, stop, type, modifiers, comment, comment_stop, "
@@ -1276,12 +788,12 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str) -> Tuple[ta
1276
788
  )
1277
789
 
1278
790
  for row in cursor.fetchall():
1279
-
1280
791
  media_file_name = cfg.NA
1281
792
 
1282
793
  if observation[cfg.TYPE] == cfg.MEDIA:
1283
794
  observation_type = "Media file"
1284
795
 
796
+ # get the media file name of the start of event
1285
797
  media_file_name = observation_operations.event2media_file_name(observation, row["start"])
1286
798
  if media_file_name is None:
1287
799
  media_file_name = "Not found"
@@ -1293,12 +805,8 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str) -> Tuple[ta
1293
805
  if observation[cfg.FILE][player]:
1294
806
  for media_file in observation[cfg.FILE][player]:
1295
807
  media_file_lst.append(media_file)
1296
- fps_lst.append(
1297
- f"{observation[cfg.MEDIA_INFO][cfg.FPS].get(media_file, cfg.NA):.3f}"
1298
- )
1299
- media_durations_lst.append(
1300
- f"{observation[cfg.MEDIA_INFO][cfg.LENGTH].get(media_file, cfg.NA):.3f}"
1301
- )
808
+ fps_lst.append(f"{observation[cfg.MEDIA_INFO][cfg.FPS].get(media_file, cfg.NA):.3f}")
809
+ media_durations_lst.append(f"{observation[cfg.MEDIA_INFO][cfg.LENGTH].get(media_file, cfg.NA):.3f}")
1302
810
  if player > "1":
1303
811
  media_file_str += "|"
1304
812
  fps_str += "|"
@@ -1330,7 +838,8 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str) -> Tuple[ta
1330
838
  observation["date"].replace("T", " "),
1331
839
  obs_description,
1332
840
  observation_type,
1333
- media_file_str,
841
+ media_file_str, # list of media used in observation
842
+ pj[cfg.OBSERVATIONS][obsId][cfg.TIME_OFFSET],
1334
843
  f"{coding_duration:.3f}" if not coding_duration.is_nan() else cfg.NA,
1335
844
  media_durations_str,
1336
845
  fps_str,
@@ -1340,13 +849,8 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str) -> Tuple[ta
1340
849
  # independent variables
1341
850
  if cfg.INDEPENDENT_VARIABLES in pj:
1342
851
  for idx_var in util.sorted_keys(pj[cfg.INDEPENDENT_VARIABLES]):
1343
- if (
1344
- pj[cfg.INDEPENDENT_VARIABLES][idx_var]["label"]
1345
- in observation[cfg.INDEPENDENT_VARIABLES]
1346
- ):
1347
- var_value = observation[cfg.INDEPENDENT_VARIABLES][
1348
- pj[cfg.INDEPENDENT_VARIABLES][idx_var]["label"]
1349
- ]
852
+ if pj[cfg.INDEPENDENT_VARIABLES][idx_var]["label"] in observation[cfg.INDEPENDENT_VARIABLES]:
853
+ var_value = observation[cfg.INDEPENDENT_VARIABLES][pj[cfg.INDEPENDENT_VARIABLES][idx_var]["label"]]
1350
854
  if pj[cfg.INDEPENDENT_VARIABLES][idx_var]["type"] == "timestamp":
1351
855
  var_value = var_value.replace("T", " ")
1352
856
 
@@ -1389,9 +893,7 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str) -> Tuple[ta
1389
893
  f"{row['start']:.3f}" if row["start"] is not None else cfg.NA,
1390
894
  f"{row['stop']:.3f}" if row["stop"] is not None else cfg.NA,
1391
895
  # duration
1392
- f"{row['stop'] - row['start']:.3f}"
1393
- if (row["stop"] is not None) and (row["start"] is not None)
1394
- else cfg.NA,
896
+ f"{row['stop'] - row['start']:.3f}" if (row["stop"] is not None) and (row["start"] is not None) else cfg.NA,
1395
897
  media_file_name, # Media file name
1396
898
  ]
1397
899
  )
@@ -1427,10 +929,12 @@ def events_to_behavioral_sequences(pj, obs_id: str, subj: str, parameters: dict,
1427
929
  str: behavioral string for selected subject in selected observation
1428
930
  """
1429
931
 
1430
- out = ""
1431
- current_states = []
932
+ out: str = ""
933
+ current_states: list = []
1432
934
  # add status (POINT, START, STOP) to event
1433
- events_with_status = project_functions.events_start_stop(pj[cfg.ETHOGRAM], pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS])
935
+ events_with_status = project_functions.events_start_stop(
936
+ pj[cfg.ETHOGRAM], pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS], pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE]
937
+ )
1434
938
 
1435
939
  for event in events_with_status:
1436
940
  # check if event in selected behaviors
@@ -1438,10 +942,7 @@ def events_to_behavioral_sequences(pj, obs_id: str, subj: str, parameters: dict,
1438
942
  if event[cfg.EVENT_BEHAVIOR_FIELD_IDX] not in parameters[cfg.SELECTED_BEHAVIORS]:
1439
943
  continue
1440
944
 
1441
- if event[cfg.EVENT_SUBJECT_FIELD_IDX] == subj or (
1442
- subj == cfg.NO_FOCAL_SUBJECT and event[cfg.EVENT_SUBJECT_FIELD_IDX] == ""
1443
- ):
1444
-
945
+ if event[cfg.EVENT_SUBJECT_FIELD_IDX] == subj or (subj == cfg.NO_FOCAL_SUBJECT and event[cfg.EVENT_SUBJECT_FIELD_IDX] == ""):
1445
946
  # if event[cfg.EVENT_STATUS_FIELD_IDX] == cfg.POINT:
1446
947
  if event[-1] == cfg.POINT: # status is last element
1447
948
  if current_states:
@@ -1473,7 +974,6 @@ def events_to_behavioral_sequences(pj, obs_id: str, subj: str, parameters: dict,
1473
974
 
1474
975
  # if event[cfg.EVENT_STATUS_FIELD_IDX] == cfg.STOP:
1475
976
  if event[-1] == cfg.STOP:
1476
-
1477
977
  if parameters[cfg.INCLUDE_MODIFIERS]:
1478
978
  behav_modif = (
1479
979
  f"{event[cfg.EVENT_BEHAVIOR_FIELD_IDX]}"
@@ -1497,9 +997,7 @@ def events_to_behavioral_sequences(pj, obs_id: str, subj: str, parameters: dict,
1497
997
  return out
1498
998
 
1499
999
 
1500
- def events_to_behavioral_sequences_all_subj(
1501
- pj, obs_id: str, subjects_list: list, parameters: dict, behav_seq_separator: str
1502
- ) -> str:
1000
+ def events_to_behavioral_sequences_all_subj(pj, obs_id: str, subjects_list: list, parameters: dict, behav_seq_separator: str) -> str:
1503
1001
  """
1504
1002
  return the behavioral sequences for all selected subjects in obs_id
1505
1003
 
@@ -1516,7 +1014,9 @@ def events_to_behavioral_sequences_all_subj(
1516
1014
 
1517
1015
  out = ""
1518
1016
  current_states = {i: [] for i in subjects_list}
1519
- events_with_status = project_functions.events_start_stop(pj[cfg.ETHOGRAM], pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS])
1017
+ events_with_status = project_functions.events_start_stop(
1018
+ pj[cfg.ETHOGRAM], pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS], pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE]
1019
+ )
1520
1020
 
1521
1021
  for event in events_with_status:
1522
1022
  # check if event in selected behaviors
@@ -1526,14 +1026,11 @@ def events_to_behavioral_sequences_all_subj(
1526
1026
  if (event[cfg.EVENT_SUBJECT_FIELD_IDX] in subjects_list) or (
1527
1027
  event[cfg.EVENT_SUBJECT_FIELD_IDX] == "" and cfg.NO_FOCAL_SUBJECT in subjects_list
1528
1028
  ):
1529
-
1530
1029
  subject = event[cfg.EVENT_SUBJECT_FIELD_IDX] if event[cfg.EVENT_SUBJECT_FIELD_IDX] else cfg.NO_FOCAL_SUBJECT
1531
1030
 
1532
1031
  if event[-1] == cfg.POINT:
1533
1032
  if current_states[subject]:
1534
- out += (
1535
- f"[{subject}]" + "+".join(current_states[subject]) + "+" + event[cfg.EVENT_BEHAVIOR_FIELD_IDX]
1536
- )
1033
+ out += f"[{subject}]" + "+".join(current_states[subject]) + "+" + event[cfg.EVENT_BEHAVIOR_FIELD_IDX]
1537
1034
  else:
1538
1035
  out += f"[{subject}]" + event[cfg.EVENT_BEHAVIOR_FIELD_IDX]
1539
1036
 
@@ -1559,7 +1056,6 @@ def events_to_behavioral_sequences_all_subj(
1559
1056
  out += behav_seq_separator
1560
1057
 
1561
1058
  if event[-1] == cfg.STOP:
1562
-
1563
1059
  if parameters[cfg.INCLUDE_MODIFIERS]:
1564
1060
  behav_modif = (
1565
1061
  f"{event[cfg.EVENT_BEHAVIOR_FIELD_IDX]}"
@@ -1601,9 +1097,7 @@ def events_to_timed_behavioral_sequences(
1601
1097
  str: behavioral string for selected subject in selected observation
1602
1098
  """
1603
1099
 
1604
- out = ""
1605
- current_states = []
1606
- # events_with_status = project_functions.events_start_stop(pj[ETHOGRAM], pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS])
1100
+ out: str = ""
1607
1101
 
1608
1102
  state_behaviors_codes = util.state_behavior_codes(pj[cfg.ETHOGRAM])
1609
1103
  delta = dec(str(round(precision, 3)))
@@ -1638,10 +1132,7 @@ def events_to_timed_behavioral_sequences(
1638
1132
  return out
1639
1133
 
1640
1134
 
1641
- def observation_to_behavioral_sequences(
1642
- pj, selected_observations, parameters, behaviors_separator, separated_subjects, timed, file_name
1643
- ):
1644
-
1135
+ def observation_to_behavioral_sequences(pj, selected_observations, parameters, behaviors_separator, separated_subjects, timed, file_name):
1645
1136
  try:
1646
1137
  with open(file_name, "w", encoding="utf-8") as out_file:
1647
1138
  for obs_id in selected_observations:
@@ -1679,9 +1170,7 @@ def observation_to_behavioral_sequences(
1679
1170
  out_file.write("# Independent variables\n")
1680
1171
 
1681
1172
  for variable in pj[cfg.OBSERVATIONS][obs_id][cfg.INDEPENDENT_VARIABLES]:
1682
- out_file.write(
1683
- f"# {variable}: {pj[cfg.OBSERVATIONS][obs_id][cfg.INDEPENDENT_VARIABLES][variable]}\n"
1684
- )
1173
+ out_file.write(f"# {variable}: {pj[cfg.OBSERVATIONS][obs_id][cfg.INDEPENDENT_VARIABLES][variable]}\n")
1685
1174
  out_file.write("\n")
1686
1175
 
1687
1176
  # one sequence for all subjects
@@ -1702,9 +1191,7 @@ def observation_to_behavioral_sequences(
1702
1191
  out = events_to_behavioral_sequences(pj, obs_id, subject, parameters, behaviors_separator)
1703
1192
 
1704
1193
  if timed:
1705
- out = events_to_timed_behavioral_sequences(
1706
- pj, obs_id, subject, parameters, 0.001, behaviors_separator
1707
- )
1194
+ out = events_to_timed_behavioral_sequences(pj, obs_id, subject, parameters, 0.001, behaviors_separator)
1708
1195
 
1709
1196
  if out:
1710
1197
  out_file.write(out + "\n")