boris-behav-obs 8.16.6__py3-none-any.whl → 9.7.1__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.
Files changed (125) hide show
  1. boris/__init__.py +1 -1
  2. boris/__main__.py +1 -1
  3. boris/about.py +24 -40
  4. boris/add_modifier.py +88 -80
  5. boris/add_modifier_ui.py +235 -131
  6. boris/advanced_event_filtering.py +23 -29
  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 +16 -34
  23. boris/config.py +101 -49
  24. boris/config_file.py +55 -64
  25. boris/connections.py +105 -58
  26. boris/converters.py +13 -37
  27. boris/converters_ui.py +187 -110
  28. boris/cooccurence.py +250 -0
  29. boris/core.py +2108 -1275
  30. boris/core_qrc.py +15892 -10829
  31. boris/core_ui.py +941 -806
  32. boris/db_functions.py +17 -42
  33. boris/dev.py +134 -0
  34. boris/dialog.py +461 -242
  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 +405 -281
  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 +180 -203
  43. boris/export_observation.py +60 -73
  44. boris/external_processes.py +123 -98
  45. boris/geometric_measurement.py +427 -218
  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 +304 -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 +16 -6
  55. boris/modifier_coding_map_creator.py +1013 -0
  56. boris/modifiers_coding_map.py +7 -9
  57. boris/mpv2.py +127 -36
  58. boris/observation.py +493 -210
  59. boris/observation_operations.py +1010 -391
  60. boris/observation_ui.py +573 -363
  61. boris/observations_list.py +51 -58
  62. boris/otx_parser.py +74 -68
  63. boris/param_panel.py +45 -59
  64. boris/param_panel_ui.py +254 -138
  65. boris/player_dock_widget.py +91 -56
  66. boris/plot_data_module.py +18 -53
  67. boris/plot_events.py +56 -153
  68. boris/plot_events_rt.py +16 -30
  69. boris/plot_spectrogram_rt.py +80 -56
  70. boris/plot_waveform_rt.py +23 -48
  71. boris/plugins.py +431 -0
  72. boris/portion/__init__.py +18 -8
  73. boris/portion/const.py +35 -18
  74. boris/portion/dict.py +5 -5
  75. boris/portion/func.py +2 -2
  76. boris/portion/interval.py +21 -41
  77. boris/portion/io.py +41 -32
  78. boris/preferences.py +298 -123
  79. boris/preferences_ui.py +664 -225
  80. boris/project.py +293 -270
  81. boris/project_functions.py +610 -537
  82. boris/project_import_export.py +204 -213
  83. boris/project_ui.py +673 -441
  84. boris/qrc_boris.py +6 -3
  85. boris/qrc_boris5.py +6 -3
  86. boris/select_modifiers.py +62 -90
  87. boris/select_observations.py +19 -197
  88. boris/select_subj_behav.py +67 -39
  89. boris/state_events.py +51 -33
  90. boris/subjects_pad.py +6 -8
  91. boris/synthetic_time_budget.py +25 -17
  92. boris/time_budget_functions.py +169 -169
  93. boris/time_budget_widget.py +71 -86
  94. boris/transitions.py +41 -41
  95. boris/utilities.py +562 -222
  96. boris/version.py +3 -3
  97. boris/video_equalizer.py +16 -14
  98. boris/video_equalizer_ui.py +199 -130
  99. boris/video_operations.py +78 -28
  100. boris/view_df.py +104 -0
  101. boris/view_df_ui.py +75 -0
  102. boris/write_event.py +240 -136
  103. boris_behav_obs-9.7.1.dist-info/METADATA +140 -0
  104. boris_behav_obs-9.7.1.dist-info/RECORD +109 -0
  105. {boris_behav_obs-8.16.6.dist-info → boris_behav_obs-9.7.1.dist-info}/WHEEL +1 -1
  106. boris_behav_obs-9.7.1.dist-info/entry_points.txt +2 -0
  107. boris/README.TXT +0 -22
  108. boris/add_modifier.ui +0 -323
  109. boris/converters.ui +0 -289
  110. boris/core.qrc +0 -37
  111. boris/core.ui +0 -1571
  112. boris/edit_event.ui +0 -233
  113. boris/icons/logo_eye.ico +0 -0
  114. boris/map_creator.py +0 -982
  115. boris/observation.ui +0 -814
  116. boris/param_panel.ui +0 -379
  117. boris/preferences.ui +0 -537
  118. boris/project.ui +0 -1074
  119. boris/vlc_local.py +0 -90
  120. boris_behav_obs-8.16.6.dist-info/LICENSE.TXT +0 -674
  121. boris_behav_obs-8.16.6.dist-info/METADATA +0 -134
  122. boris_behav_obs-8.16.6.dist-info/RECORD +0 -106
  123. boris_behav_obs-8.16.6.dist-info/entry_points.txt +0 -2
  124. {boris → boris_behav_obs-9.7.1.dist-info/licenses}/LICENSE.TXT +0 -0
  125. {boris_behav_obs-8.16.6.dist-info → boris_behav_obs-9.7.1.dist-info}/top_level.txt +0 -0
