boris-behav-obs 8.16.6__py3-none-any.whl → 9.7.2__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 +108 -49
  24. boris/config_file.py +58 -67
  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 +2106 -1277
  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 +304 -123
  79. boris/preferences_ui.py +684 -227
  80. boris/project.py +293 -270
  81. boris/project_functions.py +618 -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.2.dist-info/METADATA +140 -0
  104. boris_behav_obs-9.7.2.dist-info/RECORD +109 -0
  105. {boris_behav_obs-8.16.6.dist-info → boris_behav_obs-9.7.2.dist-info}/WHEEL +1 -1
  106. boris_behav_obs-9.7.2.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.2.dist-info/licenses}/LICENSE.TXT +0 -0
  125. {boris_behav_obs-8.16.6.dist-info → boris_behav_obs-9.7.2.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
@@ -129,18 +221,17 @@ def preferences(self):
129
221
  preferencesWindow.cbConfirmSound.setChecked(self.confirmSound)
130
222
  # beep every
131
223
  preferencesWindow.sbBeepEvery.setValue(self.beep_every)
224
+ # frame step size
225
+ #preferencesWindow.sb_frame_step_size.setValue(self.config_param.get(cfg.FRAME_STEP_SIZE, cfg.FRAME_STEP_SIZE_DEFAULT_VALUE))
226
+
132
227
  # alert no focal subject
133
228
  preferencesWindow.cbAlertNoFocalSubject.setChecked(self.alertNoFocalSubject)
134
229
  # tracking cursor above event
135
- preferencesWindow.cbTrackingCursorAboveEvent.setChecked(
136
- self.trackingCursorAboveEvent
137
- )
230
+ preferencesWindow.cbTrackingCursorAboveEvent.setChecked(self.trackingCursorAboveEvent)
138
231
  # check for new version
139
232
  preferencesWindow.cbCheckForNewVersion.setChecked(self.checkForNewVersion)
140
233
  # display subtitles
141
- preferencesWindow.cb_display_subtitles.setChecked(
142
- self.config_param[cfg.DISPLAY_SUBTITLES]
143
- )
234
+ preferencesWindow.cb_display_subtitles.setChecked(self.config_param.get(cfg.DISPLAY_SUBTITLES, False))
144
235
  # pause before add event
145
236
  preferencesWindow.cb_pause_before_addevent.setChecked(self.pause_before_addevent)
146
237
  # MPV hwdec
@@ -148,18 +239,77 @@ def preferences(self):
148
239
  preferencesWindow.cb_hwdec.addItems(cfg.MPV_HWDEC_OPTIONS)
149
240
  try:
150
241
  preferencesWindow.cb_hwdec.setCurrentIndex(
151
- cfg.MPV_HWDEC_OPTIONS.index(
152
- self.config_param.get(cfg.MPV_HWDEC, cfg.MPV_HWDEC_DEFAULT_VALUE)
153
- )
242
+ cfg.MPV_HWDEC_OPTIONS.index(self.config_param.get(cfg.MPV_HWDEC, cfg.MPV_HWDEC_DEFAULT_VALUE))
154
243
  )
155
244
  except Exception:
156
- preferencesWindow.cb_hwdec.setCurrentIndex(cfg.MPV_HWDEC_DEFAULT_VALUE)
245
+ preferencesWindow.cb_hwdec.setCurrentIndex(cfg.MPV_HWDEC_OPTIONS.index(cfg.MPV_HWDEC_DEFAULT_VALUE))
246
+ # check integrity
247
+ preferencesWindow.cb_check_integrity_at_opening.setChecked(self.config_param.get(cfg.CHECK_PROJECT_INTEGRITY, True))
248
+
249
+ # BORIS plugins
250
+ preferencesWindow.lv_all_plugins.itemClicked.connect(show_plugin_info)
251
+
252
+ preferencesWindow.lv_all_plugins.clear()
253
+
254
+ for file_ in (Path(__file__).parent / "analysis_plugins").glob("*.py"):
255
+ if file_.name.startswith("_"):
256
+ continue
257
+ plugin_name = plugins.get_plugin_name(file_)
258
+ if plugin_name is not None:
259
+ item = QListWidgetItem(plugin_name)
260
+ item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
261
+ if plugin_name in self.config_param.get(cfg.EXCLUDED_PLUGINS, set()):
262
+ item.setCheckState(Qt.Unchecked)
263
+ else:
264
+ item.setCheckState(Qt.Checked)
265
+ item.setData(100, str(file_))
266
+ preferencesWindow.lv_all_plugins.addItem(item)
267
+
268
+ # personal plugins
269
+ preferencesWindow.le_personal_plugins_dir.setText(self.config_param.get(cfg.PERSONAL_PLUGINS_DIR, ""))
270
+ preferencesWindow.lw_personal_plugins.itemClicked.connect(show_plugin_info)
271
+
272
+ preferencesWindow.lw_personal_plugins.clear()
273
+ if self.config_param.get(cfg.PERSONAL_PLUGINS_DIR, ""):
274
+ # Python plugins
275
+ for file_ in Path(self.config_param[cfg.PERSONAL_PLUGINS_DIR]).glob("*.py"):
276
+ if file_.name.startswith("_"):
277
+ continue
278
+ plugin_name = plugins.get_plugin_name(file_)
279
+ if plugin_name is None:
280
+ continue
281
+ # check if personal plugin name is in BORIS plugins (case sensitive)
282
+ if plugin_name in [preferencesWindow.lv_all_plugins.item(i).text() for i in range(preferencesWindow.lv_all_plugins.count())]:
283
+ continue
284
+ item = QListWidgetItem(plugin_name)
285
+ item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
286
+ if plugin_name in self.config_param.get(cfg.EXCLUDED_PLUGINS, set()):
287
+ item.setCheckState(Qt.Unchecked)
288
+ else:
289
+ item.setCheckState(Qt.Checked)
290
+ item.setData(100, str(file_))
291
+ preferencesWindow.lw_personal_plugins.addItem(item)
292
+
293
+ # R plugins
294
+ for file_ in Path(self.config_param[cfg.PERSONAL_PLUGINS_DIR]).glob("*.R"):
295
+ plugin_name = plugins.get_r_plugin_name(file_)
296
+ if plugin_name is None:
297
+ continue
298
+ # check if personal plugin name is in BORIS plugins (case sensitive)
299
+ if plugin_name in [preferencesWindow.lv_all_plugins.item(i).text() for i in range(preferencesWindow.lv_all_plugins.count())]:
300
+ continue
301
+ item = QListWidgetItem(plugin_name)
302
+ item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
303
+ if plugin_name in self.config_param.get(cfg.EXCLUDED_PLUGINS, set()):
304
+ item.setCheckState(Qt.Unchecked)
305
+ else:
306
+ item.setCheckState(Qt.Checked)
307
+ item.setData(100, str(file_))
308
+ preferencesWindow.lw_personal_plugins.addItem(item)
157
309
 
158
310
  # PROJET FILE INDENTATION
159
311
  preferencesWindow.combo_project_file_indentation.clear()
160
- preferencesWindow.combo_project_file_indentation.addItems(
161
- cfg.PROJECT_FILE_INDENTATION_COMBO_OPTIONS
162
- )
312
+ preferencesWindow.combo_project_file_indentation.addItems(cfg.PROJECT_FILE_INDENTATION_COMBO_OPTIONS)
163
313
  try:
164
314
  preferencesWindow.combo_project_file_indentation.setCurrentIndex(
165
315
  cfg.PROJECT_FILE_INDENTATION_OPTIONS.index(
@@ -172,9 +322,7 @@ def preferences(self):
172
322
  except Exception:
173
323
  preferencesWindow.combo_project_file_indentation.setCurrentText(
174
324
  cfg.PROJECT_FILE_INDENTATION_COMBO_OPTIONS[
175
- cfg.PROJECT_FILE_INDENTATION_OPTIONS.index(
176
- cfg.PROJECT_FILE_INDENTATION_DEFAULT_VALUE
177
- )
325
+ cfg.PROJECT_FILE_INDENTATION_OPTIONS.index(cfg.PROJECT_FILE_INDENTATION_DEFAULT_VALUE)
178
326
  ]
179
327
  )
180
328
 
@@ -186,29 +334,24 @@ def preferences(self):
186
334
  preferencesWindow.cbSpectrogramColorMap.clear()
187
335
  preferencesWindow.cbSpectrogramColorMap.addItems(cfg.SPECTROGRAM_COLOR_MAPS)
188
336
  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
- )
337
+ preferencesWindow.cbSpectrogramColorMap.setCurrentIndex(cfg.SPECTROGRAM_COLOR_MAPS.index(self.spectrogram_color_map))
201
338
  except Exception:
202
- preferencesWindow.cbSpectrogramColorMap.setCurrentIndex(
203
- cfg.SPECTROGRAM_COLOR_MAPS.index(cfg.SPECTROGRAM_DEFAULT_COLOR_MAP)
204
- )
205
-
339
+ preferencesWindow.cbSpectrogramColorMap.setCurrentIndex(cfg.SPECTROGRAM_COLOR_MAPS.index(cfg.SPECTROGRAM_DEFAULT_COLOR_MAP))
340
+ # time interval
206
341
  try:
207
342
  preferencesWindow.sb_time_interval.setValue(self.spectrogram_time_interval)
208
343
  except Exception:
209
- preferencesWindow.sb_time_interval.setValue(
210
- cfg.SPECTROGRAM_DEFAULT_TIME_INTERVAL
211
- )
344
+ preferencesWindow.sb_time_interval.setValue(cfg.SPECTROGRAM_DEFAULT_TIME_INTERVAL)
345
+ # window type
346
+ preferencesWindow.cb_window_type.setCurrentText(self.config_param.get(cfg.SPECTROGRAM_WINDOW_TYPE, cfg.SPECTROGRAM_DEFAULT_WINDOW_TYPE))
347
+ # NFFT
348
+ preferencesWindow.cb_NFFT.setCurrentText(self.config_param.get(cfg.SPECTROGRAM_NFFT, cfg.SPECTROGRAM_DEFAULT_NFFT))
349
+ # noverlap
350
+ preferencesWindow.sb_noverlap.setValue(self.config_param.get(cfg.SPECTROGRAM_NOVERLAP, cfg.SPECTROGRAM_DEFAULT_NOVERLAP))
351
+ # vmin
352
+ preferencesWindow.sb_vmin.setValue(self.config_param.get(cfg.SPECTROGRAM_VMIN, cfg.SPECTROGRAM_DEFAULT_VMIN))
353
+ # vmax
354
+ preferencesWindow.sb_vmax.setValue(self.config_param.get(cfg.SPECTROGRAM_VMAX, cfg.SPECTROGRAM_DEFAULT_VMAX))
212
355
 
213
356
  # behavior colors
214
357
  if not self.plot_colors:
@@ -218,109 +361,147 @@ def preferences(self):
218
361
  # category colors
219
362
  if not self.behav_category_colors:
220
363
  self.behav_category_colors = cfg.CATEGORY_COLORS_LIST
221
- preferencesWindow.te_category_colors.setPlainText(
222
- "\n".join(self.behav_category_colors)
223
- )
364
+ preferencesWindow.te_category_colors.setPlainText("\n".join(self.behav_category_colors))
365
+
366
+ # interface
367
+ preferencesWindow.sb_toolbar_icon_size.setValue(self.config_param.get(cfg.TOOLBAR_ICON_SIZE, cfg.DEFAULT_TOOLBAR_ICON_SIZE_VALUE))
224
368
 
225
369
  gui_utilities.restore_geometry(preferencesWindow, "preferences", (700, 500))
226
370
 
227
- if preferencesWindow.exec_():
371
+ while True:
372
+ if preferencesWindow.exec():
373
+ if preferencesWindow.sb_vmin.value() >= preferencesWindow.sb_vmax.value():
374
+ QMessageBox.warning(self, cfg.programName, "Spectrogram parameters: the vmin value must be lower than the vmax value.")
375
+ continue
228
376
 
229
- gui_utilities.save_geometry(preferencesWindow, "preferences")
377
+ if preferencesWindow.sb_noverlap.value() >= int(preferencesWindow.cb_NFFT.currentText()):
378
+ QMessageBox.warning(self, cfg.programName, "Spectrogram parameters: the noverlap value must be lower than the NFFT value.")
379
+ continue
230
380
 
231
- if preferencesWindow.flag_refresh:
232
- # refresh preferences remove the config file
381
+ gui_utilities.save_geometry(preferencesWindow, "preferences")
233
382
 
234
- logging.debug("flag refresh ")
383
+ if preferencesWindow.flag_refresh:
384
+ # refresh preferences remove the config file
235
385
 
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()
386
+ logging.debug("flag refresh ")
243
387
 
244
- if preferencesWindow.cbTimeFormat.currentIndex() == 0:
245
- self.timeFormat = cfg.S
388
+ self.config_param["refresh_preferences"] = True
389
+ self.close()
390
+ # check if refresh canceled for not saved project
391
+ if "refresh_preferences" in self.config_param:
392
+ if (Path.home() / ".boris").exists():
393
+ os.remove(Path.home() / ".boris")
394
+ sys.exit()
246
395
 
247
- if preferencesWindow.cbTimeFormat.currentIndex() == 1:
248
- self.timeFormat = cfg.HHMMSS
396
+ if preferencesWindow.cbTimeFormat.currentIndex() == 0:
397
+ self.timeFormat = cfg.S
249
398
 
250
- self.fast = preferencesWindow.sbffSpeed.value()
399
+ if preferencesWindow.cbTimeFormat.currentIndex() == 1:
400
+ self.timeFormat = cfg.HHMMSS
251
401
 
252
- self.config_param[
253
- cfg.ADAPT_FAST_JUMP
254
- ] = preferencesWindow.cb_adapt_fast_jump.isChecked()
402
+ self.fast = preferencesWindow.sbffSpeed.value()
255
403
 
256
- self.repositioningTimeOffset = preferencesWindow.sbRepositionTimeOffset.value()
404
+ self.config_param[cfg.ADAPT_FAST_JUMP] = preferencesWindow.cb_adapt_fast_jump.isChecked()
257
405
 
258
- self.play_rate_step = preferencesWindow.sbSpeedStep.value()
406
+ self.repositioningTimeOffset = preferencesWindow.sbRepositionTimeOffset.value()
259
407
 
260
- self.automaticBackup = preferencesWindow.sbAutomaticBackup.value()
261
- if self.automaticBackup:
262
- self.automaticBackupTimer.start(self.automaticBackup * 60000)
263
- else:
264
- self.automaticBackupTimer.stop()
408
+ self.play_rate_step = preferencesWindow.sbSpeedStep.value()
265
409
 
266
- self.behav_seq_separator = preferencesWindow.leSeparator.text()
410
+ self.automaticBackup = preferencesWindow.sbAutomaticBackup.value()
411
+ if self.automaticBackup:
412
+ self.automaticBackupTimer.start(self.automaticBackup * 60000)
413
+ else:
414
+ self.automaticBackupTimer.stop()
267
415
 
268
- self.close_the_same_current_event = (
269
- preferencesWindow.cbCloseSameEvent.isChecked()
270
- )
416
+ self.behav_seq_separator = preferencesWindow.leSeparator.text()
271
417
 
272
- self.confirmSound = preferencesWindow.cbConfirmSound.isChecked()
418
+ self.close_the_same_current_event = preferencesWindow.cbCloseSameEvent.isChecked()
273
419
 
274
- self.beep_every = preferencesWindow.sbBeepEvery.value()
420
+ self.confirmSound = preferencesWindow.cbConfirmSound.isChecked()
275
421
 
276
- self.alertNoFocalSubject = preferencesWindow.cbAlertNoFocalSubject.isChecked()
422
+ self.beep_every = preferencesWindow.sbBeepEvery.value()
277
423
 
278
- self.trackingCursorAboveEvent = (
279
- preferencesWindow.cbTrackingCursorAboveEvent.isChecked()
280
- )
424
+ # frame step size
425
+ #self.config_param[cfg.FRAME_STEP_SIZE] = preferencesWindow.sb_frame_step_size.value()
281
426
 
282
- self.checkForNewVersion = preferencesWindow.cbCheckForNewVersion.isChecked()
427
+ self.alertNoFocalSubject = preferencesWindow.cbAlertNoFocalSubject.isChecked()
283
428
 
284
- self.config_param[
285
- cfg.DISPLAY_SUBTITLES
286
- ] = preferencesWindow.cb_display_subtitles.isChecked()
429
+ self.trackingCursorAboveEvent = preferencesWindow.cbTrackingCursorAboveEvent.isChecked()
287
430
 
288
- self.pause_before_addevent = (
289
- preferencesWindow.cb_pause_before_addevent.isChecked()
290
- )
431
+ self.checkForNewVersion = preferencesWindow.cbCheckForNewVersion.isChecked()
291
432
 
292
- # MPV hwdec
293
- self.config_param[cfg.MPV_HWDEC] = cfg.MPV_HWDEC_OPTIONS[
294
- preferencesWindow.cb_hwdec.currentIndex()
295
- ]
433
+ self.config_param[cfg.DISPLAY_SUBTITLES] = preferencesWindow.cb_display_subtitles.isChecked()
296
434
 
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
- ]
435
+ self.pause_before_addevent = preferencesWindow.cb_pause_before_addevent.isChecked()
303
436
 
304
- if self.observationId:
305
- self.load_tw_events(self.observationId)
306
- self.display_statusbar_info(self.observationId)
437
+ # MPV hwdec
438
+ self.config_param[cfg.MPV_HWDEC] = cfg.MPV_HWDEC_OPTIONS[preferencesWindow.cb_hwdec.currentIndex()]
307
439
 
308
- self.ffmpeg_cache_dir = preferencesWindow.leFFmpegCacheDir.text()
440
+ # check project integrity
441
+ self.config_param[cfg.CHECK_PROJECT_INTEGRITY] = preferencesWindow.cb_check_integrity_at_opening.isChecked()
309
442
 
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
- )
443
+ # update BORIS analysis plugins
444
+ self.config_param[cfg.ANALYSIS_PLUGINS] = {}
445
+ self.config_param[cfg.EXCLUDED_PLUGINS] = set()
446
+ for i in range(preferencesWindow.lv_all_plugins.count()):
447
+ if preferencesWindow.lv_all_plugins.item(i).checkState() == Qt.Checked:
448
+ self.config_param[cfg.ANALYSIS_PLUGINS][preferencesWindow.lv_all_plugins.item(i).text()] = (
449
+ preferencesWindow.lv_all_plugins.item(i).data(100)
450
+ )
451
+ else:
452
+ self.config_param[cfg.EXCLUDED_PLUGINS].add(preferencesWindow.lv_all_plugins.item(i).text())
323
453
 
