boris-behav-obs 9.7.1__py3-none-any.whl → 9.7.15__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.
boris/plot_waveform_rt.py CHANGED
@@ -156,7 +156,7 @@ class Plot_waveform_RT(QWidget):
156
156
  self.interval += 5 * action
157
157
  self.plot_waveform(current_time=self.time_mem, force_plot=True)
158
158
 
159
- def plot_waveform(self, current_time: float, force_plot: bool = False):
159
+ def plot_waveform(self, current_time: float, force_plot: bool = False) -> None:
160
160
  """
161
161
  plot sound waveform centered on the current time
162
162
 
@@ -172,6 +172,9 @@ class Plot_waveform_RT(QWidget):
172
172
 
173
173
  self.ax.clear()
174
174
 
175
+ if current_time is None:
176
+ return
177
+
175
178
  # start
176
179
  if current_time <= self.interval / 2:
177
180
  time_ = np.linspace(
boris/plugins.py CHANGED
@@ -24,6 +24,8 @@ import logging
24
24
  import numpy as np
25
25
  import pandas as pd
26
26
  from pathlib import Path
27
+ import copy
28
+ import inspect
27
29
 
28
30
  from PySide6.QtGui import QAction
29
31
  from PySide6.QtWidgets import QMessageBox
@@ -297,6 +299,7 @@ def run_plugin(self, plugin_name):
297
299
 
298
300
  logging.debug(f"{plugin_path=}")
299
301
 
302
+ # check if plugin file exists
300
303
  if not Path(plugin_path).is_file():
301
304
  QMessageBox.critical(self, cfg.programName, f"The plugin {plugin_path} was not found.")
302
305
  return
@@ -308,27 +311,7 @@ def run_plugin(self, plugin_name):
308
311
  if not selected_observations:
309
312
  return
310
313
 
311
- logging.info("preparing dataframe for plugin")
312
-
313
- message, df = project_functions.project2dataframe(self.pj, selected_observations)
314
- if message:
315
- logging.critical(message)
316
- QMessageBox.critical(self, cfg.programName, message)
317
- return
318
-
319
- logging.info("done")
320
-
321
- """
322
- logging.debug("dataframe info")
323
- logging.debug(f"{df.info()}")
324
- logging.debug(f"{df.head()}")
325
- """
326
-
327
- # filter the dataframe with parameters
328
- logging.info("filtering dataframe for plugin")
329
- filtered_df = plugin_df_filter(df, observations_list=selected_observations, parameters=parameters)
330
- logging.info("done")
331
-
314
+ # Python plugin
332
315
  if Path(plugin_path).suffix == ".py":
333
316
  # load plugin as module
334
317
  module_name = Path(plugin_path).stem
@@ -347,9 +330,48 @@ def run_plugin(self, plugin_name):
347
330
  f"{plugin_module.__plugin_name__} loaded v.{getattr(plugin_module, '__version__')} v. {getattr(plugin_module, '__version_date__')}"
348
331
  )
349
332
 
350
- # run plugin
351
- plugin_results = plugin_module.run(filtered_df)
333
+ # check arguments required by the run function of the plugin
334
+ dataframe_required = False
335
+ project_required = False
336
+ # for param in inspect.signature(plugin_module.run).parameters.values():
337
+ for name, annotation in inspect.getfullargspec(plugin_module.run).annotations.items():
338
+ if name == "df" and annotation is pd.DataFrame:
339
+ dataframe_required = True
340
+ if name == "project" and annotation is dict:
341
+ project_required = True
342
+
343
+ # create arguments for the plugin run function
344
+ plugin_kwargs: dict = {}
345
+
346
+ if dataframe_required:
347
+ logging.info("preparing dataframe for plugin")
348
+ message, df = project_functions.project2dataframe(self.pj, selected_observations)
349
+ if message:
350
+ logging.critical(message)
351
+ QMessageBox.critical(self, cfg.programName, message)
352
+ return
353
+ logging.info("done")
354
+
355
+ # filter the dataframe with parameters
356
+ logging.info("filtering dataframe for plugin")
357
+ filtered_df = plugin_df_filter(df, observations_list=selected_observations, parameters=parameters)
358
+ logging.info("done")
352
359
 
360
+ plugin_kwargs["df"] = filtered_df
361
+
362
+ if project_required:
363
+ pj_copy = copy.deepcopy(self.pj)
364
+
365
+ # remove unselected observations from project
366
+ for obs_id in self.pj[cfg.OBSERVATIONS]:
367
+ if obs_id not in selected_observations:
368
+ del pj_copy[cfg.OBSERVATIONS][obs_id]
369
+
370
+ plugin_kwargs["project"] = pj_copy
371
+
372
+ plugin_results = plugin_module.run(**plugin_kwargs)
373
+
374
+ # R plugin
353
375
  if Path(plugin_path).suffix in (".R", ".r"):
354
376
  try:
355
377
  from rpy2 import robjects
@@ -360,6 +382,21 @@ def run_plugin(self, plugin_name):
360
382
  QMessageBox.critical(self, cfg.programName, "The rpy2 Python module is not installed. R plugins cannot be used")
361
383
  return
362
384
 
385
+ logging.info("preparing dataframe for plugin")
386
+
387
+ message, df = project_functions.project2dataframe(self.pj, selected_observations)
388
+ if message:
389
+ logging.critical(message)
390
+ QMessageBox.critical(self, cfg.programName, message)
391
+ return
392
+
393
+ logging.info("done")
394
+
395
+ # filter the dataframe with parameters
396
+ logging.info("filtering dataframe for plugin")
397
+ filtered_df = plugin_df_filter(df, observations_list=selected_observations, parameters=parameters)
398
+ logging.info("done")
399
+
363
400
  # Read code from file
364
401
  try:
365
402
  with open(plugin_path, "r") as f:
boris/preferences.py CHANGED
@@ -55,14 +55,38 @@ class Preferences(QDialog, Ui_prefDialog):
55
55
  self.pbOK.clicked.connect(self.accept)
56
56
  self.pbCancel.clicked.connect(self.reject)
57
57
 
58
+ self.pb_reset_spectro_values.clicked.connect(self.reset_spectro_values)
59
+ self.cb_use_vmin_vmax.toggled.connect(self.cb_vmin_vmax_changed)
60
+
58
61
  self.flag_refresh = False
59
62
 
60
63
  # Create a monospace QFont
61
- monospace_font = QFont("Courier New") # or "Monospace", "Consolas", "Liberation Mono", etc.
64
+ monospace_font = QFont("Courier New")
62
65
  monospace_font.setStyleHint(QFont.Monospace)
63
66
  monospace_font.setPointSize(12)
64
67
  self.pte_plugin_code.setFont(monospace_font)
65
68
 
69
+ def reset_spectro_values(self):
70
+ """
71
+ reset spectrogram values to default
72
+ """
73
+ self.cbSpectrogramColorMap.setCurrentIndex(cfg.SPECTROGRAM_COLOR_MAPS.index(cfg.SPECTROGRAM_DEFAULT_COLOR_MAP))
74
+ self.sb_time_interval.setValue(cfg.SPECTROGRAM_DEFAULT_TIME_INTERVAL)
75
+ self.cb_window_type.setCurrentText(cfg.SPECTROGRAM_DEFAULT_WINDOW_TYPE)
76
+ self.cb_NFFT.setCurrentText(cfg.SPECTROGRAM_DEFAULT_NFFT)
77
+ self.sb_noverlap.setValue(cfg.SPECTROGRAM_DEFAULT_NOVERLAP)
78
+ self.cb_use_vmin_vmax.setChecked(cfg.SPECTROGRAM_USE_VMIN_VMAX_DEFAULT)
79
+ self.cb_vmin_vmax_changed()
80
+ self.sb_vmin.setValue(cfg.SPECTROGRAM_DEFAULT_VMIN)
81
+ self.sb_vmax.setValue(cfg.SPECTROGRAM_DEFAULT_VMAX)
82
+
83
+ def cb_vmin_vmax_changed(self):
84
+ """
85
+ activate or de-activate vmin and vmax
86
+ """
87
+ for w in (self.sb_vmin, self.sb_vmax, self.label_vmin, self.label_vmin_2, self.label_vmax, self.label_vmax_2):
88
+ w.setEnabled(self.cb_use_vmin_vmax.isChecked())
89
+
66
90
  def browse_plugins_dir(self):
67
91
  """
