boris-behav-obs 8.16.5__py3-none-any.whl → 9.7.12__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 (126) hide show
  1. boris/__init__.py +1 -1
  2. boris/__main__.py +1 -1
  3. boris/about.py +28 -40
  4. boris/add_modifier.py +88 -80
  5. boris/add_modifier_ui.py +266 -144
  6. boris/advanced_event_filtering.py +23 -29
  7. boris/analysis_plugins/__init__.py +0 -0
  8. boris/analysis_plugins/_export_to_feral.py +225 -0
  9. boris/analysis_plugins/_latency.py +59 -0
  10. boris/analysis_plugins/irr_cohen_kappa.py +109 -0
  11. boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
  12. boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
  13. boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
  14. boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
  15. boris/analysis_plugins/number_of_occurences.py +22 -0
  16. boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
  17. boris/analysis_plugins/time_budget.py +61 -0
  18. boris/behav_coding_map_creator.py +235 -236
  19. boris/behavior_binary_table.py +33 -50
  20. boris/behaviors_coding_map.py +17 -18
  21. boris/boris_cli.py +6 -25
  22. boris/cmd_arguments.py +12 -1
  23. boris/coding_pad.py +19 -36
  24. boris/config.py +109 -50
  25. boris/config_file.py +58 -67
  26. boris/connections.py +105 -58
  27. boris/converters.py +13 -37
  28. boris/converters_ui.py +187 -110
  29. boris/cooccurence.py +250 -0
  30. boris/core.py +2174 -1303
  31. boris/core_qrc.py +15892 -10829
  32. boris/core_ui.py +941 -806
  33. boris/db_functions.py +17 -42
  34. boris/dev.py +27 -7
  35. boris/dialog.py +461 -242
  36. boris/duration_widget.py +9 -14
  37. boris/edit_event.py +61 -31
  38. boris/edit_event_ui.py +208 -97
  39. boris/event_operations.py +405 -281
  40. boris/events_cursor.py +25 -17
  41. boris/events_snapshots.py +36 -82
  42. boris/exclusion_matrix.py +4 -9
  43. boris/export_events.py +180 -203
  44. boris/export_observation.py +60 -73
  45. boris/external_processes.py +123 -98
  46. boris/geometric_measurement.py +427 -218
  47. boris/gui_utilities.py +91 -14
  48. boris/image_overlay.py +4 -4
  49. boris/import_observations.py +190 -98
  50. boris/ipc_mpv.py +325 -0
  51. boris/irr.py +20 -57
  52. boris/latency.py +31 -24
  53. boris/measurement_widget.py +14 -18
  54. boris/media_file.py +17 -19
  55. boris/menu_options.py +16 -6
  56. boris/modifier_coding_map_creator.py +1013 -0
  57. boris/modifiers_coding_map.py +7 -9
  58. boris/mpv2.py +128 -35
  59. boris/observation.py +501 -211
  60. boris/observation_operations.py +1037 -393
  61. boris/observation_ui.py +573 -363
  62. boris/observations_list.py +51 -58
  63. boris/otx_parser.py +74 -68
  64. boris/param_panel.py +45 -59
  65. boris/param_panel_ui.py +254 -138
  66. boris/player_dock_widget.py +91 -56
  67. boris/plot_data_module.py +20 -53
  68. boris/plot_events.py +56 -153
  69. boris/plot_events_rt.py +16 -30
  70. boris/plot_spectrogram_rt.py +83 -56
  71. boris/plot_waveform_rt.py +27 -49
  72. boris/plugins.py +468 -0
  73. boris/portion/__init__.py +18 -8
  74. boris/portion/const.py +35 -18
  75. boris/portion/dict.py +5 -5
  76. boris/portion/func.py +2 -2
  77. boris/portion/interval.py +21 -41
  78. boris/portion/io.py +41 -32
  79. boris/preferences.py +307 -123
  80. boris/preferences_ui.py +686 -227
  81. boris/project.py +294 -271
  82. boris/project_functions.py +626 -537
  83. boris/project_import_export.py +204 -213
  84. boris/project_ui.py +673 -441
  85. boris/qrc_boris.py +6 -3
  86. boris/qrc_boris5.py +6 -3
  87. boris/select_modifiers.py +62 -90
  88. boris/select_observations.py +19 -197
  89. boris/select_subj_behav.py +67 -39
  90. boris/state_events.py +51 -33
  91. boris/subjects_pad.py +7 -9
  92. boris/synthetic_time_budget.py +42 -26
  93. boris/time_budget_functions.py +169 -169
  94. boris/time_budget_widget.py +77 -89
  95. boris/transitions.py +41 -41
  96. boris/utilities.py +594 -226
  97. boris/version.py +3 -3
  98. boris/video_equalizer.py +16 -14
  99. boris/video_equalizer_ui.py +199 -130
  100. boris/video_operations.py +86 -28
  101. boris/view_df.py +104 -0
  102. boris/view_df_ui.py +75 -0
  103. boris/write_event.py +240 -136
  104. boris_behav_obs-9.7.12.dist-info/METADATA +139 -0
  105. boris_behav_obs-9.7.12.dist-info/RECORD +110 -0
  106. {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.12.dist-info}/WHEEL +1 -1
  107. boris_behav_obs-9.7.12.dist-info/entry_points.txt +2 -0
  108. boris/README.TXT +0 -22
  109. boris/add_modifier.ui +0 -323
  110. boris/converters.ui +0 -289
  111. boris/core.qrc +0 -37
  112. boris/core.ui +0 -1571
  113. boris/edit_event.ui +0 -233
  114. boris/icons/logo_eye.ico +0 -0
  115. boris/map_creator.py +0 -982
  116. boris/observation.ui +0 -814
  117. boris/param_panel.ui +0 -379
  118. boris/preferences.ui +0 -537
  119. boris/project.ui +0 -1074
  120. boris/vlc_local.py +0 -90
  121. boris_behav_obs-8.16.5.dist-info/LICENSE.TXT +0 -674
  122. boris_behav_obs-8.16.5.dist-info/METADATA +0 -134
  123. boris_behav_obs-8.16.5.dist-info/RECORD +0 -107
  124. boris_behav_obs-8.16.5.dist-info/entry_points.txt +0 -2
  125. {boris → boris_behav_obs-9.7.12.dist-info/licenses}/LICENSE.TXT +0 -0
  126. {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.12.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
- )
337
+ preferencesWindow.cbSpectrogramColorMap.setCurrentIndex(cfg.SPECTROGRAM_COLOR_MAPS.index(self.spectrogram_color_map))
192
338
  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
- )
201
- 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,150 @@ 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())
453
+
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())
323
463
 
324
- menu_options.update_menu(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
325
508
 
326
- config_file.save(self)
509
+ # activate main window
510
+ self.activateWindow()