boris-behav-obs 9.2__tar.gz → 9.2.2__tar.gz

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.
Files changed (120) hide show
  1. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/PKG-INFO +4 -7
  2. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/advanced_event_filtering.py +5 -3
  3. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/behavior_binary_table.py +5 -5
  4. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/cooccurence.py +4 -2
  5. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/core.py +16 -14
  6. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/export_events.py +8 -6
  7. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/gui_utilities.py +19 -11
  8. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/observation_operations.py +16 -9
  9. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/player_dock_widget.py +19 -4
  10. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/plot_events.py +1 -1
  11. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/plugins.py +31 -4
  12. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/project_functions.py +6 -14
  13. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/synthetic_time_budget.py +9 -5
  14. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/time_budget_functions.py +1 -1
  15. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/time_budget_widget.py +6 -4
  16. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/utilities.py +0 -2
  17. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/version.py +2 -2
  18. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris_behav_obs.egg-info/PKG-INFO +4 -7
  19. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris_behav_obs.egg-info/SOURCES.txt +2 -1
  20. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris_behav_obs.egg-info/requires.txt +0 -1
  21. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/pyproject.toml +4 -11
  22. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/tests/test_utilities.py +7 -0
  23. boris_behav_obs-9.2.2/tests/test_utilities2.py +124 -0
  24. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/LICENSE.TXT +0 -0
  25. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/MANIFEST.in +0 -0
  26. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/README.TXT +0 -0
  27. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/README.md +0 -0
  28. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/__init__.py +0 -0
  29. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/__main__.py +0 -0
  30. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/about.py +0 -0
  31. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/add_modifier.py +0 -0
  32. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/add_modifier_ui.py +0 -0
  33. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/analysis_plugins/__init__.py +0 -0
  34. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/analysis_plugins/number_of_occurences.py +0 -0
  35. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/analysis_plugins/number_of_occurences_by_independent_variable.py +0 -0
  36. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/analysis_plugins/time_budget.py +0 -0
  37. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/behav_coding_map_creator.py +0 -0
  38. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/behaviors_coding_map.py +0 -0
  39. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/boris_cli.py +0 -0
  40. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/cmd_arguments.py +0 -0
  41. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/coding_pad.py +0 -0
  42. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/config.py +0 -0
  43. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/config_file.py +0 -0
  44. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/connections.py +0 -0
  45. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/converters.py +0 -0
  46. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/converters_ui.py +0 -0
  47. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/core_qrc.py +0 -0
  48. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/core_ui.py +0 -0
  49. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/db_functions.py +0 -0
  50. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/dev.py +0 -0
  51. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/dialog.py +0 -0
  52. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/duration_widget.py +0 -0
  53. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/edit_event.py +0 -0
  54. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/edit_event_ui.py +0 -0
  55. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/event_operations.py +0 -0
  56. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/events_cursor.py +0 -0
  57. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/events_snapshots.py +0 -0
  58. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/exclusion_matrix.py +0 -0
  59. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/export_observation.py +0 -0
  60. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/external_processes.py +0 -0
  61. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/geometric_measurement.py +0 -0
  62. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/image_overlay.py +0 -0
  63. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/import_observations.py +0 -0
  64. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/irr.py +0 -0
  65. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/latency.py +0 -0
  66. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/map_creator.py +0 -0
  67. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/measurement_widget.py +0 -0
  68. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/media_file.py +0 -0
  69. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/menu_options.py +0 -0
  70. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/modifiers_coding_map.py +0 -0
  71. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/mpv-1.0.3.py +0 -0
  72. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/mpv.py +0 -0
  73. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/mpv2.py +0 -0
  74. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/observation.py +0 -0
  75. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/observation_ui.py +0 -0
  76. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/observations_list.py +0 -0
  77. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/otx_parser.py +0 -0
  78. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/param_panel.py +0 -0
  79. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/param_panel_ui.py +0 -0
  80. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/plot_data_module.py +0 -0
  81. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/plot_events_rt.py +0 -0
  82. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/plot_spectrogram_rt.py +0 -0
  83. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/plot_waveform_rt.py +0 -0
  84. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/portion/__init__.py +0 -0
  85. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/portion/const.py +0 -0
  86. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/portion/dict.py +0 -0
  87. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/portion/func.py +0 -0
  88. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/portion/interval.py +0 -0
  89. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/portion/io.py +0 -0
  90. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/preferences.py +0 -0
  91. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/preferences_ui.py +0 -0
  92. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/project.py +0 -0
  93. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/project_import_export.py +0 -0
  94. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/project_ui.py +0 -0
  95. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/qrc_boris.py +0 -0
  96. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/qrc_boris5.py +0 -0
  97. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/select_modifiers.py +0 -0
  98. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/select_observations.py +0 -0
  99. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/select_subj_behav.py +0 -0
  100. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/state_events.py +0 -0
  101. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/subjects_pad.py +0 -0
  102. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/transitions.py +0 -0
  103. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/video_equalizer.py +0 -0
  104. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/video_equalizer_ui.py +0 -0
  105. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/video_operations.py +0 -0
  106. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/view_df.py +0 -0
  107. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/view_df_ui.py +0 -0
  108. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris/write_event.py +0 -0
  109. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris_behav_obs.egg-info/dependency_links.txt +0 -0
  110. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris_behav_obs.egg-info/entry_points.txt +0 -0
  111. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/boris_behav_obs.egg-info/top_level.txt +0 -0
  112. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/setup.cfg +0 -0
  113. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/tests/test_db_functions.py +0 -0
  114. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/tests/test_export_observation.py +0 -0
  115. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/tests/test_irr.py +0 -0
  116. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/tests/test_observation_gui.py +0 -0
  117. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/tests/test_otx_parser.py +0 -0
  118. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/tests/test_preferences_gui.py +0 -0
  119. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/tests/test_project_functions.py +0 -0
  120. {boris_behav_obs-9.2 → boris_behav_obs-9.2.2}/tests/test_time_budget.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: boris-behav-obs