boris/preferences.py CHANGED
@@ -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
 
@@ -22,26 +22,30 @@ This file is part of BORIS.
22
22
 
23
23
  import logging
24
24
  import os
25
- import pathlib
25
+ from pathlib import Path
26
26
  import sys
27
-
28
27
  from . import dialog
29
28
  from . import gui_utilities
30
29
  from . import menu_options
31
30
  from . import config as cfg
32
31
  from . import config_file
32
+ from . import plugins
33
33
 
34
34
  from .preferences_ui import Ui_prefDialog
35
35
 
36
- from PyQt5.QtWidgets import QDialog, QFileDialog
36
+ from PySide6.QtWidgets import QDialog, QFileDialog, QListWidgetItem, QMessageBox
37
+ from PySide6.QtCore import Qt
38
+ from PySide6.QtGui import QFont
37
39
 
38
40
 
39
41
  class Preferences(QDialog, Ui_prefDialog):
40
42
  def __init__(self, parent=None):
41
-
42
43
  super().__init__()
43
44
  self.setupUi(self)
44
45
 
46
+ # plugins
47
+ self.pb_browse_plugins_dir.clicked.connect(self.browse_plugins_dir)
48
+
45
49
  self.pbBrowseFFmpegCacheDir.clicked.connect(self.browseFFmpegCacheDir)
46
50
 
47
51
  self.pb_reset_behav_colors.clicked.connect(self.reset_behav_colors)
@@ -52,7 +56,40 @@ class Preferences(QDialog, Ui_prefDialog):
52
56
  self.pbCancel.clicked.connect(self.reject)
53
57
 
54
58
  self.flag_refresh = False
55
- self.flag_reset_frames_memory = False
59
+
60
+ # Create a monospace QFont
61
+ monospace_font = QFont("Courier New") # or "Monospace", "Consolas", "Liberation Mono", etc.
62
+ monospace_font.setStyleHint(QFont.Monospace)
63
+ monospace_font.setPointSize(12)
64
+ self.pte_plugin_code.setFont(monospace_font)
65
+
66
+ def browse_plugins_dir(self):
67
+ """
68
+ get the personal plugins directory
69
+ """
70
+ directory = QFileDialog.getExistingDirectory(None, "Select the plugins directory", self.le_personal_plugins_dir.text())
71
+ if not directory:
72
+ return
73
+
74
+ self.le_personal_plugins_dir.setText(directory)
75
+ self.lw_personal_plugins.clear()
76
+ for file_ in Path(directory).glob("*.py"):
77
+ if file_.name.startswith("_"):
78
+ continue
79
+ plugin_name = plugins.get_plugin_name(file_)
80
+ if plugin_name is None:
81
+ continue
82
+ # check if personal plugin name is in BORIS plugins (case sensitive)
83
+ if plugin_name in [self.lv_all_plugins.item(i).text() for i in range(self.lv_all_plugins.count())]:
84
+ continue
85
+ item = QListWidgetItem(plugin_name)
86
+ item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
87
+ item.setCheckState(Qt.Checked)
88
+ item.setData(100, str(file_))
89
+ self.lw_personal_plugins.addItem(item)
90
+
91
+ if self.lw_personal_plugins.count() == 0:
92
+ QMessageBox.warning(self, cfg.programName, f"No plugin found in {directory}")
56
93
 
57
94
  def refresh_preferences(self):
58
95
  """
@@ -61,7 +98,7 @@ class Preferences(QDialog, Ui_prefDialog):
61
98
  if (
62
99
  dialog.MessageDialog(
63
100
  "BORIS",
64
- ("Refresh will re-initialize " "all your preferences and close BORIS"),
101
+ ("Refresh will re-initialize all your preferences and close BORIS"),
65
102
  [cfg.CANCEL, "Refresh preferences"],
66
103
  )
67
104
  == "Refresh preferences"
@@ -73,11 +110,11 @@ class Preferences(QDialog, Ui_prefDialog):
73
110
  """