68
92
  get the personal plugins directory
@@ -99,7 +123,7 @@ class Preferences(QDialog, Ui_prefDialog):
99
123
  dialog.MessageDialog(
100
124
  "BORIS",
101
125
  ("Refresh will re-initialize all your preferences and close BORIS"),
102
- [cfg.CANCEL, "Refresh preferences"],
126
+ (cfg.CANCEL, "Refresh preferences"),
103
127
  )
104
128
  == "Refresh preferences"
105
129
  ):
@@ -221,6 +245,9 @@ def preferences(self):
221
245
  preferencesWindow.cbConfirmSound.setChecked(self.confirmSound)
222
246
  # beep every
223
247
  preferencesWindow.sbBeepEvery.setValue(self.beep_every)
248
+ # frame step size
249
+ # preferencesWindow.sb_frame_step_size.setValue(self.config_param.get(cfg.FRAME_STEP_SIZE, cfg.FRAME_STEP_SIZE_DEFAULT_VALUE))
250
+
224
251
  # alert no focal subject
225
252
  preferencesWindow.cbAlertNoFocalSubject.setChecked(self.alertNoFocalSubject)
226
253
  # tracking cursor above event
@@ -345,6 +372,11 @@ def preferences(self):
345
372
  preferencesWindow.cb_NFFT.setCurrentText(self.config_param.get(cfg.SPECTROGRAM_NFFT, cfg.SPECTROGRAM_DEFAULT_NFFT))
