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 file is part of BORIS.
7
7
 
@@ -26,6 +26,7 @@ import pathlib as pl
26
26
  from decimal import Decimal as dec
27
27
  from io import StringIO
28
28
  import pandas as pd
29
+ import time
29
30
 
30
31
  try:
31
32
  import pyreadr
@@ -36,8 +37,8 @@ except ModuleNotFoundError:
36
37
 
37
38
 
38
39
  import tablib
39
- from PyQt5.QtCore import Qt
40
- from PyQt5.QtWidgets import (
40
+ from PySide6.QtCore import Qt
41
+ from PySide6.QtWidgets import (
41
42
  QFileDialog,
42
43
  QHBoxLayout,
43
44
  QInputDialog,
@@ -51,6 +52,7 @@ from PyQt5.QtWidgets import (
51
52
  QTableWidgetItem,
52
53
  QVBoxLayout,
53
54
  QWidget,
55
+ QApplication,
54
56
  )
55
57
 
56
58
  from . import config as cfg
@@ -110,7 +112,7 @@ class timeBudgetResults(QWidget):
110
112
  spacerItem = QSpacerItem(241, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
111
113
  hbox2.addItem(spacerItem)
112
114
 
113
- self.pbClose = QPushButton("Close", clicked=self.close_clicked)
115
+ self.pbClose = QPushButton(cfg.CLOSE, clicked=self.close_clicked)
114
116
  hbox2.addWidget(self.pbClose)
115
117
 
116
118
  hbox.addLayout(hbox2)
@@ -129,48 +131,47 @@ class timeBudgetResults(QWidget):
129
131
  save time budget analysis results in TSV, CSV, ODS, XLS format
130
132
  """
131
133
 
132
- def complete(l: list, max_: int) -> list:
134
+ def complete(lst: list, max_: int) -> list:
133
135
  """
134
136
  complete list with empty string until len = max
135
137
 
136
138
  Args:
137
- l (list): list to complete
139
+ lst (list): list to complete
138
140
  max_ (int): length of the returned list
139
141
 
140
142
  Returns:
141
143
  list: completed list
142
144
  """
143
145
 
144
- l.extend([""] * (max_ - len(l)))
145
- return l
146
+ lst.extend([""] * (max_ - len(lst)))
147
+ return lst
146
148
 
147
149
  logging.debug("save time budget results to file")
148
150
 
149
- file_formats = [cfg.TSV, cfg.CSV, cfg.ODS, cfg.XLSX, cfg.XLS, cfg.HTML, cfg.PANDAS_DF, cfg.RDS]
151
+ file_formats = (cfg.TSV, cfg.CSV, cfg.ODS, cfg.XLSX, cfg.XLS, cfg.HTML, cfg.TEXT_FILE, cfg.PANDAS_DF, cfg.RDS)
150
152
 
151
- file_name, filter_ = QFileDialog().getSaveFileName(
152
- self, "Save Time budget analysis", "", ";;".join(file_formats)
153
- )
153
+ file_name, filter_ = QFileDialog().getSaveFileName(self, "Save Time budget analysis", "", ";;".join(file_formats))
154
154
 
155
155
  if not file_name:
156
156
  return
157
157
 
158
158
  # add correct file extension if not present
159
159
  if pl.Path(file_name).suffix != f".{cfg.FILE_NAME_SUFFIX[filter_]}":
160
- file_name = str(pl.Path(file_name)) + "." + cfg.FILE_NAME_SUFFIX[filter_]
160
+ if cfg.FILE_NAME_SUFFIX[filter_] != "cli":
161
+ file_name = str(pl.Path(file_name)) + "." + cfg.FILE_NAME_SUFFIX[filter_]
162
+ else:
163
+ file_name = str(pl.Path(file_name))
161
164
  # check if file with new extension already exists
162
165
  if pl.Path(file_name).is_file():
163
166
  if (
164
- dialog.MessageDialog(
165
- cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE]
166
- )
167
+ dialog.MessageDialog(cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE])
167
168
  == cfg.CANCEL
168
169
  ):
169
170
  return
170
171
 
171
- rows = []
172
+ rows: list = []
172
173
 
173
- header = ["Observation id", "Observation date", "Description"]
174
+ header: list = ["Observation id", "Observation date", "Description"]
174
175
  # indep var labels
175
176
  header.extend([self.pj[cfg.INDEPENDENT_VARIABLES][idx]["label"] for idx in self.pj[cfg.INDEPENDENT_VARIABLES]])
176
177
  header.extend(["Time budget start", "Time budget stop", "Time budget duration"])
@@ -203,9 +204,9 @@ class timeBudgetResults(QWidget):
203
204
  for idx in self.pj.get(cfg.INDEPENDENT_VARIABLES, []):
204
205
  if self.lw.count() == 1:
205
206
  # var has value in obs?
206
- if self.pj[cfg.INDEPENDENT_VARIABLES][idx]["label"] in self.pj[cfg.OBSERVATIONS][
207
- self.lw.item(0).text()
208
- ].get(cfg.INDEPENDENT_VARIABLES, []):
207
+ if self.pj[cfg.INDEPENDENT_VARIABLES][idx]["label"] in self.pj[cfg.OBSERVATIONS][self.lw.item(0).text()].get(
208
+ cfg.INDEPENDENT_VARIABLES, []
209
+ ):
209
210
  col1.append(
210
211
  self.pj[cfg.OBSERVATIONS][self.lw.item(0).text()][cfg.INDEPENDENT_VARIABLES][
211
212
  self.pj[cfg.INDEPENDENT_VARIABLES][idx]["label"]
@@ -221,10 +222,10 @@ class timeBudgetResults(QWidget):
221
222
  col1.extend([f"{self.min_time:0.3f}", f"{self.max_time:0.3f}", f"{self.max_time - self.min_time:0.3f}"])
222
223
 
223
224
  if self.time_interval == cfg.TIME_FULL_OBS:
224
- col1.extend([f"Full observation", f"Full observation", f"Full observation"])
225
+ col1.extend(["Full observation", "Full observation", "Full observation"])
225
226
 
226
227
  if self.time_interval == cfg.TIME_EVENTS:
227
- col1.extend([f"Limited to coded events", f"Limited to coded events", f"Limited to coded events"])
228
+ col1.extend(["Limited to coded events", "Limited to coded events", "Limited to coded events"])
228
229
 
229
230
  for row_idx in range(self.twTB.rowCount()):
230
231
  values = []
@@ -376,7 +377,7 @@ class timeBudgetResults(QWidget):
376
377
 
377
378
  # write results
378
379
  with open(file_name, "wb") as f:
379
- if filter_ in (cfg.TSV, cfg.CSV, cfg.HTML):
380
+ if filter_ in (cfg.TSV, cfg.CSV, cfg.HTML, cfg.TEXT_FILE):
380
381
  f.write(str.encode(data.export(cfg.FILE_NAME_SUFFIX[filter_])))
381
382
  if filter_ in (cfg.ODS, cfg.XLSX, cfg.XLS):
382
383
  f.write(data.export(cfg.FILE_NAME_SUFFIX[filter_]))
@@ -389,6 +390,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
389
390
  Args:
390
391
  mode (str): ["by_behavior", "by_category"]
391
392
  mode2 (str): must be in ["list", "current"]
393
+ "current" time budget of current observation
392
394
  """
393
395
 
394
396
  if mode2 == "current":
@@ -415,9 +417,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
415
417
  flagGroup: bool = False
416
418
  if len(selected_observations) > 1:
417
419
  flagGroup = (
418
- dialog.MessageDialog(
419
- cfg.programName, "Group the selected observations in a single time budget analysis?", [cfg.YES, cfg.NO]
420
- )
420
+ dialog.MessageDialog(cfg.programName, "Group the selected observations in a single time budget analysis?", [cfg.YES, cfg.NO])
421
421
  == cfg.YES
422
422
  )
423
423
 
@@ -425,20 +425,25 @@ def time_budget(self, mode: str, mode2: str = "list"):
425
425
  self.pj[cfg.OBSERVATIONS], selected_observations
426
426
  )
427
427
 
428
- logging.debug(
429
- f"max_media_duration_all_obs: {max_media_duration_all_obs}, total_media_duration_all_obs={total_media_duration_all_obs}"
430
- )
428
+ logging.debug(f"max_media_duration_all_obs: {max_media_duration_all_obs}, total_media_duration_all_obs={total_media_duration_all_obs}")
431
429
 
432
430
  start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
433
431
 
434
- parameters = select_subj_behav.choose_obs_subj_behav_category(
432
+ start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
433
+
434
+ parameters: dict = select_subj_behav.choose_obs_subj_behav_category(
435
435
  self,
436
436
  selected_observations,
437
437
  start_coding=start_coding,
438
438
  end_coding=end_coding,
439
+ # start_interval=start_interval,
440
+ # end_interval=end_interval,
441
+ start_interval=None,
442
+ end_interval=None,
439
443
  maxTime=max_media_duration_all_obs,
440
444
  by_category=(mode == "by_category"),
441
445
  n_observations=len(selected_observations),
446
+ show_exclude_non_coded_modifiers=True,
442
447
  )
443
448
  if parameters == {}:
444
449
  return
@@ -447,21 +452,27 @@ def time_budget(self, mode: str, mode2: str = "list"):
447
452
  QMessageBox.warning(None, cfg.programName, "Select subject(s) and behavior(s) to analyze")
448
453
  return
449
454
 
455
+ logging.debug(f"{parameters=}")
456
+
450
457
  # ask for excluding behaviors durations from total time
451
- if not start_coding.is_nan():
458
+ if start_coding is not None and not start_coding.is_nan():
452
459
  cancel_pressed, parameters[cfg.EXCLUDED_BEHAVIORS] = self.filter_behaviors(
453
460
  title="Select behaviors to exclude from the total time",
454
- text=("The duration of the selected behaviors will " "be subtracted from the total time"),
461
+ text=("The duration of the selected behaviors will be subtracted from the total time"),
455
462
  table="",
456
- behavior_type=[cfg.STATE_EVENT],
463
+ behavior_type=cfg.STATE_EVENT_TYPES,
457
464
  )
458
465
  if cancel_pressed:
459
466
  return
460
467
  else:
461
468
  parameters[cfg.EXCLUDED_BEHAVIORS] = []
462
469
 
470
+ self.statusbar.showMessage(f"Generating time budget for {len(selected_observations)} observation(s)")
471
+ QApplication.processEvents()
472
+
463
473
  # check if time_budget window must be used
464
474
  if flagGroup or len(selected_observations) == 1:
475
+ t0 = time.time()
465
476
 
466
477
  cursor = db_functions.load_events_in_db(
467
478
  self.pj,
@@ -473,7 +484,6 @@ def time_budget(self, mode: str, mode2: str = "list"):
473
484
 
474
485
  total_observation_time = 0
475
486
  for obsId in selected_observations:
476
-
477
487
  obs_length = observation_operations.observation_total_length(self.pj[cfg.OBSERVATIONS][obsId])
478
488
 
479
489
  if obs_length == dec(-1): # media length not available
@@ -482,7 +492,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
482
492
  if obs_length == dec(-2): # images obs without time
483
493
  parameters[cfg.TIME_INTERVAL] = cfg.TIME_EVENTS
484
494
 
485
- if parameters[cfg.TIME_INTERVAL] == cfg.TIME_FULL_OBS:
495
+ if parameters[cfg.TIME_INTERVAL] == cfg.TIME_FULL_OBS: # media file duration
486
496
  min_time = float(0)
487
497
  # check if the last event is recorded after media file length
488
498
  try:
@@ -493,7 +503,14 @@ def time_budget(self, mode: str, mode2: str = "list"):
493
503
  except Exception:
494
504
  max_time = float(obs_length)
495
505
 
496
- if parameters[cfg.TIME_INTERVAL] == cfg.TIME_EVENTS:
506
+ if parameters[cfg.TIME_INTERVAL] == cfg.TIME_OBS_INTERVAL:
507
+ obs_interval = self.pj[cfg.OBSERVATIONS][obsId].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0])
508
+ offset = float(self.pj[cfg.OBSERVATIONS][obsId][cfg.TIME_OFFSET])
509
+ min_time = float(obs_interval[0]) + offset
510
+ # Use max media duration for max time if no interval is defined (=0)
511
+ max_time = float(obs_interval[1]) + offset if obs_interval[1] != 0 else float(obs_length)
512
+
513
+ if parameters[cfg.TIME_INTERVAL] == cfg.TIME_EVENTS: # events duration
497
514
  try:
498
515
  min_time = float(self.pj[cfg.OBSERVATIONS][obsId][cfg.EVENTS][0][0]) # first event
499
516
  except Exception:
@@ -511,8 +528,10 @@ def time_budget(self, mode: str, mode2: str = "list"):
511
528
  # check intervals
512
529
  for subj in parameters[cfg.SELECTED_SUBJECTS]:
513
530
  for behav in parameters[cfg.SELECTED_BEHAVIORS]:
514
- if cfg.POINT in self.eventType(behav).upper():
531
+ # if cfg.POINT in self.eventType(behav).upper():
532
+ if project_functions.event_type(behav, self.pj[cfg.ETHOGRAM]) in cfg.POINT_EVENT_TYPES:
515
533
  continue
534
+
516
535
  # extract modifiers
517
536
 
518
537
  cursor.execute(
@@ -524,7 +543,6 @@ def time_budget(self, mode: str, mode2: str = "list"):
524
543
  # logging.debug("distinct_modifiers: {}".format(distinct_modifiers))
525
544
 
526
545
  for modifier in distinct_modifiers:
527
-
528
546
  # logging.debug("modifier #{}#".format(modifier[0]))
529
547
 
530
548
  # insert events at boundaries of time interval
@@ -541,12 +559,8 @@ def time_budget(self, mode: str, mode2: str = "list"):
541
559
  )
542
560
  % 2
543
561
  ):
544
-
545
562
  cursor.execute(
546
- (
547
- "INSERT INTO events (observation, subject, code, type, modifiers, occurence) "
548
- "VALUES (?,?,?,?,?,?)"
549
- ),
563
+ ("INSERT INTO events (observation, subject, code, type, modifiers, occurence) VALUES (?,?,?,?,?,?)"),
550
564
  (obsId, subj, behav, "STATE", modifier[0], min_time),
551
565
  )
552
566
 
@@ -562,12 +576,8 @@ def time_budget(self, mode: str, mode2: str = "list"):
562
576
  )