74
111
  allow user select a cache dir for ffmpeg images
75
112
  """
76
- FFmpegCacheDir = QFileDialog().getExistingDirectory(
113
+ FFmpegCacheDir = QFileDialog.getExistingDirectory(
77
114
  self,
78
115
  "Select a directory",
79
116
  os.path.expanduser("~"),
80
- options=QFileDialog().ShowDirsOnly,
117
+ options=QFileDialog.ShowDirsOnly,
81
118
  )
82
119
  if FFmpegCacheDir:
83
120
  self.leFFmpegCacheDir.setText(FFmpegCacheDir)
@@ -104,6 +141,63 @@ def preferences(self):
104
141
  show preferences window
105
142
  """
106
143
 
144
+ def show_plugin_info(item):
145
+ """
146
+ display information about the clicked plugin
147
+ """
148
+
149
+ if item.text() not in self.config_param[cfg.ANALYSIS_PLUGINS]:
150
+ return
151
+
152
+ plugin_path = item.data(100)
153
+
154
+ # Python plugins
155
+ if Path(plugin_path).suffix == ".py":
156
+ import importlib
157
+
158
+ module_name = Path(plugin_path).stem
159
+ spec = importlib.util.spec_from_file_location(module_name, plugin_path)
160
+ plugin_module = importlib.util.module_from_spec(spec)
161
+ spec.loader.exec_module(plugin_module)
162
+ attributes_list = dir(plugin_module)
163
+
164
+ out: list = []
165
+ out.append((plugin_module.__plugin_name__ + "\n") if "__plugin_name__" in attributes_list else "No plugin name provided")
166
+ out.append(plugin_module.__author__ if "__author__" in attributes_list else "No author provided")
167
+ version_str: str = ""
168
+ if "__version__" in attributes_list:
169
+ version_str += str(plugin_module.__version__)
170
+ if "__version_date__" in attributes_list:
171
+ version_str += " " if version_str else ""
172
+ version_str += f"({plugin_module.__version_date__})"
173
+
174
+ out.append(f"Version: {version_str}\n" if version_str else "No version provided")
175
+
176
+ # out.append(plugin_module.run.__doc__.strip())
177
+ # description
178
+ if "__description__" in attributes_list:
179
+ out.append("Description:\n")
180
+ out.append(plugin_module.__description__ if "__description__" in attributes_list else "No description provided")
181
+
182
+ preferencesWindow.pte_plugin_description.setPlainText("\n".join(out))
183
+
184
+ # R plugins
185
+ if Path(plugin_path).suffix == ".R":
186
+ plugin_description = plugins.get_r_plugin_description(plugin_path)
187
+ if plugin_description is not None:
188
+ preferencesWindow.pte_plugin_description.setPlainText("\n".join(plugin_description.split("\\n")))
189
+ else:
190
+ preferencesWindow.pte_plugin_description.setPlainText("No description provided")
191
+
192
+ # display plugin code
193
+ try:
194
+ with open(plugin_path, "r") as f_in:
195
+ plugin_code = f_in.read()
196
+ except Exception:
197
+ plugin_code = "Not available"
198
+
199
+ preferencesWindow.pte_plugin_code.setPlainText(plugin_code)
200
+
107
201
  preferencesWindow = Preferences()
108
202
  preferencesWindow.tabWidget.setCurrentIndex(0)
109
203
 
@@ -114,9 +208,7 @@ def preferences(self):
114
208
  preferencesWindow.cbTimeFormat.setCurrentIndex(1)
115
209
 
116
210
  preferencesWindow.sbffSpeed.setValue(self.fast)
117
- preferencesWindow.cb_adapt_fast_jump.setChecked(
118
- self.config_param.get(cfg.ADAPT_FAST_JUMP, False)
119
- )
211
+ preferencesWindow.cb_adapt_fast_jump.setChecked(self.config_param.get(cfg.ADAPT_FAST_JUMP, False))
120
212
  preferencesWindow.sbRepositionTimeOffset.setValue(self.repositioningTimeOffset)
121
213
  preferencesWindow.sbSpeedStep.setValue(self.play_rate_step)
122
214
  # automatic backup
@@ -132,15 +224,11 @@ def preferences(self):
132
224
  # alert no focal subject
133
225
  preferencesWindow.cbAlertNoFocalSubject.setChecked(self.alertNoFocalSubject)
134
226
  # tracking cursor above event