346
373
  # noverlap
347
374
  preferencesWindow.sb_noverlap.setValue(self.config_param.get(cfg.SPECTROGRAM_NOVERLAP, cfg.SPECTROGRAM_DEFAULT_NOVERLAP))
375
+ # use vmin/xmax
376
+ preferencesWindow.cb_use_vmin_vmax.setChecked(
377
+ self.config_param.get(cfg.SPECTROGRAM_USE_VMIN_VMAX, cfg.SPECTROGRAM_USE_VMIN_VMAX_DEFAULT)
378
+ )
379
+ preferencesWindow.cb_vmin_vmax_changed()
348
380
  # vmin
349
381
  preferencesWindow.sb_vmin.setValue(self.config_param.get(cfg.SPECTROGRAM_VMIN, cfg.SPECTROGRAM_DEFAULT_VMIN))
350
382
  # vmax
@@ -367,7 +399,7 @@ def preferences(self):
367
399
 
368
400
  while True:
369
401
  if preferencesWindow.exec():
370
- if preferencesWindow.sb_vmin.value() >= preferencesWindow.sb_vmax.value():
402
+ if preferencesWindow.cb_use_vmin_vmax.isChecked() and preferencesWindow.sb_vmin.value() >= preferencesWindow.sb_vmax.value():
371
403
  QMessageBox.warning(self, cfg.programName, "Spectrogram parameters: the vmin value must be lower than the vmax value.")
372
404
  continue
373
405
 
@@ -418,6 +450,9 @@ def preferences(self):
418
450
 
419
451
  self.beep_every = preferencesWindow.sbBeepEvery.value()
420
452
 
453
+ # frame step size
454
+ # self.config_param[cfg.FRAME_STEP_SIZE] = preferencesWindow.sb_frame_step_size.value()
455
+
421
456
  self.alertNoFocalSubject = preferencesWindow.cbAlertNoFocalSubject.isChecked()
422
457
 
423
458
  self.trackingCursorAboveEvent = preferencesWindow.cbTrackingCursorAboveEvent.isChecked()
@@ -478,6 +513,8 @@ def preferences(self):
478
513
  self.config_param[cfg.SPECTROGRAM_NFFT] = preferencesWindow.cb_NFFT.currentText()
479
514
  # noverlap
480
515
  self.config_param[cfg.SPECTROGRAM_NOVERLAP] = preferencesWindow.sb_noverlap.value()
516
+ # use vmin/vmax
517
+ self.config_param[cfg.SPECTROGRAM_USE_VMIN_VMAX] = preferencesWindow.cb_use_vmin_vmax.isChecked()
481
518
  # vmin
482
519
  self.config_param[cfg.SPECTROGRAM_VMIN] = preferencesWindow.sb_vmin.value()
483
520
  # vmax
@@ -499,3 +536,6 @@ def preferences(self):
499
536
 
500
537
  else:
501
538
  break
539
+
540
+ # activate main window
541
+ self.activateWindow()
boris/preferences_ui.py CHANGED
@@ -3,7 +3,7 @@
3
3
  ################################################################################
4
4
  ## Form generated from reading UI file 'preferences.ui'
5
5
  ##
6
- ## Created by: Qt User Interface Compiler version 6.9.0
6
+ ## Created by: Qt User Interface Compiler version 6.10.0
7
7
  ##
8
8
  ## WARNING! All changes made in this file will be lost when recompiling UI file!
9
9
  ################################################################################
@@ -27,7 +27,7 @@ class Ui_prefDialog(object):
27
27
  if not prefDialog.objectName():
28
28
  prefDialog.setObjectName(u"prefDialog")
29
29
  prefDialog.setWindowModality(Qt.WindowModality.WindowModal)
30
- prefDialog.resize(904, 554)
30
+ prefDialog.resize(899, 757)
31
31
  self.horizontalLayout_17 = QHBoxLayout(prefDialog)
32
32
  self.horizontalLayout_17.setObjectName(u"horizontalLayout_17")
33
33
  self.verticalLayout_2 = QVBoxLayout()
@@ -145,11 +145,10 @@ class Ui_prefDialog(object):
145
145
 
146
146
  self.horizontalLayout_4.addWidget(self.label_4)
147
147
 
148
- self.sbffSpeed = QSpinBox(self.tab_observations)
148
+ self.sbffSpeed = QDoubleSpinBox(self.tab_observations)
149
149
  self.sbffSpeed.setObjectName(u"sbffSpeed")
150
- self.sbffSpeed.setMinimum(0)
151
- self.sbffSpeed.setMaximum(10000)
152
- self.sbffSpeed.setValue(10)
150
+ self.sbffSpeed.setDecimals(3)
151
+ self.sbffSpeed.setMaximum(1000000.000000000000000)
153
152
 
154
153
  self.horizontalLayout_4.addWidget(self.sbffSpeed)
155
154
 
@@ -244,6 +243,26 @@ class Ui_prefDialog(object):
244
243
 