3
- Version: 9.2
3
+ Version: 9.2.2
4
4
  Summary: BORIS - Behavioral Observation Research Interactive Software
5
5
  Author-email: Olivier Friard <olivier.friard@unito.it>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -686,13 +686,10 @@ Project-URL: Issues, https://github.com/olivierfriard/BORIS/issues
686
686
  Classifier: Topic :: Scientific/Engineering
687
687
  Classifier: Intended Audience :: Science/Research
688
688
  Classifier: Intended Audience :: Education
689
- Classifier: Programming Language :: Python :: 3.8
690
- Classifier: Programming Language :: Python :: 3.9
691
- Classifier: Programming Language :: Python :: 3.10
692
- Classifier: Programming Language :: Python :: 3.11
693
689
  Classifier: Programming Language :: Python :: 3.12
694
690
  Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
695
691
  Classifier: Operating System :: OS Independent
692
+ Classifier: Topic :: Scientific/Engineering
696
693
  Requires-Python: >=3.12
697
694
  Description-Content-Type: text/x-rst
698
695
  License-File: LICENSE.TXT
@@ -705,7 +702,7 @@ Requires-Dist: pyreadr
705
702
  Requires-Dist: pyside6==6.8.0.2
706
703
  Requires-Dist: hachoir>=3.3.0
707
704
  Provides-Extra: dev
708
- Requires-Dist: black; extra == "dev"
709
705
  Requires-Dist: ruff; extra == "dev"
710
706
  Requires-Dist: pytest; extra == "dev"
711
707
  Requires-Dist: pytest-cov; extra == "dev"
708
+ Dynamic: license-file
@@ -359,8 +359,10 @@ def event_filtering(self):
359
359
  selected_observations,
360
360
  start_coding=start_coding,
361
361
  end_coding=end_coding,
362
- start_interval=start_interval,
363
- end_interval=end_interval,
362
+ # start_interval=start_interval,
363
+ # end_interval=end_interval,
364
+ start_interval=None,
365
+ end_interval=None,
364
366
  maxTime=max_media_duration_all_obs,
365
367
  show_include_modifiers=False,
366
368
  show_exclude_non_coded_behaviors=False,
@@ -368,7 +370,7 @@ def event_filtering(self):
368
370
  n_observations=len(selected_observations),
369
371
  )
370
372
 
371
- if parameters == {}:
373
+ if not parameters:
372
374
  return
373
375
 
374
376
  if not parameters[cfg.SELECTED_SUBJECTS] or not parameters[cfg.SELECTED_BEHAVIORS]:
@@ -80,12 +80,12 @@ def create_behavior_binary_table(pj: dict, selected_observations: list, paramete
80
80
  end_time = dec(max_obs_length)
81
81
 
82
82
  if parameters_obs["time"] == cfg.TIME_OBS_INTERVAL:
83
- obs_interval = pj[cfg.OBSERVATIONS][obs_id][cfg.OBSERVATION_TIME_INTERVAL]
83
+ obs_interval = pj[cfg.OBSERVATIONS][obs_id].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0])
84
84
  offset = pj[cfg.OBSERVATIONS][obs_id][cfg.TIME_OFFSET]
85
85
  start_time = dec(obs_interval[0]) + offset
86
86
  # Use max observation length for end time if no interval is defined (=0)
87
87
  max_obs_length, _ = observation_operations.observation_length(pj, [obs_id])
88
- end_time = dec(obs_interval[1]) + offset if obs_interval[1] != 0 else dec(max_obs_length)
88
+ end_time = dec(obs_interval[1]) + offset if obs_interval[1] not in (0, None) else dec(max_obs_length)
89
89
 
90
90
  if obs_id not in results_df:
91
91
  results_df[obs_id] = {}