324
- menu_options.update_menu(self)
454
+ # update personal plugins
455
+ self.config_param[cfg.PERSONAL_PLUGINS_DIR] = preferencesWindow.le_personal_plugins_dir.text()
456
+ for i in range(preferencesWindow.lw_personal_plugins.count()):
457
+ if preferencesWindow.lw_personal_plugins.item(i).checkState() == Qt.Checked:
458
+ self.config_param[cfg.ANALYSIS_PLUGINS][preferencesWindow.lw_personal_plugins.item(i).text()] = (
459
+ preferencesWindow.lw_personal_plugins.item(i).data(100)
460
+ )
461
+ else:
462
+ self.config_param[cfg.EXCLUDED_PLUGINS].add(preferencesWindow.lw_personal_plugins.item(i).text())
325
463
 
326
- config_file.save(self)
464
+ plugins.load_plugins(self)
465
+ plugins.add_plugins_to_menu(self)
466
+
467
+ # project file indentation
468
+ self.config_param[cfg.PROJECT_FILE_INDENTATION] = cfg.PROJECT_FILE_INDENTATION_OPTIONS[
469
+ preferencesWindow.combo_project_file_indentation.currentIndex()
470
+ ]
471
+
472
+ if self.observationId:
473
+ self.load_tw_events(self.observationId)
474
+ self.display_statusbar_info(self.observationId)
475
+
476
+ self.ffmpeg_cache_dir = preferencesWindow.leFFmpegCacheDir.text()
477
+
478
+ # spectrogram
479
+ self.spectrogram_color_map = preferencesWindow.cbSpectrogramColorMap.currentText()
480
+ self.spectrogram_time_interval = preferencesWindow.sb_time_interval.value()
481
+ # window type
482
+ self.config_param[cfg.SPECTROGRAM_WINDOW_TYPE] = preferencesWindow.cb_window_type.currentText()
483
+ # NFFT
484
+ self.config_param[cfg.SPECTROGRAM_NFFT] = preferencesWindow.cb_NFFT.currentText()
485
+ # noverlap
486
+ self.config_param[cfg.SPECTROGRAM_NOVERLAP] = preferencesWindow.sb_noverlap.value()
487
+ # vmin
488
+ self.config_param[cfg.SPECTROGRAM_VMIN] = preferencesWindow.sb_vmin.value()
489
+ # vmax
490
+ self.config_param[cfg.SPECTROGRAM_VMAX] = preferencesWindow.sb_vmax.value()
491
+
492
+ # behav colors
493
+ self.plot_colors = preferencesWindow.te_behav_colors.toPlainText().split()
494
+ # category colors
495
+ self.behav_category_colors = preferencesWindow.te_category_colors.toPlainText().split()
496
+
497
+ # interface
498
+ self.config_param[cfg.TOOLBAR_ICON_SIZE] = preferencesWindow.sb_toolbar_icon_size.value()
499
+
500
+ menu_options.update_menu(self)
501
+
502
+ config_file.save(self)
503
+
504
+ break
505
+
506
+ else:
507
+ break