245
244
  self.verticalLayout.addWidget(self.cb_pause_before_addevent)
246
245
 
246
+ self.horizontalLayout_23 = QHBoxLayout()
247
+ self.horizontalLayout_23.setObjectName(u"horizontalLayout_23")
248
+ self.label_24 = QLabel(self.tab_observations)
249
+ self.label_24.setObjectName(u"label_24")
250
+ self.label_24.setEnabled(False)
251
+
252
+ self.horizontalLayout_23.addWidget(self.label_24)
253
+
254
+ self.sb_frame_step_size = QSpinBox(self.tab_observations)
255
+ self.sb_frame_step_size.setObjectName(u"sb_frame_step_size")
256
+ self.sb_frame_step_size.setEnabled(False)
257
+ self.sb_frame_step_size.setMinimum(1)
258
+ self.sb_frame_step_size.setMaximum(1000)
259
+ self.sb_frame_step_size.setValue(1)
260
+
261
+ self.horizontalLayout_23.addWidget(self.sb_frame_step_size)
262
+
263
+
264
+ self.verticalLayout.addLayout(self.horizontalLayout_23)
265
+
247
266
  self.verticalSpacer_4 = QSpacerItem(20, 391, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
248
267
 
249
268
  self.verticalLayout.addItem(self.verticalSpacer_4)
@@ -256,35 +275,35 @@ class Ui_prefDialog(object):
256
275
  self.splitter_2 = QSplitter(self.tab_analysis_plugins)
257
276
  self.splitter_2.setObjectName(u"splitter_2")
258
277
  self.splitter_2.setOrientation(Qt.Orientation.Horizontal)
259
- self.widget = QWidget(self.splitter_2)
260
- self.widget.setObjectName(u"widget")
261
- self.verticalLayout_11 = QVBoxLayout(self.widget)
278
+ self.layoutWidget = QWidget(self.splitter_2)
279
+ self.layoutWidget.setObjectName(u"layoutWidget")
280
+ self.verticalLayout_11 = QVBoxLayout(self.layoutWidget)
262
281
  self.verticalLayout_11.setObjectName(u"verticalLayout_11")
263
282
  self.verticalLayout_11.setContentsMargins(0, 0, 0, 0)
264
- self.label_13 = QLabel(self.widget)
283
+ self.label_13 = QLabel(self.layoutWidget)
265
284
  self.label_13.setObjectName(u"label_13")
266
285
 
267
286
  self.verticalLayout_11.addWidget(self.label_13)
268
287
 
269
- self.lv_all_plugins = QListWidget(self.widget)
288
+ self.lv_all_plugins = QListWidget(self.layoutWidget)
270
289
  self.lv_all_plugins.setObjectName(u"lv_all_plugins")
271
290
 
272
291
  self.verticalLayout_11.addWidget(self.lv_all_plugins)
273
292
 
274
- self.label_15 = QLabel(self.widget)
293
+ self.label_15 = QLabel(self.layoutWidget)
275
294
  self.label_15.setObjectName(u"label_15")
276
295
 
277
296
  self.verticalLayout_11.addWidget(self.label_15)
278
297
 
279
298
  self.horizontalLayout_16 = QHBoxLayout()
280
299
  self.horizontalLayout_16.setObjectName(u"horizontalLayout_16")
281
- self.le_personal_plugins_dir = QLineEdit(self.widget)
300
+ self.le_personal_plugins_dir = QLineEdit(self.layoutWidget)
282
301
  self.le_personal_plugins_dir.setObjectName(u"le_personal_plugins_dir")
283
302
  self.le_personal_plugins_dir.setReadOnly(True)
284
303
 
285
304
  self.horizontalLayout_16.addWidget(self.le_personal_plugins_dir)
286
305
 
287
- self.pb_browse_plugins_dir = QPushButton(self.widget)
306
+ self.pb_browse_plugins_dir = QPushButton(self.layoutWidget)
288
307
  self.pb_browse_plugins_dir.setObjectName(u"pb_browse_plugins_dir")
289
308
 
290
309
  self.horizontalLayout_16.addWidget(self.pb_browse_plugins_dir)
@@ -292,49 +311,49 @@ class Ui_prefDialog(object):
292
311
 
293
312
  self.verticalLayout_11.addLayout(self.horizontalLayout_16)
294
313
 
295
- self.lw_personal_plugins = QListWidget(self.widget)
314
+ self.lw_personal_plugins = QListWidget(self.layoutWidget)
296
315
  self.lw_personal_plugins.setObjectName(u"lw_personal_plugins")
297
316
 
298
317
  self.verticalLayout_11.addWidget(self.lw_personal_plugins)
299
318
 
300
- self.splitter_2.addWidget(self.widget)
319
+ self.splitter_2.addWidget(self.layoutWidget)
301
320
  self.splitter = QSplitter(self.splitter_2)
302
321
  self.splitter.setObjectName(u"splitter")
303
322
  self.splitter.setOrientation(Qt.Orientation.Vertical)
304
- self.widget1 = QWidget(self.splitter)
305
- self.widget1.setObjectName(u"widget1")
306
- self.verticalLayout_12 = QVBoxLayout(self.widget1)
323
+ self.layoutWidget1 = QWidget(self.splitter)
324
+ self.layoutWidget1.setObjectName(u"layoutWidget1")
325
+ self.verticalLayout_12 = QVBoxLayout(self.layoutWidget1)
307
326
  self.verticalLayout_12.setObjectName(u"verticalLayout_12")
308
327
  self.verticalLayout_12.setContentsMargins(0, 0, 0, 0)
309
- self.label_14 = QLabel(self.widget1)
328
+ self.label_14 = QLabel(self.layoutWidget1)
310
329
  self.label_14.setObjectName(u"label_14")
311
330
 
312
331
  self.verticalLayout_12.addWidget(self.label_14)
313
332
 
314
- self.pte_plugin_description = QPlainTextEdit(self.widget1)
333
+ self.pte_plugin_description = QPlainTextEdit(self.layoutWidget1)
315
334
  self.pte_plugin_description.setObjectName(u"pte_plugin_description")
316
335
  self.pte_plugin_description.setReadOnly(True)
317
336
 
318
337
  self.verticalLayout_12.addWidget(self.pte_plugin_description)
319
338
 
320
- self.splitter.addWidget(self.widget1)
321
- self.widget2 = QWidget(self.splitter)
322
- self.widget2.setObjectName(u"widget2")
323
- self.verticalLayout_14 = QVBoxLayout(self.widget2)
339
+ self.splitter.addWidget(self.layoutWidget1)
340
+ self.layoutWidget2 = QWidget(self.splitter)
341
+ self.layoutWidget2.setObjectName(u"layoutWidget2")
342
+ self.verticalLayout_14 = QVBoxLayout(self.layoutWidget2)
324
343
  self.verticalLayout_14.setObjectName(u"verticalLayout_14")
325
344
  self.verticalLayout_14.setContentsMargins(0, 0, 0, 0)
326
- self.label_23 = QLabel(self.widget2)
345
+ self.label_23 = QLabel(self.layoutWidget2)
327
346
  self.label_23.setObjectName(u"label_23")
328
347
 
329
348
  self.verticalLayout_14.addWidget(self.label_23)
330
349
 
331
- self.pte_plugin_code = QPlainTextEdit(self.widget2)
350
+ self.pte_plugin_code = QPlainTextEdit(self.layoutWidget2)
332
351
  self.pte_plugin_code.setObjectName(u"pte_plugin_code")
333
352
  self.pte_plugin_code.setLineWrapMode(QPlainTextEdit.LineWrapMode.NoWrap)
334
353
 
335
354
  self.verticalLayout_14.addWidget(self.pte_plugin_code)
336
355
 
337
- self.splitter.addWidget(self.widget2)
356
+ self.splitter.addWidget(self.layoutWidget2)
338
357
  self.splitter_2.addWidget(self.splitter)
339
358
 
340
359
  self.verticalLayout_15.addWidget(self.splitter_2)
@@ -394,6 +413,20 @@ class Ui_prefDialog(object):
394
413
  self.groupBox.setObjectName(u"groupBox")
395
414
  self.verticalLayout_8 = QVBoxLayout(self.groupBox)
396
415
  self.verticalLayout_8.setObjectName(u"verticalLayout_8")
416
+ self.horizontalLayout_24 = QHBoxLayout()
417
+ self.horizontalLayout_24.setObjectName(u"horizontalLayout_24")
418
+ self.pb_reset_spectro_values = QPushButton(self.groupBox)
419
+ self.pb_reset_spectro_values.setObjectName(u"pb_reset_spectro_values")
420
+
421
+ self.horizontalLayout_24.addWidget(self.pb_reset_spectro_values)
422
+
423
+ self.horizontalSpacer_9 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
424
+
425
+ self.horizontalLayout_24.addItem(self.horizontalSpacer_9)
426
+
427
+
428
+ self.verticalLayout_8.addLayout(self.horizontalLayout_24)
429
+
397
430
  self.horizontalLayout_7 = QHBoxLayout()
398
431
  self.horizontalLayout_7.setObjectName(u"horizontalLayout_7")
399
432
  self.label_7 = QLabel(self.groupBox)
@@ -502,12 +535,26 @@ class Ui_prefDialog(object):
502
535
 
503
536
  self.verticalLayout_8.addLayout(self.horizontalLayout_20)
504
537
 
538
+ self.horizontalLayout_25 = QHBoxLayout()
539
+ self.horizontalLayout_25.setObjectName(u"horizontalLayout_25")
540
+ self.cb_use_vmin_vmax = QCheckBox(self.groupBox)
541
+ self.cb_use_vmin_vmax.setObjectName(u"cb_use_vmin_vmax")
542
+
543
+ self.horizontalLayout_25.addWidget(self.cb_use_vmin_vmax)
544
+
545
+ self.horizontalSpacer_10 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
546
+
547
+ self.horizontalLayout_25.addItem(self.horizontalSpacer_10)
548
+
549
+
550
+ self.verticalLayout_8.addLayout(self.horizontalLayout_25)
551
+
505
552
  self.horizontalLayout_21 = QHBoxLayout()
506
553
  self.horizontalLayout_21.setObjectName(u"horizontalLayout_21")
507
- self.label_19 = QLabel(self.groupBox)
508
- self.label_19.setObjectName(u"label_19")
554
+ self.label_vmin = QLabel(self.groupBox)
555
+ self.label_vmin.setObjectName(u"label_vmin")
509
556
 
510
- self.horizontalLayout_21.addWidget(self.label_19)
557
+ self.horizontalLayout_21.addWidget(self.label_vmin)
511
558
 
512
559
  self.sb_vmin = QSpinBox(self.groupBox)
513
560
  self.sb_vmin.setObjectName(u"sb_vmin")
@@ -517,10 +564,10 @@ class Ui_prefDialog(object):
517
564
 
518
565
  self.horizontalLayout_21.addWidget(self.sb_vmin)
519
566
 
520
- self.label_21 = QLabel(self.groupBox)
521
- self.label_21.setObjectName(u"label_21")
567
+ self.label_vmin_2 = QLabel(self.groupBox)
568
+ self.label_vmin_2.setObjectName(u"label_vmin_2")
522
569
 
523
- self.horizontalLayout_21.addWidget(self.label_21)
570
+ self.horizontalLayout_21.addWidget(self.label_vmin_2)
524
571
 
525
572
  self.horizontalSpacer_7 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
526
573
 
@@ -531,10 +578,10 @@ class Ui_prefDialog(object):
531
578
 
532
579
  self.horizontalLayout_22 = QHBoxLayout()
533
580
  self.horizontalLayout_22.setObjectName(u"horizontalLayout_22")
534
- self.label_20 = QLabel(self.groupBox)
535
- self.label_20.setObjectName(u"label_20")
581
+ self.label_vmax = QLabel(self.groupBox)
582
+ self.label_vmax.setObjectName(u"label_vmax")
536
583
 
537
- self.horizontalLayout_22.addWidget(self.label_20)
584
+ self.horizontalLayout_22.addWidget(self.label_vmax)
538
585
 
539
586
  self.sb_vmax = QSpinBox(self.groupBox)
540
587
  self.sb_vmax.setObjectName(u"sb_vmax")
@@ -544,10 +591,10 @@ class Ui_prefDialog(object):
544
591
 
545
592
  self.horizontalLayout_22.addWidget(self.sb_vmax)
546
593
 
547
- self.label_22 = QLabel(self.groupBox)
548
- self.label_22.setObjectName(u"label_22")
594
+ self.label_vmax_2 = QLabel(self.groupBox)
595
+ self.label_vmax_2.setObjectName(u"label_vmax_2")
549
596
 
550
- self.horizontalLayout_22.addWidget(self.label_22)
597
+ self.horizontalLayout_22.addWidget(self.label_vmax_2)
551
598
 
552
599
  self.horizontalSpacer_8 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
553
600
 
@@ -676,7 +723,7 @@ class Ui_prefDialog(object):
676
723
 
677
724
  self.retranslateUi(prefDialog)
678
725
 
679
- self.tabWidget.setCurrentIndex(2)
726
+ self.tabWidget.setCurrentIndex(4)
680
727
 
681
728
 
682
729
  QMetaObject.connectSlotsByName(prefDialog)
@@ -707,6 +754,7 @@ class Ui_prefDialog(object):
707
754
  self.cbTrackingCursorAboveEvent.setText(QCoreApplication.translate("prefDialog", u"Tracking cursor above current event", None))
708
755
  self.cbAlertNoFocalSubject.setText(QCoreApplication.translate("prefDialog", u"Alert if focal subject is not set", None))
709
756
  self.cb_pause_before_addevent.setText(QCoreApplication.translate("prefDialog", u"Pause media before \"Add event\" command", None))
757
+ self.label_24.setText(QCoreApplication.translate("prefDialog", u"Frame step size", None))
710
758
  self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_observations), QCoreApplication.translate("prefDialog", u"Observations", None))