@@ -166,8 +166,8 @@ def behavior_binary_table(self):
166
166
  None,
167
167
  cfg.programName,
168
168
  (
169
- "Depending of the length of your observations "
170
- "the execution of this function may be very long.<br>"
169
+ "Depending on the length of yours observations "
170
+ "the execution of this function may take a long time.<br>"
171
171
  "The program interface may freeze, be patient. <br>"
172
172
  ),
173
173
  )
@@ -224,7 +224,7 @@ def behavior_binary_table(self):
224
224
  by_category=False,
225
225
  n_observations=len(selected_observations),
226
226
  )
227
- if parameters == {}:
227
+ if not parameters:
228
228
  return
229
229
  if not parameters[cfg.SELECTED_SUBJECTS] or not parameters[cfg.SELECTED_BEHAVIORS]:
230
230
  QMessageBox.warning(None, cfg.programName, "Select subject(s) and behavior(s) to analyze")
@@ -106,8 +106,10 @@ def get_cooccurence(self):
106
106
  selected_observations,
107
107
  start_coding=start_coding,
108
108
  end_coding=end_coding,
109
- start_interval=start_interval,
110
- end_interval=end_interval,
109
+ # start_interval=start_interval,
110
+ # end_interval=end_interval,
111
+ start_interval=None,
112
+ end_interval=None,
111
113
  maxTime=max_media_duration_all_obs,
112
114
  n_observations=len(selected_observations),
113
115
  show_include_modifiers=False,
@@ -3631,6 +3631,17 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3631
3631
  else:
3632
3632
  current_time = self.getLaps()
3633
3633
 
3634
+ # check if observation time interval
3635
+ if self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0])[1]:
3636
+ # check if current time outside of interval
3637
+ if current_time >= self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.OBSERVATION_TIME_INTERVAL, [None, None])[1]:
3638
+ self.beep("beep")
3639
+ self.liveTimer.stop()
3640
+ self.liveObservationStarted = False
3641
+ # set current time to end of observation interval
3642
+ current_time = dec(self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.OBSERVATION_TIME_INTERVAL, [None, None])[1])
3643
+ self.pb_live_obs.setText("Live observation finished")
3644
+
3634
3645
  self.lb_current_media_time.setText(util.convertTime(self.timeFormat, current_time))
3635
3646
 
3636
3647
  # extract State events
@@ -3660,14 +3671,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3660
3671
  self.liveTimer.stop()
3661
3672
  self.pb_live_obs.setText("Live observation stopped (scan sampling)")
3662
3673
 
3663
- # observation time interval
3664
- if self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0])[1]:
3665
- if current_time >= self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0])[1]:
3666
- self.beep("beep")
3667
- self.liveTimer.stop()
3668
- self.liveObservationStarted = False
3669
- self.pb_live_obs.setText("Live observation finished")
3670
-
3671
3674
  def start_live_observation(self):