135
- preferencesWindow.cbTrackingCursorAboveEvent.setChecked(
136
- self.trackingCursorAboveEvent
137
- )
227
+ preferencesWindow.cbTrackingCursorAboveEvent.setChecked(self.trackingCursorAboveEvent)
138
228
  # check for new version
139
229
  preferencesWindow.cbCheckForNewVersion.setChecked(self.checkForNewVersion)
140
230
  # display subtitles
141
- preferencesWindow.cb_display_subtitles.setChecked(
142
- self.config_param[cfg.DISPLAY_SUBTITLES]
143
- )
231
+ preferencesWindow.cb_display_subtitles.setChecked(self.config_param.get(cfg.DISPLAY_SUBTITLES, False))
144
232
  # pause before add event
145
233
  preferencesWindow.cb_pause_before_addevent.setChecked(self.pause_before_addevent)
146
234
  # MPV hwdec
@@ -148,18 +236,77 @@ def preferences(self):
148
236
  preferencesWindow.cb_hwdec.addItems(cfg.MPV_HWDEC_OPTIONS)
149
237
  try:
150
238
  preferencesWindow.cb_hwdec.setCurrentIndex(
151
- cfg.MPV_HWDEC_OPTIONS.index(
152
- self.config_param.get(cfg.MPV_HWDEC, cfg.MPV_HWDEC_DEFAULT_VALUE)
153
- )
239
+ cfg.MPV_HWDEC_OPTIONS.index(self.config_param.get(cfg.MPV_HWDEC, cfg.MPV_HWDEC_DEFAULT_VALUE))
154
240
  )
155
241
  except Exception:
156
- preferencesWindow.cb_hwdec.setCurrentIndex(cfg.MPV_HWDEC_DEFAULT_VALUE)
242
+ preferencesWindow.cb_hwdec.setCurrentIndex(cfg.MPV_HWDEC_OPTIONS.index(cfg.MPV_HWDEC_DEFAULT_VALUE))
243
+ # check integrity
244
+ preferencesWindow.cb_check_integrity_at_opening.setChecked(self.config_param.get(cfg.CHECK_PROJECT_INTEGRITY, True))
245
+
246
+ # BORIS plugins
247
+ preferencesWindow.lv_all_plugins.itemClicked.connect(show_plugin_info)
248
+
249
+ preferencesWindow.lv_all_plugins.clear()
250
+
251
+ for file_ in (Path(__file__).parent / "analysis_plugins").glob("*.py"):
252
+ if file_.name.startswith("_"):
253
+ continue
254
+ plugin_name = plugins.get_plugin_name(file_)
255
+ if plugin_name is not None:
256
+ item = QListWidgetItem(plugin_name)
257
+ item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
258
+ if plugin_name in self.config_param.get(cfg.EXCLUDED_PLUGINS, set()):
259
+ item.setCheckState(Qt.Unchecked)
260
+ else:
261
+ item.setCheckState(Qt.Checked)
262
+ item.setData(100, str(file_))
263
+ preferencesWindow.lv_all_plugins.addItem(item)
264
+
265
+ # personal plugins
266
+ preferencesWindow.le_personal_plugins_dir.setText(self.config_param.get(cfg.PERSONAL_PLUGINS_DIR, ""))
267
+ preferencesWindow.lw_personal_plugins.itemClicked.connect(show_plugin_info)
268
+
269
+ preferencesWindow.lw_personal_plugins.clear()
270
+ if self.config_param.get(cfg.PERSONAL_PLUGINS_DIR, ""):
271
+ # Python plugins
272
+ for file_ in Path(self.config_param[cfg.PERSONAL_PLUGINS_DIR]).glob("*.py"):
273
+ if file_.name.startswith("_"):
274
+ continue
275
+ plugin_name = plugins.get_plugin_name(file_)
276
+ if plugin_name is None:
277
+ continue
278
+ # check if personal plugin name is in BORIS plugins (case sensitive)
279
+ if plugin_name in [preferencesWindow.lv_all_plugins.item(i).text() for i in range(preferencesWindow.lv_all_plugins.count())]:
280
+ continue
281
+ item = QListWidgetItem(plugin_name)
282
+ item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
283
+ if plugin_name in self.config_param.get(cfg.EXCLUDED_PLUGINS, set()):
284
+ item.setCheckState(Qt.Unchecked)
285
+ else:
286
+ item.setCheckState(Qt.Checked)
287
+ item.setData(100, str(file_))
288
+ preferencesWindow.lw_personal_plugins.addItem(item)
289
+
290
+ # R plugins
291
+ for file_ in Path(self.config_param[cfg.PERSONAL_PLUGINS_DIR]).glob("*.R"):
292
+ plugin_name = plugins.get_r_plugin_name(file_)
293
+ if plugin_name is None:
294
+ continue
295
+ # check if personal plugin name is in BORIS plugins (case sensitive)
296
+ if plugin_name in [preferencesWindow.lv_all_plugins.item(i).text() for i in range(preferencesWindow.lv_all_plugins.count())]:
297
+ continue
298
+ item = QListWidgetItem(plugin_name)
299
+ item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
300
+ if plugin_name in self.config_param.get(cfg.EXCLUDED_PLUGINS, set()):
301
+ item.setCheckState(Qt.Unchecked)
302
+ else:
303
+ item.setCheckState(Qt.Checked)
304
+ item.setData(100, str(file_))
305
+ preferencesWindow.lw_personal_plugins.addItem(item)
157
306
 