711
759
  self.label_13.setText(QCoreApplication.translate("prefDialog", u"BORIS plugins", None))
712
760
  self.label_15.setText(QCoreApplication.translate("prefDialog", u"Personal plugins", None))
@@ -719,6 +767,7 @@ class Ui_prefDialog(object):
719
767
  self.pbBrowseFFmpegCacheDir.setText(QCoreApplication.translate("prefDialog", u"...", None))
720
768
  self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_ffmpeg), QCoreApplication.translate("prefDialog", u"FFmpeg framework", None))
721
769
  self.groupBox.setTitle(QCoreApplication.translate("prefDialog", u"Spectrogram", None))
770
+ self.pb_reset_spectro_values.setText(QCoreApplication.translate("prefDialog", u"Reset to default values", None))
722
771
  self.label_7.setText(QCoreApplication.translate("prefDialog", u"Color map", None))
723
772
  self.label_12.setText(QCoreApplication.translate("prefDialog", u"Default time interval (s)", None))
724
773
  self.label_16.setText(QCoreApplication.translate("prefDialog", u"Window type", None))
@@ -733,10 +782,11 @@ class Ui_prefDialog(object):
733
782
  self.cb_NFFT.setItemText(3, QCoreApplication.translate("prefDialog", u"2048", None))