563
577
  % 2
564
578
  ):
565
-
566
579
  cursor.execute(
567
- (
568
- "INSERT INTO events (observation, subject, code, type, modifiers, occurence) "
569
- "VALUES (?,?,?,?,?,?)"
570
- ),
580
+ ("INSERT INTO events (observation, subject, code, type, modifiers, occurence) VALUES (?,?,?,?,?,?)"),
571
581
  (obsId, subj, behav, "STATE", modifier[0], max_time),
572
582
  )
573
583
  try:
@@ -582,6 +592,10 @@ def time_budget(self, mode: str, mode2: str = "list"):
582
592
  "DELETE FROM events WHERE observation = ? AND (occurence < ? OR occurence > ?)",
583
593
  (obsId, min_time, max_time),
584
594
  )
595
+ try:
596
+ cursor.execute("COMMIT")
597
+ except Exception:
598
+ pass
585
599
 
586
600
  out, categories = time_budget_functions.time_budget_analysis(
587
601
  self.pj[cfg.ETHOGRAM], cursor, selected_observations, parameters, by_category=(mode == "by_category")
@@ -593,9 +607,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
593
607
  if element["subject"] not in excl_behaviors_total_time:
594
608
  excl_behaviors_total_time[element["subject"]] = 0
595
609
  if element["behavior"] in parameters[cfg.EXCLUDED_BEHAVIORS]:
596
- excl_behaviors_total_time[element["subject"]] += (
597
- element["duration"] if not isinstance(element["duration"], str) else 0
598
- )
610
+ excl_behaviors_total_time[element["subject"]] += element["duration"] if not isinstance(element["duration"], str) else 0
599
611
 
600
612
  # widget for results visualization
601
613
  self.tb = timeBudgetResults(self.pj, self.config_param)
@@ -615,20 +627,14 @@ def time_budget(self, mode: str, mode2: str = "list"):
615
627
  if len(selected_observations) > 1:
616
628
  if total_observation_time:
617
629
  if self.timeFormat == cfg.HHMMSS:
618
- self.tb.lbTotalObservedTime.setText(
619
- f"Total observation length: {util.seconds2time(total_observation_time)}"
620
- )
630
+ self.tb.lbTotalObservedTime.setText(f"Total observation length: {util.seconds2time(total_observation_time)}")
621
631
  if self.timeFormat == cfg.S:
622
- self.tb.lbTotalObservedTime.setText(
623
- f"Total observation length: {float(total_observation_time):0.3f}"
624
- )
632
+ self.tb.lbTotalObservedTime.setText(f"Total observation length: {float(total_observation_time):0.3f}")
625
633
  else:
626
634
  self.tb.lbTotalObservedTime.setText("Total observation length: not available")
627
635
  else:
628
636
  if self.timeFormat == cfg.HHMMSS:
629
- self.tb.lbTotalObservedTime.setText(
630
- f"Analysis from {util.seconds2time(min_time)} to {util.seconds2time(max_time)}"
631
- )
637
+ self.tb.lbTotalObservedTime.setText(f"Analysis from {util.seconds2time(min_time)} to {util.seconds2time(max_time)}")
632
638
  if self.timeFormat == cfg.S:
633
639
  self.tb.lbTotalObservedTime.setText(f"Analysis from {float(min_time):0.3f} to {float(max_time):0.3f} s")
634
640
 
@@ -640,8 +646,10 @@ def time_budget(self, mode: str, mode2: str = "list"):
640
646
  else:
641
647
  self.tb.excluded_behaviors_list.setVisible(False)
642
648
 
643
- if mode == "by_behavior":
649
+ self.statusbar.showMessage(f"Time budget generated in {round(time.time() - t0, 3)} s", 5000)
650
+ logging.debug("Time budget generated")
644
651
 
652
+ if mode == "by_behavior":
645
653
  tb_fields = [
646
654
  "Subject",
647
655
  "Behavior",
@@ -673,7 +681,6 @@ def time_budget(self, mode: str, mode2: str = "list"):
673
681
  self.tb.twTB.setRowCount(self.tb.twTB.rowCount() + 1)
674
682
  column = 0
675
683
  for field in fields:
676
-
677
684
  if isinstance(row[field], float):
678
685
  item = QTableWidgetItem(f"{row[field]:.3f}")
679
686
  else:
@@ -689,10 +696,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
689
696
  elif row["duration"] not in ("-", cfg.UNPAIRED) and not start_coding.is_nan():
690
697
  tot_time = float(total_observation_time)
691
698
  # substract time of excluded behaviors from the total for the subject
692
- if (
693
- row["subject"] in excl_behaviors_total_time
694
- and row["behavior"] not in parameters[cfg.EXCLUDED_BEHAVIORS]
695
- ):
699
+ if row["subject"] in excl_behaviors_total_time and row["behavior"] not in parameters[cfg.EXCLUDED_BEHAVIORS]:
696
700
  tot_time -= excl_behaviors_total_time[row["subject"]]
697
701
  item = QTableWidgetItem(f"{row['duration'] / tot_time * 100:.1f}" if tot_time > 0 else cfg.NA)
698
702
 
@@ -703,7 +707,6 @@ def time_budget(self, mode: str, mode2: str = "list"):
703
707
  self.tb.twTB.setItem(self.tb.twTB.rowCount() - 1, column, item)
704
708
 
705
709
  if mode == "by_category":
706
-
707
710
  tb_fields = ["Subject", "Category", "Total number", "Total duration (s)"]
708
711
  fields = ["number", "duration"]
709
712
 
@@ -711,9 +714,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
711
714
  self.tb.twTB.setHorizontalHeaderLabels(tb_fields)
712
715
 
713
716
  for subject in categories:
714
-
715
717
  for category in categories[subject]:
716
-
717
718
  self.tb.twTB.setRowCount(self.tb.twTB.rowCount() + 1)
718
719
 
719
720
  column = 0
@@ -745,12 +746,11 @@ def time_budget(self, mode: str, mode2: str = "list"):
745
746
 
746
747
  self.tb.twTB.resizeColumnsToContents()
747
748
 
748
- gui_utilities.restore_geometry(self.tb, "time budget", (0, 0))
749
+ gui_utilities.restore_geometry(self.tb, "time budget", (800, 600))
749
750
 
750
751
  self.tb.show()
751
752
 
752
753
  if not flagGroup and len(selected_observations) > 1:
753
-
754
754
  output_format, ok = QInputDialog.getItem(
755
755
  self,
756
756
  "Time budget analysis format",
@@ -776,9 +776,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
776
776
  if output_format in (cfg.ODS_WB, cfg.XLSX_WB):
777
777
  workbook = tablib.Databook()
778
778
 
779
- wb_file_name, filter_ = QFileDialog(self).getSaveFileName(
780
- self, "Save Time budget analysis", "", output_format
781
- )
779
+ wb_file_name, filter_ = QFileDialog(self).getSaveFileName(self, "Save Time budget analysis", "", output_format)
782
780
  if not wb_file_name:
783
781
  return
784
782
 
@@ -787,9 +785,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
787
785
  # check if file with new extension already exists
788
786
  if pl.Path(wb_file_name).is_file():
789
787
  if (
790
- dialog.MessageDialog(
791
- cfg.programName, f"The file {wb_file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE]
792
- )
788
+ dialog.MessageDialog(cfg.programName, f"The file {wb_file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE])
793
789
  == cfg.CANCEL
794
790
  ):
795
791
  return
@@ -805,7 +801,6 @@ def time_budget(self, mode: str, mode2: str = "list"):
805
801
  return
806
802
 
807
803
  if mode == "by_behavior":
808
-
809
804
  tb_fields = [
810
805
  "Subject",
811
806
  "Behavior",
@@ -831,23 +826,19 @@ def time_budget(self, mode: str, mode2: str = "list"):
831
826
  ]
832
827
 
833
828
  if mode == "by_category":
834
-
835
829
  tb_fields = ["Subject", "Category", "Total number of occurences", "Total duration (s)"]
836
830
  fields = ["subject", "category", "number", "duration"]
837
831
 
838
832
  mem_command = ""
839
833
  for obsId in selected_observations:
840
-
841
- cursor = db_functions.load_events_in_db(
842
- self.pj, parameters[cfg.SELECTED_SUBJECTS], [obsId], parameters[cfg.SELECTED_BEHAVIORS]
843
- )
834
+ cursor = db_functions.load_events_in_db(self.pj, parameters[cfg.SELECTED_SUBJECTS], [obsId], parameters[cfg.SELECTED_BEHAVIORS])
844
835
 
845
836
  obs_length = observation_operations.observation_total_length(self.pj[cfg.OBSERVATIONS][obsId])
846
837
 
847
838
  if obs_length == -1:
848
839
  obs_length = 0
849
840
 
850
- if parameters["time"] == cfg.TIME_FULL_OBS:
841
+ if parameters[cfg.TIME_INTERVAL] == cfg.TIME_FULL_OBS:
851
842
  min_time = float(0)
852
843
  # check if the last event is recorded after media file length
853
844
  try:
@@ -858,7 +849,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
858
849
  except Exception:
859
850
  max_time = float(obs_length)
860
851
 
861
- if parameters["time"] == cfg.TIME_EVENTS:
852
+ if parameters[cfg.TIME_INTERVAL] == cfg.TIME_EVENTS:
862
853
  try:
863
854
  min_time = float(self.pj[cfg.OBSERVATIONS][obsId][cfg.EVENTS][0][0])
864
855
  except Exception:
@@ -868,19 +859,15 @@ def time_budget(self, mode: str, mode2: str = "list"):
868
859
  except Exception:
869
860
  max_time = float(obs_length)
870
861
 
871
- if parameters["time"] == cfg.TIME_ARBITRARY_INTERVAL:
862
+ if parameters[cfg.TIME_INTERVAL] == cfg.TIME_ARBITRARY_INTERVAL:
872
863
  min_time = float(parameters[cfg.START_TIME])
873
864
  max_time = float(parameters[cfg.END_TIME])
874
865
 
875
866
  # check intervals
876
867
  for subj in parameters[cfg.SELECTED_SUBJECTS]:
877
868
  for behav in parameters[cfg.SELECTED_BEHAVIORS]:
878
- if cfg.POINT in project_functions.event_type(
879
- behav, self.pj[cfg.ETHOGRAM]
880
- ): # self.eventType(behav).upper():
869
+ if project_functions.event_type(behav, self.pj[cfg.ETHOGRAM]) in cfg.POINT_EVENT_TYPES:
881
870
  continue
882
- # extract modifiers
883
- # if plot_parameters["include modifiers"]:
884
871
 
885
872
  cursor.execute(
886
873
  "SELECT distinct modifiers FROM events WHERE observation = ? AND subject = ? AND code = ?",
@@ -889,7 +876,6 @@ def time_budget(self, mode: str, mode2: str = "list"):
889
876
  distinct_modifiers = list(cursor.fetchall())
890
877
 
891
878
  for modifier in distinct_modifiers:
892
-
893
879
  if (
894
880
  len(
895
881
  cursor.execute(
@@ -904,10 +890,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
904
890
  % 2
905
891
  ):
906
892
  cursor.execute(
907
- (
908
- "INSERT INTO events (observation, subject, code, type, modifiers, occurence) "
909
- "VALUES (?,?,?,?,?,?)"
910
- ),
893
+ ("INSERT INTO events (observation, subject, code, type, modifiers, occurence) VALUES (?,?,?,?,?,?)"),
911
894
  (obsId, subj, behav, "STATE", modifier[0], min_time),
912
895
  )
913
896
  if (
@@ -923,10 +906,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
923
906
  % 2
924
907
  ):
925
908
  cursor.execute(
926
- (
927
- "INSERT INTO events (observation, subject, code, type, modifiers, occurence) "
928
- "VALUES (?,?,?,?,?,?)"
929
- ),
909
+ ("INSERT INTO events (observation, subject, code, type, modifiers, occurence) VALUES (?,?,?,?,?,?)"),
930
910
  (obsId, subj, behav, cfg.STATE, modifier[0], max_time),
931
911
  )
932
912
  try:
@@ -949,9 +929,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
949
929
  if element["subject"] not in excl_behaviors_total_time:
950
930
  excl_behaviors_total_time[element["subject"]] = 0
951
931
  if element["behavior"] in parameters[cfg.EXCLUDED_BEHAVIORS]:
952
- excl_behaviors_total_time[element["subject"]] += (
953
- element["duration"] if element["duration"] != "NA" else 0
954
- )
932
+ excl_behaviors_total_time[element["subject"]] += element["duration"] if element["duration"] != "NA" else 0
955
933
 
956
934
  rows: list = []
957
935
  col1: list = []
@@ -965,9 +943,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
965
943
  indep_var_values: list = []
966
944
  for _, v in self.pj.get(cfg.INDEPENDENT_VARIABLES, {}).items():
967
945
  indep_var_label.append(v["label"])
968
- indep_var_values.append(
969
- self.pj[cfg.OBSERVATIONS][obsId].get(cfg.INDEPENDENT_VARIABLES, {}).get(v["label"], "")
970
- )
946
+ indep_var_values.append(self.pj[cfg.OBSERVATIONS][obsId].get(cfg.INDEPENDENT_VARIABLES, {}).get(v["label"], ""))
971
947
 
972
948
  header.extend(indep_var_label)
973
949
  col1.extend(indep_var_values)
@@ -980,7 +956,6 @@ def time_budget(self, mode: str, mode2: str = "list"):
980
956
  header.extend(["Time budget start", "Time budget stop", "Time budget duration"])
981
957
 
982
958
  if mode == "by_behavior":
983
-
984
959
  # header
985
960
  rows.append(header + tb_fields)
986
961
 
@@ -994,10 +969,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
994
969
  elif row["duration"] not in ("-", cfg.UNPAIRED) and not start_coding.is_nan():
995
970
  tot_time = float(max_time - min_time)
996
971
  # substract duration of excluded behaviors from total time for each subject
997
- if (
998
- row["subject"] in excl_behaviors_total_time
999
- and row["behavior"] not in parameters[cfg.EXCLUDED_BEHAVIORS]
1000
- ):
972
+ if row["subject"] in excl_behaviors_total_time and row["behavior"] not in parameters[cfg.EXCLUDED_BEHAVIORS]:
1001
973
  tot_time -= excl_behaviors_total_time[row["subject"]]
1002
974
  # % of tot time
1003
975
  values.append(round(row["duration"] / tot_time * 100, 1) if tot_time > 0 else cfg.NA)
@@ -1010,7 +982,6 @@ def time_budget(self, mode: str, mode2: str = "list"):
1010
982
  rows.append(header + tb_fields)
1011
983
 
1012
984
  for subject in categories:
1013
-
1014
985
  for category in categories[subject]:
1015
986
  values = []
1016
987
  values.append(subject)
@@ -1039,7 +1010,6 @@ def time_budget(self, mode: str, mode2: str = "list"):
1039
1010
  workbook.add_sheet(data)
1040
1011
 
1041
1012
  else:
1042
-
1043
1013
  file_name = f"{pl.Path(exportDir) / pl.Path(util.safeFileName(obsId))}.{extension}"
1044
1014
  if mem_command != cfg.OVERWRITE_ALL and pl.Path(file_name).is_file():
1045
1015
  if mem_command == "Skip all":