158
307
  # PROJET FILE INDENTATION
159
308
  preferencesWindow.combo_project_file_indentation.clear()
160
- preferencesWindow.combo_project_file_indentation.addItems(
161
- cfg.PROJECT_FILE_INDENTATION_COMBO_OPTIONS
162
- )
309
+ preferencesWindow.combo_project_file_indentation.addItems(cfg.PROJECT_FILE_INDENTATION_COMBO_OPTIONS)
163
310
  try:
164
311
  preferencesWindow.combo_project_file_indentation.setCurrentIndex(
165
312
  cfg.PROJECT_FILE_INDENTATION_OPTIONS.index(
@@ -172,9 +319,7 @@ def preferences(self):
172
319
  except Exception:
173
320
  preferencesWindow.combo_project_file_indentation.setCurrentText(
174
321
  cfg.PROJECT_FILE_INDENTATION_COMBO_OPTIONS[
175
- cfg.PROJECT_FILE_INDENTATION_OPTIONS.index(
176
- cfg.PROJECT_FILE_INDENTATION_DEFAULT_VALUE
177
- )
322
+ cfg.PROJECT_FILE_INDENTATION_OPTIONS.index(cfg.PROJECT_FILE_INDENTATION_DEFAULT_VALUE)
178
323
  ]
179
324
  )
180
325
 
@@ -186,29 +331,24 @@ def preferences(self):
186
331
  preferencesWindow.cbSpectrogramColorMap.clear()
187
332
  preferencesWindow.cbSpectrogramColorMap.addItems(cfg.SPECTROGRAM_COLOR_MAPS)
188
333
  try:
189
- preferencesWindow.cbSpectrogramColorMap.setCurrentIndex(
190
- cfg.SPECTROGRAM_COLOR_MAPS.index(self.spectrogram_color_map)
191
- )
192
- except Exception:
193
- preferencesWindow.cbSpectrogramColorMap.setCurrentIndex(
194
- cfg.SPECTROGRAM_COLOR_MAPS.index(cfg.SPECTROGRAM_DEFAULT_COLOR_MAP)
195
- )
196
-
197
- try:
198
- preferencesWindow.cbSpectrogramColorMap.setCurrentIndex(
199
- cfg.SPECTROGRAM_COLOR_MAPS.index(self.spectrogram_color_map)
200
- )
334
+ preferencesWindow.cbSpectrogramColorMap.setCurrentIndex(cfg.SPECTROGRAM_COLOR_MAPS.index(self.spectrogram_color_map))
201
335
  except Exception:
202
- preferencesWindow.cbSpectrogramColorMap.setCurrentIndex(
203
- cfg.SPECTROGRAM_COLOR_MAPS.index(cfg.SPECTROGRAM_DEFAULT_COLOR_MAP)
204
- )
205
-
336
+ preferencesWindow.cbSpectrogramColorMap.setCurrentIndex(cfg.SPECTROGRAM_COLOR_MAPS.index(cfg.SPECTROGRAM_DEFAULT_COLOR_MAP))
337
+ # time interval
206
338
  try:
207
339
  preferencesWindow.sb_time_interval.setValue(self.spectrogram_time_interval)
208
340
  except Exception:
209
- preferencesWindow.sb_time_interval.setValue(
210
- cfg.SPECTROGRAM_DEFAULT_TIME_INTERVAL
211
- )
341
+ preferencesWindow.sb_time_interval.setValue(cfg.SPECTROGRAM_DEFAULT_TIME_INTERVAL)
342
+ # window type
343
+ preferencesWindow.cb_window_type.setCurrentText(self.config_param.get(cfg.SPECTROGRAM_WINDOW_TYPE, cfg.SPECTROGRAM_DEFAULT_WINDOW_TYPE))
344
+ # NFFT
345
+ preferencesWindow.cb_NFFT.setCurrentText(self.config_param.get(cfg.SPECTROGRAM_NFFT, cfg.SPECTROGRAM_DEFAULT_NFFT))
346
+ # noverlap
347
+ preferencesWindow.sb_noverlap.setValue(self.config_param.get(cfg.SPECTROGRAM_NOVERLAP, cfg.SPECTROGRAM_DEFAULT_NOVERLAP))
348
+ # vmin
349
+ preferencesWindow.sb_vmin.setValue(self.config_param.get(cfg.SPECTROGRAM_VMIN, cfg.SPECTROGRAM_DEFAULT_VMIN))
350
+ # vmax
351
+ preferencesWindow.sb_vmax.setValue(self.config_param.get(cfg.SPECTROGRAM_VMAX, cfg.SPECTROGRAM_DEFAULT_VMAX))
212
352
 
213
353
  # behavior colors
214
354
  if not self.plot_colors:
@@ -218,109 +358,144 @@ def preferences(self):
218
358
  # category colors
219
359
  if not self.behav_category_colors:
220
360
  self.behav_category_colors = cfg.CATEGORY_COLORS_LIST
221
- preferencesWindow.te_category_colors.setPlainText(
222
- "\n".join(self.behav_category_colors)
223
- )
361
+ preferencesWindow.te_category_colors.setPlainText("\n".join(self.behav_category_colors))
362
+
363
+ # interface
364
+ preferencesWindow.sb_toolbar_icon_size.setValue(self.config_param.get(cfg.TOOLBAR_ICON_SIZE, cfg.DEFAULT_TOOLBAR_ICON_SIZE_VALUE))
224
365
 
225
366
  gui_utilities.restore_geometry(preferencesWindow, "preferences", (700, 500))
226
367
 
227
- if preferencesWindow.exec_():
368
+ while True:
369
+ if preferencesWindow.exec():
370
+ if preferencesWindow.sb_vmin.value() >= preferencesWindow.sb_vmax.value():
371
+ QMessageBox.warning(self, cfg.programName, "Spectrogram parameters: the vmin value must be lower than the vmax value.")
372
+ continue
228
373
 
229
- gui_utilities.save_geometry(preferencesWindow, "preferences")
374
+ if preferencesWindow.sb_noverlap.value() >= int(preferencesWindow.cb_NFFT.currentText()):
375
+ QMessageBox.warning(self, cfg.programName, "Spectrogram parameters: the noverlap value must be lower than the NFFT value.")
376
+ continue
230
377
 
231
- if preferencesWindow.flag_refresh:
232
- # refresh preferences remove the config file
378
+ gui_utilities.save_geometry(preferencesWindow, "preferences")
233
379
 
234
- logging.debug("flag refresh ")
380
+ if preferencesWindow.flag_refresh:
381
+ # refresh preferences remove the config file
235
382
 
236
- self.config_param["refresh_preferences"] = True
237
- self.close()
238
- # check if refresh canceled for not saved project
239
- if "refresh_preferences" in self.config_param:
240
- if (pathlib.Path.home() / ".boris").exists():
241
- os.remove(pathlib.Path.home() / ".boris")
242
- sys.exit()
383
+ logging.debug("flag refresh ")
243
384
 
244
- if preferencesWindow.cbTimeFormat.currentIndex() == 0:
245
- self.timeFormat = cfg.S
385
+ self.config_param["refresh_preferences"] = True
386
+ self.close()
387
+ # check if refresh canceled for not saved project
388
+ if "refresh_preferences" in self.config_param:
389
+ if (Path.home() / ".boris").exists():
390
+ os.remove(Path.home() / ".boris")
391
+ sys.exit()
246
392
 
247
- if preferencesWindow.cbTimeFormat.currentIndex() == 1:
248
- self.timeFormat = cfg.HHMMSS
393
+ if preferencesWindow.cbTimeFormat.currentIndex() == 0:
394
+ self.timeFormat = cfg.S
249
395
 
250
- self.fast = preferencesWindow.sbffSpeed.value()
396
+ if preferencesWindow.cbTimeFormat.currentIndex() == 1:
397
+ self.timeFormat = cfg.HHMMSS
251
398
 
252
- self.config_param[
253
- cfg.ADAPT_FAST_JUMP
254
- ] = preferencesWindow.cb_adapt_fast_jump.isChecked()
399
+ self.fast = preferencesWindow.sbffSpeed.value()
255
400
 
256
- self.repositioningTimeOffset = preferencesWindow.sbRepositionTimeOffset.value()
401
+ self.config_param[cfg.ADAPT_FAST_JUMP] = preferencesWindow.cb_adapt_fast_jump.isChecked()
257
402
 
258
- self.play_rate_step = preferencesWindow.sbSpeedStep.value()
403
+ self.repositioningTimeOffset = preferencesWindow.sbRepositionTimeOffset.value()
259
404
 
260
- self.automaticBackup = preferencesWindow.sbAutomaticBackup.value()
261
- if self.automaticBackup:
262
- self.automaticBackupTimer.start(self.automaticBackup * 60000)
263
- else:
264
- self.automaticBackupTimer.stop()
405
+ self.play_rate_step = preferencesWindow.sbSpeedStep.value()
265
406
 
266
- self.behav_seq_separator = preferencesWindow.leSeparator.text()
407
+ self.automaticBackup = preferencesWindow.sbAutomaticBackup.value()
408
+ if self.automaticBackup:
409
+ self.automaticBackupTimer.start(self.automaticBackup * 60000)
410
+ else:
411
+ self.automaticBackupTimer.stop()
267
412
 
268
- self.close_the_same_current_event = (
269
- preferencesWindow.cbCloseSameEvent.isChecked()
270
- )
413
+ self.behav_seq_separator = preferencesWindow.leSeparator.text()
271
414
 
272
- self.confirmSound = preferencesWindow.cbConfirmSound.isChecked()
415
+ self.close_the_same_current_event = preferencesWindow.cbCloseSameEvent.isChecked()
273
416
 
274
- self.beep_every = preferencesWindow.sbBeepEvery.value()
417
+ self.confirmSound = preferencesWindow.cbConfirmSound.isChecked()
275
418
 
276
- self.alertNoFocalSubject = preferencesWindow.cbAlertNoFocalSubject.isChecked()
419
+ self.beep_every = preferencesWindow.sbBeepEvery.value()
277
420
 
278
- self.trackingCursorAboveEvent = (
279
- preferencesWindow.cbTrackingCursorAboveEvent.isChecked()
280
- )
421
+ self.alertNoFocalSubject = preferencesWindow.cbAlertNoFocalSubject.isChecked()
281
422
 
282
- self.checkForNewVersion = preferencesWindow.cbCheckForNewVersion.isChecked()
423
+ self.trackingCursorAboveEvent = preferencesWindow.cbTrackingCursorAboveEvent.isChecked()
283
424
 
284
- self.config_param[
285
- cfg.DISPLAY_SUBTITLES
286
- ] = preferencesWindow.cb_display_subtitles.isChecked()
425
+ self.checkForNewVersion = preferencesWindow.cbCheckForNewVersion.isChecked()
287
426
 
288
- self.pause_before_addevent = (
289
- preferencesWindow.cb_pause_before_addevent.isChecked()
290
- )
427
+ self.config_param[cfg.DISPLAY_SUBTITLES] = preferencesWindow.cb_display_subtitles.isChecked()
291
428
 
292
- # MPV hwdec
293
- self.config_param[cfg.MPV_HWDEC] = cfg.MPV_HWDEC_OPTIONS[
294
- preferencesWindow.cb_hwdec.currentIndex()
295
- ]
429
+ self.pause_before_addevent = preferencesWindow.cb_pause_before_addevent.isChecked()
296
430
 
297
- # project file indentation
298
- self.config_param[
299
- cfg.PROJECT_FILE_INDENTATION
300
- ] = cfg.PROJECT_FILE_INDENTATION_OPTIONS[
301
- preferencesWindow.combo_project_file_indentation.currentIndex()
302
- ]
431
+ # MPV hwdec
432
+ self.config_param[cfg.MPV_HWDEC] = cfg.MPV_HWDEC_OPTIONS[preferencesWindow.cb_hwdec.currentIndex()]
303
433
 
304
- if self.observationId:
305
- self.load_tw_events(self.observationId)
306
- self.display_statusbar_info(self.observationId)
434
+ # check project integrity
435
+ self.config_param[cfg.CHECK_PROJECT_INTEGRITY] = preferencesWindow.cb_check_integrity_at_opening.isChecked()
307
436
 
308
- self.ffmpeg_cache_dir = preferencesWindow.leFFmpegCacheDir.text()
437
+ # update BORIS analysis plugins
438
+ self.config_param[cfg.ANALYSIS_PLUGINS] = {}
439
+ self.config_param[cfg.EXCLUDED_PLUGINS] = set()
440
+ for i in range(preferencesWindow.lv_all_plugins.count()):
441
+ if preferencesWindow.lv_all_plugins.item(i).checkState() == Qt.Checked:
442
+ self.config_param[cfg.ANALYSIS_PLUGINS][preferencesWindow.lv_all_plugins.item(i).text()] = (
443
+ preferencesWindow.lv_all_plugins.item(i).data(100)
444
+ )
445
+ else:
446
+ self.config_param[cfg.EXCLUDED_PLUGINS].add(preferencesWindow.lv_all_plugins.item(i).text())
309
447
 
310
- # spectrogram
311
- self.spectrogram_color_map = (
312
- preferencesWindow.cbSpectrogramColorMap.currentText()
313
- )
314
- # self.spectrogramHeight = preferencesWindow.sbSpectrogramHeight.value()
315
- self.spectrogram_time_interval = preferencesWindow.sb_time_interval.value()
316
-
317
- # behav colors
318
- self.plot_colors = preferencesWindow.te_behav_colors.toPlainText().split()
319
- # category colors
320
- self.behav_category_colors = (
321
- preferencesWindow.te_category_colors.toPlainText().split()
322
- )
448
+ # update personal plugins
449
+ self.config_param[cfg.PERSONAL_PLUGINS_DIR] = preferencesWindow.le_personal_plugins_dir.text()
450
+ for i in range(preferencesWindow.lw_personal_plugins.count()):
451
+ if preferencesWindow.lw_personal_plugins.item(i).checkState() == Qt.Checked:
452
+ self.config_param[cfg.ANALYSIS_PLUGINS][preferencesWindow.lw_personal_plugins.item(i).text()] = (
453
+ preferencesWindow.lw_personal_plugins.item(i).data(100)
454
+ )
455
+ else:
456
+ self.config_param[cfg.EXCLUDED_PLUGINS].add(preferencesWindow.lw_personal_plugins.item(i).text())
457
+
458
+ plugins.load_plugins(self)
459
+ plugins.add_plugins_to_menu(self)
323
460
 
324
- menu_options.update_menu(self)
461
+ # project file indentation
462
+ self.config_param[cfg.PROJECT_FILE_INDENTATION] = cfg.PROJECT_FILE_INDENTATION_OPTIONS[
463
+ preferencesWindow.combo_project_file_indentation.currentIndex()
464
+ ]
465
+
466
+ if self.observationId:
467
+ self.load_tw_events(self.observationId)
468
+ self.display_statusbar_info(self.observationId)
469
+
470
+ self.ffmpeg_cache_dir = preferencesWindow.leFFmpegCacheDir.text()
471
+
472
+ # spectrogram
473
+ self.spectrogram_color_map = preferencesWindow.cbSpectrogramColorMap.currentText()
474
+ self.spectrogram_time_interval = preferencesWindow.sb_time_interval.value()
475
+ # window type
476
+ self.config_param[cfg.SPECTROGRAM_WINDOW_TYPE] = preferencesWindow.cb_window_type.currentText()
477
+ # NFFT
478
+ self.config_param[cfg.SPECTROGRAM_NFFT] = preferencesWindow.cb_NFFT.currentText()
479
+ # noverlap
480
+ self.config_param[cfg.SPECTROGRAM_NOVERLAP] = preferencesWindow.sb_noverlap.value()
481
+ # vmin
482
+ self.config_param[cfg.SPECTROGRAM_VMIN] = preferencesWindow.sb_vmin.value()
483
+ # vmax
484
+ self.config_param[cfg.SPECTROGRAM_VMAX] = preferencesWindow.sb_vmax.value()
485
+
486
+ # behav colors
487
+ self.plot_colors = preferencesWindow.te_behav_colors.toPlainText().split()
488
+ # category colors
489
+ self.behav_category_colors = preferencesWindow.te_category_colors.toPlainText().split()
325
490
 
326
- config_file.save(self)
491
+ # interface
492
+ self.config_param[cfg.TOOLBAR_ICON_SIZE] = preferencesWindow.sb_toolbar_icon_size.value()
493
+
494
+ menu_options.update_menu(self)
495
+
496
+ config_file.save(self)
497
+
498
+ break
499
+
500
+ else:
501
+ break