734
783
 
735
784
  self.label_18.setText(QCoreApplication.translate("prefDialog", u"noverlap", None))
736
- self.label_19.setText(QCoreApplication.translate("prefDialog", u"vmin", None))
737
- self.label_21.setText(QCoreApplication.translate("prefDialog", u"dBFS", None))
738
- self.label_20.setText(QCoreApplication.translate("prefDialog", u"vmax", None))
739
- self.label_22.setText(QCoreApplication.translate("prefDialog", u"dBFS", None))
785
+ self.cb_use_vmin_vmax.setText(QCoreApplication.translate("prefDialog", u"Use vmin/vmax", None))
786
+ self.label_vmin.setText(QCoreApplication.translate("prefDialog", u"vmin", None))
787
+ self.label_vmin_2.setText(QCoreApplication.translate("prefDialog", u"dBFS", None))
788
+ self.label_vmax.setText(QCoreApplication.translate("prefDialog", u"vmax", None))
789
+ self.label_vmax_2.setText(QCoreApplication.translate("prefDialog", u"dBFS", None))
740
790
  self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_spectro), QCoreApplication.translate("prefDialog", u"Spectrogram/Wave form", None))
741
791
  self.label_10.setText(QCoreApplication.translate("prefDialog", u"<html><head/><body><p>List of colors for behaviors. See <a href=\"https://matplotlib.org/api/colors_api.html\"><span style=\" text-decoration: underline; color:#0000ff;\">matplotlib colors</span></a></p></body></html>", None))
