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
@@ -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
@@ -66,7 +66,6 @@ def export_events_jwatcher(
66
66
  str: error message
67
67
  """
68
68
  for subject in parameters[cfg.SELECTED_SUBJECTS]:
69
-
70
69
  # select events for current subject
71
70
  events = []
72
71
  for event in observation[cfg.EVENTS]:
@@ -80,9 +79,7 @@ def export_events_jwatcher(
80
79
 
81
80
  total_length = 0 # in seconds
82
81
  if observation[cfg.EVENTS]:
83
- total_length = (
84
- observation[cfg.EVENTS][-1][0] - observation[cfg.EVENTS][0][0]
85
- ) # 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
86
83
 
87
84
  file_name_subject = str(pathlib.Path(file_name).parent / pathlib.Path(file_name).stem) + "_" + subject + ".dat"
88
85
 
@@ -120,14 +117,13 @@ def export_events_jwatcher(
120
117
  behav_code = event[cfg.EVENT_BEHAVIOR_FIELD_IDX]
121
118
 
122
119
  try:
123
- behavior_key = [
124
- ethogram[k][cfg.BEHAVIOR_KEY] for k in ethogram if ethogram[k][cfg.BEHAVIOR_CODE] == behav_code
125
- ][0]
120
+ behavior_key = [ethogram[k][cfg.BEHAVIOR_KEY] for k in ethogram if ethogram[k][cfg.BEHAVIOR_CODE] == behav_code][0]
126
121
  except Exception:
127
122
  # coded behavior not defined in ethogram
128
123
  continue
129
- if [ethogram[k][cfg.TYPE] for k in ethogram if ethogram[k][cfg.BEHAVIOR_CODE] == behav_code] == [
130
- 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],
131
127
  ]:
132
128
  if behav_code in mem_number_of_state_events:
133
129
  mem_number_of_state_events[behav_code] += 1
@@ -155,7 +151,7 @@ def export_events_jwatcher(
155
151
  if fmf_file_path.exists():
156
152
  fmf_creation_answer = dialog.MessageDialog(
157
153
  cfg.programName,
158
- (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?"),
159
155
  [cfg.OVERWRITE, "Skip file creation", cfg.CANCEL],
160
156
  )
161
157
 
@@ -168,11 +164,9 @@ def export_events_jwatcher(
168
164
  rows.append("# Format: Focal Master File 1.0")
169
165
  rows.append(f"# Updated: {dt.datetime.now().isoformat()}")
170
166
  rows.append("#-----------------------------------------------------------")
171
- for (behav, key) in all_observed_behaviors:
167
+ for behav, key in all_observed_behaviors:
172
168
  rows.append(f"Behaviour.name.{key}={behav}")
173
- behav_description = [
174
- ethogram[k][cfg.DESCRIPTION] for k in ethogram if ethogram[k][cfg.BEHAVIOR_CODE] == behav
175
- ][0]
169
+ behav_description = [ethogram[k][cfg.DESCRIPTION] for k in ethogram if ethogram[k][cfg.BEHAVIOR_CODE] == behav][0]
176
170
  rows.append(f"Behaviour.description.{key}={behav_description}")
177
171
 
178
172
  rows.append(f"DurationMilliseconds={int(float(total_length) * 1000)}")
@@ -199,7 +193,7 @@ def export_events_jwatcher(
199
193
  if faf_file_path.exists():
200
194
  faf_creation_answer = dialog.MessageDialog(
201
195
  cfg.programName,
202
- (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?"),
203
197
  [cfg.OVERWRITE, "Skip file creation", cfg.CANCEL],
204
198
  )
205
199
  if faf_creation_answer == cfg.CANCEL:
@@ -276,7 +270,7 @@ def export_events_jwatcher(
276
270
  rows.append("AllCodesMutuallyExclusive=true")
277
271
  rows.append("")
278
272
 
279
- for (behav, key) in all_observed_behaviors:
273
+ for behav, key in all_observed_behaviors:
280
274
  rows.append(f"Behavior.isModified.{key}=false")
281
275
  rows.append(f"Behavior.isSubtracted.{key}=false")
282
276
  rows.append(f"Behavior.isIgnored.{key}=false")
@@ -319,13 +313,13 @@ def export_tabular_events(
319
313
  interval = parameters["time"]
320
314
 
321
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])
322
317
 
323
318
  if interval == cfg.TIME_EVENTS:
324
319
  min_time = start_coding
325
320
  max_time = end_coding
326
321
 
327
322
  if interval == cfg.TIME_FULL_OBS:
328
-
329
323
  if observation[cfg.TYPE] == cfg.MEDIA:
330
324
  max_media_duration, _ = observation_operations.media_duration(pj[cfg.OBSERVATIONS], [obs_id])
331
325
  min_time = dec("0")
@@ -340,6 +334,12 @@ def export_tabular_events(
340
334
  min_time = parameters[cfg.START_TIME]
341
335
  max_time = parameters[cfg.END_TIME]
342
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
+
343
343
  logging.debug(f"min_time: {min_time} max_time: {max_time}")
344
344
 
345
345
  events_with_status = project_functions.events_start_stop(ethogram, observation[cfg.EVENTS], observation[cfg.TYPE])
@@ -356,10 +356,7 @@ def export_tabular_events(
356
356
  max_modifiers = max(max_modifiers, len(event[cfg.EVENT_MODIFIER_FIELD_IDX].split("|")))
357
357
 
358
358
  # media file number
359
- mediaNb = 0
360
- if observation[cfg.TYPE] == cfg.MEDIA:
361
- for player in observation[cfg.FILE]:
362
- mediaNb += len(observation[cfg.FILE][player])
359
+ media_nb = util.count_media_file(observation[cfg.FILE])
363
360
 
364
361
  rows: list = []
365
362
 
@@ -371,9 +368,14 @@ def export_tabular_events(
371
368
  "Observation duration": float,
372
369
  "Observation type": str,
373
370
  "Source": str,
374
- "Media duration (s)": str,
375
- "FPS": float,
371
+ "Time offset (s)": str,
376
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
377
379
 
378
380
  # independent variables
379
381
  if cfg.INDEPENDENT_VARIABLES in observation:
@@ -418,7 +420,6 @@ def export_tabular_events(
418
420
  (event[cfg.EVENT_SUBJECT_FIELD_IDX] in parameters[cfg.SELECTED_SUBJECTS])
419
421
  or (event[cfg.EVENT_SUBJECT_FIELD_IDX] == "" and cfg.NO_FOCAL_SUBJECT in parameters[cfg.SELECTED_SUBJECTS])
420
422
  ) and (event[cfg.EVENT_BEHAVIOR_FIELD_IDX] in parameters[cfg.SELECTED_BEHAVIORS]):
421
-
422
423
  fields: list = []
423
424
  fields.append(obs_id)
424
425
  fields.append(observation.get("date", "").replace("T", " "))
@@ -436,9 +437,7 @@ def export_tabular_events(
436
437
  for media_file in observation[cfg.FILE][player]:
437
438
  media_file_lst.append(media_file)
438
439
  fps_lst.append(f"{observation[cfg.MEDIA_INFO][cfg.FPS].get(media_file, cfg.NA):.3f}")
439
- media_durations_lst.append(
440
- f"{observation[cfg.MEDIA_INFO][cfg.LENGTH].get(media_file, cfg.NA):.3f}"
441
- )
440
+ media_durations_lst.append(f"{observation[cfg.MEDIA_INFO][cfg.LENGTH].get(media_file, cfg.NA):.3f}")
442
441
  if player > "1":
443
442
  media_file_str += "|"
444
443
  fps_str += "|"
@@ -488,6 +487,9 @@ def export_tabular_events(
488
487
  else:
489
488
  fields.append("")
490
489
 
490
+ # time offset
491
+ fields.append(observation[cfg.TIME_OFFSET])
492
+
491
493
  # media duration
492
494
  fields.append(media_durations_str)
493
495
 
@@ -526,9 +528,7 @@ def export_tabular_events(
526
528
 
527
529
  # check video file name containing the event
528
530
  if observation[cfg.TYPE] == cfg.MEDIA:
529
- video_file_name = observation_operations.event2media_file_name(
530
- observation, event[cfg.EVENT_TIME_FIELD_IDX]
531
- )
531
+ video_file_name = observation_operations.event2media_file_name(observation, event[cfg.EVENT_TIME_FIELD_IDX])
532
532
  if video_file_name is None:
533
533
  video_file_name = "Not found"
534
534
 
@@ -573,9 +573,7 @@ def export_tabular_events(
573
573
  return r, msg
574
574
 
575
575
 
576
- def dataset_write(
577
- dataset: tablib.Dataset, file_name: str, output_format: str, dtype: dict = {}
578
- ) -> tuple: # -> tuple[bool, str]:
576
+ def dataset_write(dataset: tablib.Dataset, file_name: str, output_format: str, dtype: dict = {}) -> tuple: # -> tuple[bool, str]:
579
577
  """
580
578
  write a tablib dataset with aggregated events or tabular events to file in specified format (output_format)
581
579
 
@@ -583,6 +581,7 @@ def dataset_write(
583
581
  dataset (tablib.dataset): dataset to write
584
582
  file_name (str): file name
585
583
  output_format (str): format of output
584
+ dtype (dict): type of field
586
585
 
587
586
  Returns:
588
587
  bool: result. True if OK else False
@@ -592,11 +591,9 @@ def dataset_write(
592
591
  logging.debug("function: dataset_write")
593
592
 
594
593
  try:
595
-
596
- if output_format in ("pkl", "rds"):
597
-
594
+ if output_format in (cfg.PANDAS_DF_EXT, cfg.RDS_EXT):
598
595
  # build pandas dataframe from the tsv export of tablib dataset
599
- date_type = []
596
+ date_type: list = []
600
597
  for field_name in dtype:
601
598
  if dtype[field_name] == dt.datetime:
602
599
  date_type.append(field_name)
@@ -605,27 +602,26 @@ def dataset_write(
605
602
  del dtype[field_name]
606
603
 
607
604
  df = pd.read_csv(
608
- StringIO(dataset.export("tsv")),
605
+ StringIO(dataset.export(cfg.TSV_EXT)),
609
606
  sep="\t",
610
607
  dtype=dtype,
611
608
  parse_dates=date_type,
612
609
  )
613
610
 
614
- if output_format == "pkl":
611
+ if output_format == cfg.PANDAS_DF_EXT:
615
612
  df.to_pickle(file_name)
616
613
 
617
- if flag_pyreadr_loaded and output_format == "rds":
614
+ if output_format == cfg.RDS_EXT and flag_pyreadr_loaded:
618
615
  pyreadr.write_rds(file_name, df)
619
616
 
620
617
  return True, ""
621
618
 
622
- if output_format in ("csv", "tsv", "html"):
619
+ if output_format in (cfg.CSV_EXT, cfg.TSV_EXT, cfg.HTML_EXT):
623
620
  with open(file_name, "wb") as f:
624
621
  f.write(str.encode(dataset.export(output_format)))
625
622
  return True, ""
626
623
 
627
- if output_format in ("ods", "xls", "xlsx"):
628
-
624
+ if output_format in (cfg.ODS_EXT, cfg.XLS_EXT, cfg.XLSX_EXT):
629
625
  dataset.title = util.safe_xl_worksheet_title(dataset.title, output_format)
630
626
 
631
627
  with open(file_name, "wb") as f:
@@ -638,7 +634,7 @@ def dataset_write(
638
634
  return False, str(sys.exc_info()[1])
639
635
 
640
636
 
641
- 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]:
642
638
  """
643
639
  export aggregated events of one observation
644
640
 
@@ -646,6 +642,7 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str) -> Tuple[ta
646
642
  pj (dict): BORIS project
647
643
  parameters (dict): subjects, behaviors
648
644
  obsId (str): observation id
645
+ force_number_modifiers (int): force the number of modifiers to return
649
646
 
650
647
  Returns:
651
648
  tablib.Dataset:
@@ -660,6 +657,8 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str) -> Tuple[ta
660
657
  data = tablib.Dataset()
661
658
 
662
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
+
663
662
  if start_coding is None and end_coding is None: # no events
664
663
  return data, 0
665
664
 
@@ -681,21 +680,22 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str) -> Tuple[ta
681
680
  min_time = float(parameters[cfg.START_TIME])
682
681
  max_time = float(parameters[cfg.END_TIME])
683
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
+
684
689
  logging.debug(f"min_time: {min_time} max_time: {max_time}")
685
690
 
686
691
  # obs description
687
692
  obs_description = util.eol2space(observation[cfg.DESCRIPTION])
688
693
 
689
- """
690
- obs_length = observation_operations.observation_total_length(pj[cfg.OBSERVATIONS][obsId])
691
- logging.debug(f"obs_length: {obs_length}")
692
- """
693
-
694
694
  _, _, connector = db_functions.load_aggregated_events_in_db(
695
695
  pj, parameters[cfg.SELECTED_SUBJECTS], [obsId], parameters[cfg.SELECTED_BEHAVIORS]
696
696
  )
697
697
  if connector is None:
698
- logging.critical(f"error when loading aggregated events in DB")
698
+ logging.critical("error when loading aggregated events in DB")
699
699
  return data, 0
700
700
 
701
701
  cursor = connector.cursor()
@@ -749,13 +749,16 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str) -> Tuple[ta
749
749
  behavioral_category = project_functions.behavior_category(pj[cfg.ETHOGRAM])
750
750
 
751
751
  cursor.execute("SELECT DISTINCT modifiers FROM aggregated_events")
752
- max_modifiers = 0
753
- for row in cursor.fetchall():
754
- if row["modifiers"]:
755
- max_modifiers = max(max_modifiers, row["modifiers"].count("|") + 1)
756
752
 
757
- 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
758
760
 
761
+ for subject in parameters[cfg.SELECTED_SUBJECTS]:
759
762
  # calculate observation duration by subject (by obs)
760
763
  cursor.execute(("SELECT SUM(stop - start) AS duration FROM aggregated_events WHERE subject = ? "), (subject,))
761
764
  duration_by_subject_by_obs = cursor.fetchone()["duration"]
@@ -763,7 +766,6 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str) -> Tuple[ta
763
766
  duration_by_subject_by_obs = round(duration_by_subject_by_obs, 3)
764
767
 
765
768
  for behavior in parameters[cfg.SELECTED_BEHAVIORS]:
766
-
767
769
  cursor.execute(
768
770
  "SELECT DISTINCT modifiers FROM aggregated_events WHERE subject=? AND behavior=? ORDER BY modifiers",
769
771
  (
@@ -775,7 +777,6 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str) -> Tuple[ta
775
777
  rows_distinct_modifiers = list(x[0] for x in cursor.fetchall())
776
778
 
777
779
  for distinct_modifiers in rows_distinct_modifiers:
778
-
779
780
  cursor.execute(
780
781
  (
781
782
  "SELECT start, stop, type, modifiers, comment, comment_stop, "
@@ -787,12 +788,12 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str) -> Tuple[ta
787
788
  )
788
789
 
789
790
  for row in cursor.fetchall():
790
-
791
791
  media_file_name = cfg.NA
792
792
 
793
793
  if observation[cfg.TYPE] == cfg.MEDIA:
794
794
  observation_type = "Media file"
795
795
 
796
+ # get the media file name of the start of event
796
797
  media_file_name = observation_operations.event2media_file_name(observation, row["start"])
797
798
  if media_file_name is None:
798
799
  media_file_name = "Not found"
@@ -804,12 +805,8 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str) -> Tuple[ta
804
805
  if observation[cfg.FILE][player]:
805
806
  for media_file in observation[cfg.FILE][player]:
806
807
  media_file_lst.append(media_file)
807
- fps_lst.append(
808
- f"{observation[cfg.MEDIA_INFO][cfg.FPS].get(media_file, cfg.NA):.3f}"
809
- )
810
- media_durations_lst.append(
811
- f"{observation[cfg.MEDIA_INFO][cfg.LENGTH].get(media_file, cfg.NA):.3f}"
812
- )
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}")
813
810
  if player > "1":
814
811
  media_file_str += "|"
815
812
  fps_str += "|"
@@ -841,7 +838,8 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str) -> Tuple[ta
841
838
  observation["date"].replace("T", " "),
842
839
  obs_description,
843
840
  observation_type,
844
- media_file_str,
841
+ media_file_str, # list of media used in observation
842
+ pj[cfg.OBSERVATIONS][obsId][cfg.TIME_OFFSET],
845
843
  f"{coding_duration:.3f}" if not coding_duration.is_nan() else cfg.NA,
846
844
  media_durations_str,
847
845
  fps_str,
@@ -851,13 +849,8 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str) -> Tuple[ta
851
849
  # independent variables
852
850
  if cfg.INDEPENDENT_VARIABLES in pj:
853
851
  for idx_var in util.sorted_keys(pj[cfg.INDEPENDENT_VARIABLES]):
854
- if (
855
- pj[cfg.INDEPENDENT_VARIABLES][idx_var]["label"]
856
- in observation[cfg.INDEPENDENT_VARIABLES]
857
- ):
858
- var_value = observation[cfg.INDEPENDENT_VARIABLES][
859
- pj[cfg.INDEPENDENT_VARIABLES][idx_var]["label"]
860
- ]
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"]]
861
854
  if pj[cfg.INDEPENDENT_VARIABLES][idx_var]["type"] == "timestamp":
862
855
  var_value = var_value.replace("T", " ")
863
856
 
@@ -900,9 +893,7 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str) -> Tuple[ta
900
893
  f"{row['start']:.3f}" if row["start"] is not None else cfg.NA,
901
894
  f"{row['stop']:.3f}" if row["stop"] is not None else cfg.NA,
902
895
  # duration
903
- f"{row['stop'] - row['start']:.3f}"
904
- if (row["stop"] is not None) and (row["start"] is not None)
905
- 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,
906
897
  media_file_name, # Media file name
907
898
  ]
908
899
  )
@@ -951,10 +942,7 @@ def events_to_behavioral_sequences(pj, obs_id: str, subj: str, parameters: dict,
951
942
  if event[cfg.EVENT_BEHAVIOR_FIELD_IDX] not in parameters[cfg.SELECTED_BEHAVIORS]:
952
943
  continue
953
944
 
954
- if event[cfg.EVENT_SUBJECT_FIELD_IDX] == subj or (
955
- subj == cfg.NO_FOCAL_SUBJECT and event[cfg.EVENT_SUBJECT_FIELD_IDX] == ""
956
- ):
957
-
945
+ if event[cfg.EVENT_SUBJECT_FIELD_IDX] == subj or (subj == cfg.NO_FOCAL_SUBJECT and event[cfg.EVENT_SUBJECT_FIELD_IDX] == ""):
958
946
  # if event[cfg.EVENT_STATUS_FIELD_IDX] == cfg.POINT:
959
947
  if event[-1] == cfg.POINT: # status is last element
960
948
  if current_states:
@@ -986,7 +974,6 @@ def events_to_behavioral_sequences(pj, obs_id: str, subj: str, parameters: dict,
986
974
 
987
975
  # if event[cfg.EVENT_STATUS_FIELD_IDX] == cfg.STOP:
988
976
  if event[-1] == cfg.STOP:
989
-
990
977
  if parameters[cfg.INCLUDE_MODIFIERS]:
991
978
  behav_modif = (
992
979
  f"{event[cfg.EVENT_BEHAVIOR_FIELD_IDX]}"
@@ -1010,9 +997,7 @@ def events_to_behavioral_sequences(pj, obs_id: str, subj: str, parameters: dict,
1010
997
  return out
1011
998
 
1012
999
 
1013
- def events_to_behavioral_sequences_all_subj(
1014
- pj, obs_id: str, subjects_list: list, parameters: dict, behav_seq_separator: str
1015
- ) -> str:
1000
+ def events_to_behavioral_sequences_all_subj(pj, obs_id: str, subjects_list: list, parameters: dict, behav_seq_separator: str) -> str:
1016
1001
  """
1017
1002
  return the behavioral sequences for all selected subjects in obs_id
1018
1003
 
@@ -1041,14 +1026,11 @@ def events_to_behavioral_sequences_all_subj(
1041
1026
  if (event[cfg.EVENT_SUBJECT_FIELD_IDX] in subjects_list) or (
1042
1027
  event[cfg.EVENT_SUBJECT_FIELD_IDX] == "" and cfg.NO_FOCAL_SUBJECT in subjects_list
1043
1028
  ):
1044
-
1045
1029
  subject = event[cfg.EVENT_SUBJECT_FIELD_IDX] if event[cfg.EVENT_SUBJECT_FIELD_IDX] else cfg.NO_FOCAL_SUBJECT
1046
1030
 
1047
1031
  if event[-1] == cfg.POINT:
1048
1032
  if current_states[subject]:
1049
- out += (
1050
- f"[{subject}]" + "+".join(current_states[subject]) + "+" + event[cfg.EVENT_BEHAVIOR_FIELD_IDX]
1051
- )
1033
+ out += f"[{subject}]" + "+".join(current_states[subject]) + "+" + event[cfg.EVENT_BEHAVIOR_FIELD_IDX]
1052
1034
  else:
1053
1035
  out += f"[{subject}]" + event[cfg.EVENT_BEHAVIOR_FIELD_IDX]
1054
1036
 
@@ -1074,7 +1056,6 @@ def events_to_behavioral_sequences_all_subj(
1074
1056
  out += behav_seq_separator
1075
1057
 
1076
1058
  if event[-1] == cfg.STOP:
1077
-
1078
1059
  if parameters[cfg.INCLUDE_MODIFIERS]:
1079
1060
  behav_modif = (
1080
1061
  f"{event[cfg.EVENT_BEHAVIOR_FIELD_IDX]}"
@@ -1151,10 +1132,7 @@ def events_to_timed_behavioral_sequences(
1151
1132
  return out
1152
1133
 
1153
1134
 
1154
- def observation_to_behavioral_sequences(
1155
- pj, selected_observations, parameters, behaviors_separator, separated_subjects, timed, file_name
1156
- ):
1157
-
1135
+ def observation_to_behavioral_sequences(pj, selected_observations, parameters, behaviors_separator, separated_subjects, timed, file_name):
1158
1136
  try:
1159
1137
  with open(file_name, "w", encoding="utf-8") as out_file:
1160
1138
  for obs_id in selected_observations:
@@ -1192,9 +1170,7 @@ def observation_to_behavioral_sequences(
1192
1170
  out_file.write("# Independent variables\n")
1193
1171
 
1194
1172
  for variable in pj[cfg.OBSERVATIONS][obs_id][cfg.INDEPENDENT_VARIABLES]:
1195
- out_file.write(
1196
- f"# {variable}: {pj[cfg.OBSERVATIONS][obs_id][cfg.INDEPENDENT_VARIABLES][variable]}\n"
1197
- )
1173
+ out_file.write(f"# {variable}: {pj[cfg.OBSERVATIONS][obs_id][cfg.INDEPENDENT_VARIABLES][variable]}\n")
1198
1174
  out_file.write("\n")
1199
1175
 
1200
1176
  # one sequence for all subjects
@@ -1215,9 +1191,7 @@ def observation_to_behavioral_sequences(
1215
1191
  out = events_to_behavioral_sequences(pj, obs_id, subject, parameters, behaviors_separator)
1216
1192
 
1217
1193
  if timed:
1218
- out = events_to_timed_behavioral_sequences(
1219
- pj, obs_id, subject, parameters, 0.001, behaviors_separator
1220
- )
1194
+ out = events_to_timed_behavioral_sequences(pj, obs_id, subject, parameters, 0.001, behaviors_separator)
1221
1195
 
1222
1196
  if out:
1223
1197
  out_file.write(out + "\n")