3672
3675
  """
3673
3676
  activate the live observation mode
@@ -3769,8 +3772,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3769
3772
  selected_observations,
3770
3773
  start_coding=start_coding,
3771
3774
  end_coding=end_coding,
3772
- start_interval=start_interval,
3773
- end_interval=end_interval,
3775
+ # start_interval=start_interval,
3776
+ # end_interval=end_interval,
3777
+ start_interval=None,
3778
+ end_interval=None,
3774
3779
  maxTime=max_media_duration_all_obs,
3775
3780
  show_include_modifiers=False,
3776
3781
  show_exclude_non_coded_behaviors=False,
@@ -5747,10 +5752,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5747
5752
  if self.playerType == cfg.MEDIA:
5748
5753
  self.pause_video()
5749
5754
 
5750
- if cfg.OBSERVATION_TIME_INTERVAL in self.pj[cfg.OBSERVATIONS][self.observationId]:
5751
- self.seek_mediaplayer(int(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.OBSERVATION_TIME_INTERVAL][0]))
5752
- else:
5753
- self.seek_mediaplayer(0)
5755
+ self.seek_mediaplayer(int(self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0])[0]))
5754
5756
 
5755
5757
  self.update_visualizations()
5756
5758
 
@@ -76,7 +76,7 @@ def export_events_as_behavioral_sequences(self, separated_subjects=False, timed=
76
76
  else:
77
77
  max_media_duration_all_obs = None
78
78
  start_coding, end_coding = dec("NaN"), dec("NaN")
79
- start_interval, end_interval = dec("NaN"), dec("NaN")
79
+ start_interval, end_interval = None, None
80
80
 
81
81
  parameters = select_subj_behav.choose_obs_subj_behav_category(
82
82
  self,
@@ -169,7 +169,7 @@ def export_tabular_events(self, mode: str = "tabular") -> None:
169
169
  else:
170
170
  max_media_duration_all_obs = None
171
171
  start_coding, end_coding = dec("NaN"), dec("NaN")
172
- start_interval, end_interval = dec("NaN"), dec("NaN")
172
+ start_interval, end_interval = None, None
173
173
 
174
174
  parameters = select_subj_behav.choose_obs_subj_behav_category(
175
175
  self,
@@ -373,7 +373,7 @@ def export_aggregated_events(self):
373
373
  else:
374
374
  max_media_duration_all_obs = None
375
375
  start_coding, end_coding = dec("NaN"), dec("NaN")
376
- start_interval, end_interval = dec("NaN"), dec("NaN")
376
+ start_interval, end_interval = None, None
377
377
 
378
378
  parameters = select_subj_behav.choose_obs_subj_behav_category(
379
379
  self,
@@ -681,8 +681,10 @@ def export_events_as_textgrid(self) -> None:
681
681
  selected_observations,
682
682
  start_coding=start_coding,
683
683
  end_coding=end_coding,
684
- start_interval=start_interval,
685
- end_interval=end_interval,
684
+ # start_interval=start_interval,
685
+ # end_interval=end_interval,
686
+ start_interval=None,
687
+ end_interval=None,
686
688
  show_include_modifiers=False,
687
689
  show_exclude_non_coded_behaviors=False,
688
690
  maxTime=max_obs_length,
@@ -786,7 +788,7 @@ def export_events_as_textgrid(self) -> None:
786
788
 
787
789
  if parameters["time"] == cfg.TIME_OBS_INTERVAL:
788
790
  max_media_duration, _ = observation_operations.media_duration(self.pj[cfg.OBSERVATIONS], [obs_id])
789
- obs_interval = self.pj[cfg.OBSERVATIONS][obs_id][cfg.OBSERVATION_TIME_INTERVAL]
791
+ obs_interval = self.pj[cfg.OBSERVATIONS][obs_id].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0])
790
792
  offset = float(self.pj[cfg.OBSERVATIONS][obs_id][cfg.TIME_OFFSET])
791
793
  min_time = float(obs_interval[0]) + offset
792
794
  # Use max media duration for max time if no interval is defined (=0)
@@ -31,32 +31,40 @@ def save_geometry(widget: QWidget, widget_name: str):
31
31
  save window geometry in ini file
32
32
  """
33
33
 
34
- try:
35
- ini_file_path = pl.Path.home() / pl.Path(".boris")
36
- if ini_file_path.is_file():
34
+ ini_file_path = pl.Path.home() / pl.Path(".boris")
35
+ if ini_file_path.is_file():
36
+ try:
37
37
  settings = QSettings(str(ini_file_path), QSettings.IniFormat)
38
38
  settings.setValue(f"{widget_name} geometry", widget.saveGeometry())
39
- except Exception:
40
- logging.warning(f"error during saving {widget_name} geometry")
39
+ except Exception:
40
+ logging.warning(f"error during saving {widget_name} geometry")
41
41
 
42
42
 
43
- def restore_geometry(widget: QWidget, widget_name: str, default_geometry):
43
+ def restore_geometry(widget: QWidget, widget_name: str, default_width_height):
44
44
  """
45
45
  restore window geometry in ini file
46
46
  """
47
+ def default_resize(widget, default_width_height):
48
+ if default_width_height != (0, 0):
49
+ try:
50
+ widget.resize(default_width_height[0], default_width_height[1])
51
+ except Exception:
52
+ logging.warning("Error during restoring default")
53
+
47
54
 
55
+ logging.debug(f'restore geometry function for {widget_name}')
48
56
  try:
49
57
  ini_file_path = pl.Path.home() / pl.Path(".boris")
50
58
  if ini_file_path.is_file():
51
59
  settings = QSettings(str(ini_file_path), QSettings.IniFormat)
60
+ print(settings.value(f"{widget_name} geometry"))
52
61
  widget.restoreGeometry(settings.value(f"{widget_name} geometry"))
62
+ logging.debug(f'geometry restored for {widget_name} {settings.value(f"{widget_name} geometry")}')
63
+ else:
64
+ default_resize(widget, default_width_height)
53
65
  except Exception:
54
66
  logging.warning(f"error during restoring {widget_name} geometry")
55
- if default_geometry != (0, 0):
56
- try:
57
- widget.resize(default_geometry[0], default_geometry[1])
58
- except Exception:
59
- logging.warning("Error during restoring default")
67
+ default_resize(widget, default_width_height)
60
68
 
61
69
 
62
70
  def set_icons(self, theme_mode: str) -> None:
@@ -359,14 +359,20 @@ def time_intervals_range(observations: dict, observations_list: list) -> Tuple[O
359
359
  decimal.Decimal: time of latest end interval
360
360
 
361
361
  """
362
- start_interval_list = []
363
- end_interval_list = []
362
+ start_interval_list: list = []
363
+ end_interval_list: list = []
364
364
  for obs_id in observations_list:
365
365
  observation = observations[obs_id]
366
366
  offset = observation[cfg.TIME_OFFSET]
367
- if dec(observation[cfg.OBSERVATION_TIME_INTERVAL][0]) + offset and dec(observation[cfg.OBSERVATION_TIME_INTERVAL][1]) + offset:
368
- start_interval_list.append(dec(observation[cfg.OBSERVATION_TIME_INTERVAL][0]) + offset)
369
- end_interval_list.append(dec(observation[cfg.OBSERVATION_TIME_INTERVAL][1]) + offset)
367
+ # check if observation interval is defined
368
+ if (
369
+ not observation.get(cfg.OBSERVATION_TIME_INTERVAL, [None, None])[0]
370
+ and not observation.get(cfg.OBSERVATION_TIME_INTERVAL, [None, None])[1]
371
+ ):
372
+ return None, None
373
+
374
+ start_interval_list.append(dec(observation[cfg.OBSERVATION_TIME_INTERVAL][0]) + offset)
375
+ end_interval_list.append(dec(observation[cfg.OBSERVATION_TIME_INTERVAL][1]) + offset)
370
376
 
371
377
  if not start_interval_list:
372
378
  earliest_start_interval = None
@@ -872,8 +878,8 @@ def new_observation(self, mode: str = cfg.NEW, obsId: str = "") -> None:
872
878
  observationWindow.cb_observation_time_interval.setText(
873
879
  (
874
880
  "Limit observation to a time interval: "
875
- f"{self.pj[cfg.OBSERVATIONS][obsId][cfg.OBSERVATION_TIME_INTERVAL][0]} - "
876
- f"{self.pj[cfg.OBSERVATIONS][obsId][cfg.OBSERVATION_TIME_INTERVAL][1]}"
881
+ f"{self.pj[cfg.OBSERVATIONS][obsId].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0])[0]} - "
882
+ f"{self.pj[cfg.OBSERVATIONS][obsId].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0])[1]}"
877
883
  )
878
884
  )
879
885
 
@@ -1950,8 +1956,9 @@ def initialize_new_media_observation(self) -> bool:
1950
1956
  )
1951
1957
 
1952
1958
  # position media
1953
- if cfg.OBSERVATION_TIME_INTERVAL in self.pj[cfg.OBSERVATIONS][self.observationId]:
1954
- self.seek_mediaplayer(int(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.OBSERVATION_TIME_INTERVAL][0]), player=i)
1959
+ self.seek_mediaplayer(
1960
+ int(self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0])[0]), player=i
1961
+ )
1955
1962
 
1956
1963
  # restore video zoom level
1957
1964
  if cfg.ZOOM_LEVEL in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO]:
@@ -23,7 +23,18 @@ This file is part of BORIS.
23
23
  import sys
24
24
  import logging
25
25
  import functools
26
- from PySide6.QtWidgets import QLabel, QDockWidget, QWidget, QHBoxLayout, QVBoxLayout, QSlider, QSizePolicy, QStackedWidget, QToolButton
26
+ from PySide6.QtWidgets import (
27
+ QApplication,
28
+ QLabel,
29
+ QDockWidget,
30
+ QWidget,
31
+ QHBoxLayout,
32
+ QVBoxLayout,
33
+ QSlider,
34
+ QSizePolicy,
35
+ QStackedWidget,
36
+ QToolButton,
37
+ )
27
38
  from PySide6.QtCore import Signal, QEvent, Qt
28
39
  from PySide6.QtGui import QIcon, QAction
29
40
 
@@ -141,7 +152,10 @@ class DW_player(QDockWidget):
141
152
  self.mute_button.setFocusPolicy(Qt.NoFocus)
142
153
  self.mute_button.setAutoRaise(True)
143
154
  self.mute_action = QAction()
144
- self.mute_action.setIcon(QIcon(":/volume_xmark"))
155
+
156
+ theme_mode = "dark" if QApplication.instance().palette().window().color().value() < 128 else "light"
157
+
158
+ self.mute_action.setIcon(QIcon(f":/volume_xmark_{theme_mode}"))
145
159
  self.mute_action.triggered.connect(self.mute_action_triggered)
146
160
  self.mute_button.setDefaultAction(self.mute_action)
147
161
 
@@ -180,10 +194,11 @@ class DW_player(QDockWidget):
180
194
  """
181
195
  emit signal when mute action is triggered
182
196
  """
197
+ theme_mode = "dark" if QApplication.instance().palette().window().color().value() < 128 else "light"
183
198
  if self.player.mute:
184
- self.mute_action.setIcon(QIcon(":/volume_xmark"))
199
+ self.mute_action.setIcon(QIcon(f":/volume_xmark_{theme_mode}"))
185
200
  else:
186
- self.mute_action.setIcon(QIcon(":/volume_off"))
201
+ self.mute_action.setIcon(QIcon(f":/volume_off_{theme_mode}"))
187
202
  self.mute_action_triggered_signal.emit(self.id_)
188
203
 
189
204
  def keyPressEvent(self, event):
@@ -441,7 +441,7 @@ def create_events_plot(
441
441
  max_time = float(obs_length)
442
442
 
443
443
  if interval == cfg.TIME_OBS_INTERVAL:
444
- obs_interval = self.pj[cfg.OBSERVATIONS][obs_id][cfg.OBSERVATION_TIME_INTERVAL]
444
+ obs_interval = self.pj[cfg.OBSERVATIONS][obs_id].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0])
445
445
  offset = float(self.pj[cfg.OBSERVATIONS][obs_id][cfg.TIME_OFFSET])
446
446
  min_time = float(obs_interval[0]) + offset
447
447
  # Use max media duration for max time if no interval is defined (=0)
@@ -65,24 +65,44 @@ def load_plugins(self):
65
65
  """
66
66
  load selected plugins in analysis menu
67
67
  """
68
+
69
+ def msg():
70
+ QMessageBox.warning(
71
+ self,
72
+ cfg.programName,
73
+ f"A plugin with the same name is already loaded ({self.config_param[cfg.ANALYSIS_PLUGINS][plugin_name]}).\n\nThe plugin from {file_} is not loaded.",
74
+ QMessageBox.Ok | QMessageBox.Default,
75
+ QMessageBox.NoButton,
76
+ )
77
+
68
78
  self.menu_plugins.clear()
69
79
  self.config_param[cfg.ANALYSIS_PLUGINS] = {}
70
80
 
71
81
  # load BORIS plugins
72
- for file_ in (Path(__file__).parent / "analysis_plugins").glob("*.py"):
82
+ for file_ in sorted((Path(__file__).parent / "analysis_plugins").glob("*.py")):
73
83
  if file_.name == "__init__.py":
74
84
  continue
75
85
  plugin_name = get_plugin_name(file_)
76
86
  if plugin_name is not None and plugin_name not in self.config_param.get(cfg.EXCLUDED_PLUGINS, set()):
87
+ # check if plugin with same name already loaded
88
+ if plugin_name in self.config_param[cfg.ANALYSIS_PLUGINS]:
89
+ msg()
90
+ continue
91
+
77
92
  self.config_param[cfg.ANALYSIS_PLUGINS][plugin_name] = str(file_)
78
93
 
79
94
  # load personal plugins
80
95
  if self.config_param.get(cfg.PERSONAL_PLUGINS_DIR, ""):
81
- for file_ in Path(self.config_param.get(cfg.PERSONAL_PLUGINS_DIR, "")).glob("*.py"):
96
+ for file_ in sorted(Path(self.config_param.get(cfg.PERSONAL_PLUGINS_DIR, "")).glob("*.py")):
82
97
  if file_.name == "__init__.py":
83
98
  continue
84
99
  plugin_name = get_plugin_name(file_)
85
100
  if plugin_name is not None and plugin_name not in self.config_param.get(cfg.EXCLUDED_PLUGINS, set()):
101
+ # check if plugin with same name already loaded
102
+ if plugin_name in self.config_param[cfg.ANALYSIS_PLUGINS]:
103
+ msg()
104
+ continue
105
+
86
106
  self.config_param[cfg.ANALYSIS_PLUGINS][plugin_name] = str(file_)
87
107
 
88
108
  logging.debug(f"{self.config_param.get(cfg.ANALYSIS_PLUGINS, {})=}")
@@ -108,7 +128,7 @@ def plugin_df_filter(df: pd.DataFrame, observations_list: list = [], parameters:
108
128
  # filter selected behaviors
109
129
  df = df[df["Behavior"].isin(parameters["selected behaviors"])]
110
130
 
111
- if parameters["time"] == "interval of observation":
131
+ if parameters["time"] == cfg.TIME_OBS_INTERVAL:
112
132
  # filter each observation with observation interval start/stop
113
133
 
114
134
  # keep events between observation interval start time and observation interval stop/end
@@ -142,6 +162,7 @@ def plugin_df_filter(df: pd.DataFrame, observations_list: list = [], parameters:
142
162
  | ((df["Start (s)"] < MIN_TIME) & (df["Stop (s)"] > MAX_TIME))
143
163
  ]
144
164
 
165
+ # cut state events to interval
145
166
  df_interval.loc[df["Start (s)"] < MIN_TIME, "Start (s)"] = MIN_TIME
146
167
  df_interval.loc[df["Stop (s)"] > MAX_TIME, "Stop (s)"] = MAX_TIME
147
168
 
@@ -152,7 +173,7 @@ def plugin_df_filter(df: pd.DataFrame, observations_list: list = [], parameters:
152
173
  print("filtered")
153
174
  print("=" * 50)
154
175
 
155
- print(f"{df=}")
176
+ # print(f"{df=}")
156
177
 
157
178
  return df
158
179
 
@@ -205,8 +226,12 @@ def run_plugin(self, plugin_name):
205
226
  if not selected_observations:
206
227
  return
207
228
 
229
+ logging.info("preparing dtaaframe for plugin")
230
+
208
231
  df = project_functions.project2dataframe(self.pj, selected_observations)
209
232
 
233
+ logging.info("done")
234
+
210
235
  """
211
236
  logging.debug("dataframe info")
212
237
  logging.debug(f"{df.info()}")
@@ -214,7 +239,9 @@ def run_plugin(self, plugin_name):
214
239
  """
215
240
 
216
241
  # filter the dataframe with parameters
242
+ logging.info("filtering dataframe for plugin")
217
243
  filtered_df = plugin_df_filter(df, observations_list=selected_observations, parameters=parameters)
244
+ logging.info("done")
218
245
 
219
246
  plugin_results = plugin_module.run(filtered_df)
220
247
  # test if plugin_tests is a tuple: if not transform to tuple
@@ -1899,20 +1899,12 @@ def project2dataframe(pj: dict, observations_list: list = []) -> pd.DataFrame:
1899
1899
  if idx_event in stop_event_idx:
1900
1900
  continue
1901
1901
  data["Observation id"].append(obs_id)
1902
- data["Observation date"].append(pj["observations"][obs_id]["date"])
1903
- data["Description"].append(" ".join(pj["observations"][obs_id]["description"].splitlines()))
1904
- data["Observation type"].append(pj["observations"][obs_id]["type"])
1905
-
1906
- data["Observation interval start"].append(
1907
- pj["observations"][obs_id]["observation time interval"][0]
1908
- if pj["observations"][obs_id]["observation time interval"][0]
1909
- else None
1910
- )
1911
- data["Observation interval stop"].append(
1912
- pj["observations"][obs_id]["observation time interval"][1]
1913
- if pj["observations"][obs_id]["observation time interval"][1]
1914
- else None
1915
- )
1902
+ data["Observation date"].append(pj[cfg.OBSERVATIONS][obs_id]["date"])
1903
+ data["Description"].append(" ".join(pj[cfg.OBSERVATIONS][obs_id]["description"].splitlines()))
1904
+ data["Observation type"].append(pj[cfg.OBSERVATIONS][obs_id]["type"])
1905
+
1906
+ data["Observation interval start"].append(pj[cfg.OBSERVATIONS][obs_id].get(cfg.OBSERVATION_TIME_INTERVAL, [None, None])[0])
1907
+ data["Observation interval stop"].append(pj[cfg.OBSERVATIONS][obs_id].get(cfg.OBSERVATION_TIME_INTERVAL, [None, None])[1])
1916
1908
 
1917
1909
  # data["Source"].append("")
1918
1910
  # data["Time offset (s)"].append(pj["observations"][obs_id]["time offset"])
@@ -71,8 +71,10 @@ def synthetic_time_budget(self) -> None:
71
71
  selected_observations,
72
72
  start_coding=start_coding,
73
73
  end_coding=end_coding,
74
- start_interval=start_interval,
75
- end_interval=end_interval,
74
+ # start_interval=start_interval,
75
+ # end_interval=end_interval,
76
+ start_interval=None,
77
+ end_interval=None,
76
78
  maxTime=max_media_duration_all_obs,
77
79
  show_exclude_non_coded_behaviors=False,
78
80
  by_category=False,
@@ -201,8 +203,10 @@ def synthetic_binned_time_budget(self) -> None:
201
203
  selected_observations,
202
204
  start_coding=start_coding,
203
205
  end_coding=end_coding,
204
- start_interval=start_interval,
205
- end_interval=end_interval,
206
+ # start_interval=start_interval,
207
+ # end_interval=end_interval,
208
+ start_interval=None,
209
+ end_interval=None,
206
210
  maxTime=max_media_duration_all_obs,
207
211
  show_exclude_non_coded_behaviors=False,
208
212
  by_category=False,
@@ -221,7 +225,7 @@ def synthetic_binned_time_budget(self) -> None:
221
225
  # ask for excluding behaviors durations from total time
222
226
  cancel_pressed, synth_tb_param[cfg.EXCLUDED_BEHAVIORS] = self.filter_behaviors(
223
227
  title="Select behaviors to exclude",
224
- text=("The duration of the selected behaviors will " "be subtracted from the total time"),
228
+ text=("The duration of the selected behaviors will be subtracted from the total time"),
225
229
  table="",
226
230
  behavior_type=[cfg.STATE_EVENT],
227
231
  )
@@ -260,7 +260,7 @@ def synthetic_time_budget_bin(pj: dict, selected_observations: list, parameters_
260
260
  max_time = dec(end_time)
261
261
 
262
262
  if time_interval == cfg.TIME_OBS_INTERVAL:
263
- obs_interval = pj[cfg.OBSERVATIONS][obs_id][cfg.OBSERVATION_TIME_INTERVAL]
263
+ obs_interval = pj[cfg.OBSERVATIONS][obs_id].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0])
264
264
  offset = pj[cfg.OBSERVATIONS][obs_id][cfg.TIME_OFFSET]
265
265
  min_time = dec(obs_interval[0]) + offset
266
266
  # Use max media duration for max time if no interval is defined (=0)
@@ -436,8 +436,10 @@ def time_budget(self, mode: str, mode2: str = "list"):
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,
439
+ # start_interval=start_interval,
440
+ # end_interval=end_interval,
441
+ start_interval=None,
442
+ end_interval=None,
441
443
  maxTime=max_media_duration_all_obs,
442
444
  by_category=(mode == "by_category"),
443
445
  n_observations=len(selected_observations),
@@ -502,7 +504,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
502
504
  max_time = float(obs_length)
503
505
 
504
506
  if parameters[cfg.TIME_INTERVAL] == cfg.TIME_OBS_INTERVAL:
505
- obs_interval = self.pj[cfg.OBSERVATIONS][obsId][cfg.OBSERVATION_TIME_INTERVAL]
507
+ obs_interval = self.pj[cfg.OBSERVATIONS][obsId].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0])
506
508
  offset = float(self.pj[cfg.OBSERVATIONS][obsId][cfg.TIME_OFFSET])
507
509
  min_time = float(obs_interval[0]) + offset
508
510
  # Use max media duration for max time if no interval is defined (=0)
@@ -744,7 +746,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
744
746
 
745
747
  self.tb.twTB.resizeColumnsToContents()
746
748
 
747
- gui_utilities.restore_geometry(self.tb, "time budget", (0, 0))
749
+ gui_utilities.restore_geometry(self.tb, "time budget", (800, 600))
748
750
 
749
751
  self.tb.show()
750
752
 
@@ -1456,8 +1456,6 @@ def accurate_media_analysis(ffmpeg_bin: str, file_name: str) -> dict:
1456
1456
 
1457
1457
  ffprobe_results = ffprobe_media_analysis(ffmpeg_bin, file_name)
1458
1458
 
1459
- print(f"{ffprobe_results=}")
1460
-
1461
1459
  logging.debug(f"file_name: {file_name}")
1462
1460
  logging.debug(f"ffprobe_results: {ffprobe_results}")
1463
1461
 
@@ -20,5 +20,5 @@ This file is part of BORIS.
20
20
 
21
21
  """
22
22
 
23
- __version__ = "9.2"
24
- __version_date__ = "2025-03-17"
23
+ __version__ = "9.2.2"
24
+ __version_date__ = "2025-03-28"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: boris-behav-obs
3
- Version: 9.2
3
+ Version: 9.2.2
4
4
  Summary: BORIS - Behavioral Observation Research Interactive Software
5
5
  Author-email: Olivier Friard <olivier.friard@unito.it>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -686,13 +686,10 @@ Project-URL: Issues, https://github.com/olivierfriard/BORIS/issues
686
686
  Classifier: Topic :: Scientific/Engineering
687
687
  Classifier: Intended Audience :: Science/Research
688
688
  Classifier: Intended Audience :: Education
689
- Classifier: Programming Language :: Python :: 3.8
690
- Classifier: Programming Language :: Python :: 3.9
691
- Classifier: Programming Language :: Python :: 3.10
692
- Classifier: Programming Language :: Python :: 3.11
693
689
  Classifier: Programming Language :: Python :: 3.12
694
690
  Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
695
691
  Classifier: Operating System :: OS Independent
692
+ Classifier: Topic :: Scientific/Engineering
696
693
  Requires-Python: >=3.12
697
694
  Description-Content-Type: text/x-rst
698
695
  License-File: LICENSE.TXT
@@ -705,7 +702,7 @@ Requires-Dist: pyreadr
705
702
  Requires-Dist: pyside6==6.8.0.2
706
703
  Requires-Dist: hachoir>=3.3.0
707
704
  Provides-Extra: dev
708
- Requires-Dist: black; extra == "dev"
709
705
  Requires-Dist: ruff; extra == "dev"
710
706
  Requires-Dist: pytest; extra == "dev"
711
707
  Requires-Dist: pytest-cov; extra == "dev"
708
+ Dynamic: license-file
@@ -114,4 +114,5 @@ tests/test_otx_parser.py
114
114
  tests/test_preferences_gui.py
115
115
  tests/test_project_functions.py
116
116
  tests/test_time_budget.py
117
- tests/test_utilities.py
117
+ tests/test_utilities.py
118
+ tests/test_utilities2.py
@@ -8,7 +8,6 @@ pyside6==6.8.0.2
8
8
  hachoir>=3.3.0
9
9
 
10
10
  [dev]
11
- black
12
11
  ruff
13
12
  pytest
14
13
  pytest-cov