742
792
  self.pb_reset_behav_colors.setText(QCoreApplication.translate("prefDialog", u"Reset colors to default", None))
boris/project.py CHANGED
@@ -1977,7 +1977,7 @@ class projectDialog(QDialog, Ui_dlgProject):
1977
1977
  self.pj[cfg.INDEPENDENT_VARIABLES] = dict(self.indVar)
1978
1978
 
1979
1979
  # converters
1980
- converters = {}
1980
+ converters: dict = {}
1981
1981
  for row in range(self.tw_converters.rowCount()):
1982
1982
  converters[self.tw_converters.item(row, 0).text()] = {
1983
1983
  "name": self.tw_converters.item(row, 0).text(),
@@ -22,25 +22,22 @@ Copyright 2012-2025 Olivier Friard
22
22
  import gzip
23
23
  import json
24
24
  import logging
25
- import pandas as pd
26
- import numpy as np
27
- from pathlib import Path
28
25
  import sys
29
26
  from decimal import Decimal as dec
27
+ from pathlib import Path
30
28
  from shutil import copyfile
31
- from typing import List, Tuple, Dict
29
+ from typing import Dict, List, Tuple
32
30
 
31
+ import numpy as np
32
+ import pandas as pd
33
33
  import tablib
34
- from PySide6.QtWidgets import QMessageBox, QTableWidgetItem, QAbstractItemView
35
34
  from PySide6.QtCore import Qt
35
+ from PySide6.QtWidgets import QAbstractItemView, QMessageBox, QTableWidgetItem
36
36
 
37
37
  from . import config as cfg
38
- from . import db_functions
39
- from . import dialog
40
- from . import observation_operations
38
+ from . import db_functions, dialog, observation_operations, version
41
39
  from . import portion as I
42
40
  from . import utilities as util
43
- from . import version
44
41
 
45
42
 
46
43
  def check_observation_exhaustivity(
@@ -425,6 +422,9 @@ def check_project_integrity(
425
422
  r = check_coded_behaviors(pj)
426
423
  if r:
427
424
  out += f"The following behaviors are not defined in the ethogram: <b>{', '.join(r)}</b><br>"
425
+ flag_all_behaviors_defined = False
426
+ else:
427
+ flag_all_behaviors_defined = True
428
428
 
429
429
  # check for unpaired state events
430
430
  for obs_id in pj[cfg.OBSERVATIONS]:
@@ -577,26 +577,27 @@ def check_project_integrity(
577
577
  obs_results: dict = {}
578
578
  for obs_id in pj[cfg.OBSERVATIONS]:
579
579
  for event_idx, event in enumerate(pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]):
580
- # event[2]
581
580
  for idx in pj[cfg.ETHOGRAM]:
582
- if pj[cfg.ETHOGRAM][idx]["code"] == event[2]:
581
+ if pj[cfg.ETHOGRAM][idx][cfg.BEHAVIOR_CODE] == event[cfg.EVENT_BEHAVIOR_FIELD_IDX]:
583
582
  break
584
583
  else:
585
- raise
586
- if (not event[3]) and not pj[cfg.ETHOGRAM][idx][cfg.MODIFIERS]:
584
+ # behavior not defined in ethogram
587
585
  continue
588
586
 
589
- if len(event[3].split("|")) != len(pj[cfg.ETHOGRAM][idx][cfg.MODIFIERS]):
590
- print("behavior", event[2])
591
- print(f"modifier(s) #{event[3]}#", len(event[3].split("|")))
592
- print(pj[cfg.ETHOGRAM][idx]["code"], pj[cfg.ETHOGRAM][idx][cfg.MODIFIERS])
593
- print()
587
+ if (not event[cfg.EVENT_MODIFIER_FIELD_IDX]) and not pj[cfg.ETHOGRAM][idx][cfg.MODIFIERS]: # no modifiers
588
+ continue
589
+
590
+ if len(event[cfg.EVENT_MODIFIER_FIELD_IDX].split("|")) != len(pj[cfg.ETHOGRAM][idx][cfg.MODIFIERS]):
591
+ # print("behavior", event[cfg.EVENT_BEHAVIOR_FIELD_IDX])
592
+ # print(f"modifier(s) #{event[cfg.EVENT_MODIFIER_FIELD_IDX]}#", len(event[cfg.EVENT_MODIFIER_FIELD_IDX].split("|")))
593
+ # print(pj[cfg.ETHOGRAM][idx]["code"], pj[cfg.ETHOGRAM][idx][cfg.MODIFIERS])
594
+ # print()
594
595
  if obs_id not in obs_results:
595
596
  obs_results[obs_id] = []
596
597
 
597
598
  obs_results[obs_id].append(
598
599
  (
599
- f"Event #{event_idx}: the coded modifiers for {event[2]} are {len(event[3].split('|'))} "
600
+ f"Event #{event_idx}: the coded modifiers for {event[cfg.EVENT_BEHAVIOR_FIELD_IDX]} are {len(event[cfg.EVENT_MODIFIER_FIELD_IDX].split('|'))} "
600
601
  f"but {len(pj[cfg.ETHOGRAM][idx][cfg.MODIFIERS])} sets were defined in ethogram."
601
602
  )
602
603
  )
@@ -1425,6 +1426,7 @@ def open_project_json(project_file_name: str) -> tuple:
1425
1426
  pj[cfg.OBSERVATIONS][obs][cfg.TYPE] = cfg.MEDIA
1426
1427
 
1427
1428
  # convert old media list in new one
1429
+ d1: dict = {}
1428
1430
  if len(pj[cfg.OBSERVATIONS][obs][cfg.FILE]):
1429
1431
  d1 = {cfg.PLAYER1: [pj[cfg.OBSERVATIONS][obs][cfg.FILE][0]]}
1430
1432
 
@@ -1470,6 +1472,17 @@ def open_project_json(project_file_name: str) -> tuple:
1470
1472
  pj[cfg.PROJECT_VERSION] = cfg.project_format_version
1471
1473
  projectChanged = True
1472
1474
 
1475
+ # check if behavioral categories are stored as a list
1476
+ if cfg.BEHAVIORAL_CATEGORIES_CONF in pj:
1477
+ if isinstance(pj[cfg.BEHAVIORAL_CATEGORIES_CONF], list):
1478
+ # convert to dict
1479
+ pj[cfg.BEHAVIORAL_CATEGORIES_CONF] = {str(idx): {"name": bc} for idx, bc in enumerate(pj[cfg.BEHAVIORAL_CATEGORIES_CONF])}
1480
+ logging.info("Behavioral categories was converted from a list to a dictionary")
1481
+ projectChanged = True
1482
+ else:
1483
+ pj[cfg.BEHAVIORAL_CATEGORIES_CONF] = dict()
1484
+ projectChanged = True
1485
+
1473
1486
  # add category key if not found
1474
1487
  for idx in pj[cfg.ETHOGRAM]:
1475
1488
  if cfg.BEHAVIOR_CATEGORY not in pj[cfg.ETHOGRAM][idx]:
@@ -1789,7 +1802,7 @@ def explore_project(self) -> None:
1789
1802
  if results:
1790
1803
  self.results_dialog = dialog.View_explore_project_results()
1791
1804
  self.results_dialog.setWindowTitle("Explore project results")
1792
- self.results_dialog.setWindowFlags(Qt.WindowStaysOnTopHint)
1805
+ self.results_dialog.setWindowFlags(Qt.WindowType.WindowStaysOnTopHint)
1793
1806
  self.results_dialog.double_click_signal.connect(double_click_explore_project)
1794
1807
  txt = f"<b>{len(results)}</b> events"
1795
1808
  txt2 = ""