boris-behav-obs 9.7.12__py3-none-any.whl → 9.8.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 (84) hide show
  1. boris/__init__.py +1 -1
  2. boris/__main__.py +1 -1
  3. boris/about.py +4 -3
  4. boris/add_modifier.py +1 -1
  5. boris/advanced_event_filtering.py +1 -1
  6. boris/analysis_plugins/export_to_feral.py +336 -0
  7. boris/analysis_plugins/irr_weighted_cohen_kappa.py +2 -2
  8. boris/behav_coding_map_creator.py +1 -1
  9. boris/behavior_binary_table.py +1 -1
  10. boris/behaviors_coding_map.py +1 -1
  11. boris/boris_cli.py +1 -1
  12. boris/cmd_arguments.py +1 -1
  13. boris/coding_pad.py +1 -1
  14. boris/config.py +15 -3
  15. boris/config_file.py +18 -19
  16. boris/connections.py +12 -13
  17. boris/converters.py +1 -1
  18. boris/converters_ui.py +2 -3
  19. boris/cooccurence.py +1 -1
  20. boris/core.py +168 -166
  21. boris/core_qrc.py +1830 -1967
  22. boris/core_ui.py +1 -1
  23. boris/db_functions.py +5 -14
  24. boris/dialog.py +24 -24
  25. boris/edit_event.py +1 -1
  26. boris/event_operations.py +1 -1
  27. boris/events_cursor.py +1 -1
  28. boris/events_snapshots.py +133 -78
  29. boris/exclusion_matrix.py +1 -1
  30. boris/export_events.py +49 -43
  31. boris/export_observation.py +1 -1
  32. boris/external_processes.py +1 -1
  33. boris/geometric_measurement.py +1 -1
  34. boris/gui_utilities.py +1 -1
  35. boris/image_overlay.py +1 -1
  36. boris/import_observations.py +1 -1
  37. boris/ipc_mpv.py +1 -1
  38. boris/irr.py +1 -1
  39. boris/latency.py +1 -1
  40. boris/measurement_widget.py +1 -1
  41. boris/media_file.py +1 -1
  42. boris/menu_options.py +14 -12
  43. boris/modifier_coding_map_creator.py +1 -1
  44. boris/modifiers_coding_map.py +1 -1
  45. boris/observation.py +13 -14
  46. boris/observation_operations.py +1 -1
  47. boris/observations_list.py +1 -1
  48. boris/otx_parser.py +1 -1
  49. boris/param_panel.py +1 -1
  50. boris/player_dock_widget.py +1 -1
  51. boris/plot_data_module.py +1 -1
  52. boris/plot_events.py +1 -1
  53. boris/plot_events_rt.py +1 -1
  54. boris/plot_spectrogram_rt.py +42 -73
  55. boris/plot_waveform_rt.py +1 -1
  56. boris/plugins.py +1 -1
  57. boris/preferences.py +35 -4
  58. boris/preferences_ui.py +48 -18
  59. boris/project.py +1 -1
  60. boris/project_functions.py +19 -22
  61. boris/project_import_export.py +1 -1
  62. boris/select_modifiers.py +1 -1
  63. boris/select_observations.py +22 -23
  64. boris/select_subj_behav.py +4 -4
  65. boris/state_events.py +1 -1
  66. boris/subjects_pad.py +1 -1
  67. boris/synthetic_time_budget.py +1 -1
  68. boris/time_budget_functions.py +1 -1
  69. boris/time_budget_widget.py +1 -1
  70. boris/transitions.py +1 -1
  71. boris/utilities.py +1 -1
  72. boris/version.py +3 -3
  73. boris/video_equalizer.py +1 -1
  74. boris/video_operations.py +1 -1
  75. boris/view_df.py +28 -4
  76. boris/write_event.py +1 -1
  77. {boris_behav_obs-9.7.12.dist-info → boris_behav_obs-9.8.2.dist-info}/METADATA +2 -2
  78. boris_behav_obs-9.8.2.dist-info/RECORD +110 -0
  79. {boris_behav_obs-9.7.12.dist-info → boris_behav_obs-9.8.2.dist-info}/WHEEL +1 -1
  80. boris/analysis_plugins/_export_to_feral.py +0 -225
  81. boris_behav_obs-9.7.12.dist-info/RECORD +0 -110
  82. {boris_behav_obs-9.7.12.dist-info → boris_behav_obs-9.8.2.dist-info}/entry_points.txt +0 -0
  83. {boris_behav_obs-9.7.12.dist-info → boris_behav_obs-9.8.2.dist-info}/licenses/LICENSE.TXT +0 -0
  84. {boris_behav_obs-9.7.12.dist-info → boris_behav_obs-9.8.2.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2025 Olivier Friard
4
+ Copyright 2012-2026 Olivier Friard
5
5
 
6
6
 
7
7
  This program is free software; you can redistribute it and/or modify
@@ -183,7 +183,7 @@ class Plot_spectrogram_RT(QWidget):
183
183
 
184
184
  return {"media_length": self.media_length, "frame_rate": self.frame_rate}
185
185
 
186
- def plot_spectro(self, current_time: float, force_plot: bool = False) -> tuple[float, bool]:
186
+ def plot_spectro(self, current_time: float | None, force_plot: bool = False) -> tuple[float, bool] | None:
187
187
  """
188
188
  plot sound spectrogram centered on the current time
189
189
 
@@ -192,6 +192,31 @@ class Plot_spectrogram_RT(QWidget):
192
192
  force_plot (bool): force plot even if media paused
193
193
  """
194
194
 
195
+ def spectrogram(self, x, window_type, nfft, noverlap, vmin, vmax) -> None:
196
+ window = matplotlib.mlab.window_hanning
197
+
198
+ if window_type == "hanning":
199
+ window = matplotlib.mlab.window_hanning
200
+
201
+ if window_type == "hamming":
202
+ window = signal.get_window(window_type, nfft)
203
+
204
+ if window_type == "blackmanharris":
205
+ window = signal.get_window(window_type, nfft)
206
+
207
+ # print(f"{self.frame_rate=} {vmin=} {vmax=} {window_type=} {nfft=} {noverlap=}")
208
+ self.ax.specgram(
209
+ x,
210
+ mode="psd",
211
+ NFFT=nfft,
212
+ Fs=self.frame_rate,
213
+ noverlap=noverlap,
214
+ window=window,
215
+ cmap=self.spectro_color_map,
216
+ vmin=vmin,
217
+ vmax=vmax,
218
+ )
219
+
195
220
  if not force_plot and current_time == self.time_mem:
196
221
  return
197
222
 
@@ -199,40 +224,21 @@ class Plot_spectrogram_RT(QWidget):
199
224
 
200
225
  self.ax.clear()
201
226
 
202
- window_type = "blackmanharris" # self.config_param.get(cfg.SPECTROGRAM_WINDOW_TYPE, cfg.SPECTROGRAM_DEFAULT_WINDOW_TYPE)
227
+ window_type = self.config_param.get(cfg.SPECTROGRAM_WINDOW_TYPE, cfg.SPECTROGRAM_DEFAULT_WINDOW_TYPE)
203
228
  nfft = int(self.config_param.get(cfg.SPECTROGRAM_NFFT, cfg.SPECTROGRAM_DEFAULT_NFFT))
204
229
  noverlap = self.config_param.get(cfg.SPECTROGRAM_NOVERLAP, cfg.SPECTROGRAM_DEFAULT_NOVERLAP)
205
- vmin = self.config_param.get(cfg.SPECTROGRAM_VMIN, cfg.SPECTROGRAM_DEFAULT_VMIN)
206
- vmax = self.config_param.get(cfg.SPECTROGRAM_VMAX, cfg.SPECTROGRAM_DEFAULT_VMAX)
230
+ if self.config_param.get(cfg.SPECTROGRAM_USE_VMIN_VMAX, cfg.SPECTROGRAM_USE_VMIN_VMAX_DEFAULT):
231
+ vmin = self.config_param.get(cfg.SPECTROGRAM_VMIN, cfg.SPECTROGRAM_DEFAULT_VMIN)
232
+ vmax = self.config_param.get(cfg.SPECTROGRAM_VMAX, cfg.SPECTROGRAM_DEFAULT_VMAX)
233
+ else:
234
+ vmin, vmax = None, None
207
235
 
208
236
  if current_time is None:
209
237
  return
210
238
 
211
239
  # start
212
240
  if current_time <= self.interval / 2:
213
- self.ax.specgram(
214
- self.sound_info[: int(self.interval * self.frame_rate)],
215
- mode="psd",
216
- NFFT=nfft,
217
- Fs=self.frame_rate,
218
- noverlap=noverlap,
219
- window=signal.get_window(window_type, nfft),
220
- # matplotlib.mlab.window_hanning
221
- # if window_type == "hanning"
222
- # else matplotlib.mlab.window_hamming
223
- # if window_type == "hamming"
224
- # else matplotlib.mlab.window_blackmanharris
225
- # if window_type == "blackmanharris"
226
- # else matplotlib.mlab.window_hanning,
227
- cmap=self.spectro_color_map,
228
- vmin=vmin,
229
- vmax=vmax,
230
- # mode="psd",
231
- ## NFFT=1024,
232
- # Fs=self.frame_rate,
233
- ## noverlap=900,
234
- # cmap=self.spectro_color_map,
235
- )
241
+ spectrogram(self, self.sound_info[: int(self.interval * self.frame_rate)], window_type, nfft, noverlap, vmin, vmax)
236
242
 
237
243
  self.ax.set_xlim(current_time - self.interval / 2, current_time + self.interval / 2)
238
244
 
@@ -242,29 +248,7 @@ class Plot_spectrogram_RT(QWidget):
242
248
  elif current_time >= self.media_length - self.interval / 2:
243
249
  i = int(round(len(self.sound_info) - (self.interval * self.frame_rate), 0))
244
250
 
245
- self.ax.specgram(
246
- self.sound_info[i:],
247
- mode="psd",
248
- NFFT=nfft,
249
- Fs=self.frame_rate,
250
- noverlap=noverlap,
251
- window=signal.get_window(window_type, nfft),
252
- # matplotlib.mlab.window_hanning
253
- # if window_type == "hanning"
254
- # else matplotlib.mlab.window_hamming
255
- # if window_type == "hamming"
256
- # else matplotlib.mlab.window_blackmanharris
257
- # if window_type == "blackmanharris"
258
- # else matplotlib.mlab.window_hanning,
259
- cmap=self.spectro_color_map,
260
- vmin=vmin,
261
- vmax=vmax,
262
- # mode="psd",
263
- ## NFFT=1024,
264
- # Fs=self.frame_rate,
265
- ## noverlap=900,
266
- # cmap=self.spectro_color_map,
267
- )
251
+ spectrogram(self, self.sound_info[i:], window_type, nfft, noverlap, vmin, vmax)
268
252
 
269
253
  lim1 = current_time - (self.media_length - self.interval / 2)
270
254
  lim2 = lim1 + self.interval
@@ -279,32 +263,18 @@ class Plot_spectrogram_RT(QWidget):
279
263
 
280
264
  # middle
281
265
  else:
282
- self.ax.specgram(
266
+ spectrogram(
267
+ self,
283
268
  self.sound_info[
284
269
  int(round((current_time - self.interval / 2) * self.frame_rate, 0)) : int(
285
270
  round((current_time + self.interval / 2) * self.frame_rate, 0)
286
271
  )
287
272
  ],
288
- mode="psd",
289
- NFFT=nfft,
290
- Fs=self.frame_rate,
291
- noverlap=noverlap,
292
- window=signal.get_window(window_type, nfft),
293
- # matplotlib.mlab.window_hanning
294
- # if window_type == "hanning"
295
- # else matplotlib.mlab.window_hamming
296
- # if window_type == "hamming"
297
- # else matplotlib.mlab.window_blackmanharris
298
- # if window_type == "blackmanharris"
299
- # else matplotlib.mlab.window_hanning,
300
- cmap=self.spectro_color_map,
301
- vmin=vmin,
302
- vmax=vmax,
303
- # mode="psd",
304
- ## NFFT=1024,
305
- # Fs=self.frame_rate,
306
- ## noverlap=900,
307
- # cmap=self.spectro_color_map,
273
+ window_type,
274
+ nfft,
275
+ noverlap,
276
+ vmin,
277
+ vmax,
308
278
  )
309
279
 
310
280
  self.ax.xaxis.set_major_locator(mticker.FixedLocator(self.ax.get_xticks().tolist()))
@@ -314,6 +284,5 @@ class Plot_spectrogram_RT(QWidget):
314
284
  self.ax.axvline(x=self.interval / 2, color=self.cursor_color, linestyle="-")
315
285
 
316
286
  self.ax.set_ylim(self.sb_freq_min.value(), self.sb_freq_max.value())
317
- """self.figure.subplots_adjust(wspace=0, hspace=0)"""
318
287
 
319
288
  self.canvas.draw()
boris/plot_waveform_rt.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2025 Olivier Friard
4
+ Copyright 2012-2026 Olivier Friard
5
5
 
6
6
 
7
7
  This program is free software; you can redistribute it and/or modify
boris/plugins.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2025 Olivier Friard
4
+ Copyright 2012-2026 Olivier Friard
5
5
 
6
6
  This program is free software; you can redistribute it and/or modify
7
7
  it under the terms of the GNU General Public License as published by
boris/preferences.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2025 Olivier Friard
4
+ Copyright 2012-2026 Olivier Friard
5
5
 
6
6
  This file is part of BORIS.
7
7
 
@@ -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
  ):
@@ -348,6 +372,11 @@ def preferences(self):
348
372
  preferencesWindow.cb_NFFT.setCurrentText(self.config_param.get(cfg.SPECTROGRAM_NFFT, cfg.SPECTROGRAM_DEFAULT_NFFT))
349
373
  # noverlap
350
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()
351
380
  # vmin
352
381
  preferencesWindow.sb_vmin.setValue(self.config_param.get(cfg.SPECTROGRAM_VMIN, cfg.SPECTROGRAM_DEFAULT_VMIN))
353
382
  # vmax
@@ -370,7 +399,7 @@ def preferences(self):
370
399
 
371
400
  while True:
372
401
  if preferencesWindow.exec():
373
- 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():
374
403
  QMessageBox.warning(self, cfg.programName, "Spectrogram parameters: the vmin value must be lower than the vmax value.")
375
404
  continue
376
405
 
@@ -484,6 +513,8 @@ def preferences(self):
484
513
  self.config_param[cfg.SPECTROGRAM_NFFT] = preferencesWindow.cb_NFFT.currentText()
485
514
  # noverlap
486
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()
487
518
  # vmin
488
519
  self.config_param[cfg.SPECTROGRAM_VMIN] = preferencesWindow.sb_vmin.value()
489
520
  # vmax
boris/preferences_ui.py CHANGED
@@ -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()
@@ -413,6 +413,20 @@ class Ui_prefDialog(object):
413
413
  self.groupBox.setObjectName(u"groupBox")
414
414
  self.verticalLayout_8 = QVBoxLayout(self.groupBox)
415
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
+
416
430
  self.horizontalLayout_7 = QHBoxLayout()
417
431
  self.horizontalLayout_7.setObjectName(u"horizontalLayout_7")
418
432
  self.label_7 = QLabel(self.groupBox)
@@ -521,12 +535,26 @@ class Ui_prefDialog(object):
521
535
 
522
536
  self.verticalLayout_8.addLayout(self.horizontalLayout_20)
523
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
+
524
552
  self.horizontalLayout_21 = QHBoxLayout()
525
553
  self.horizontalLayout_21.setObjectName(u"horizontalLayout_21")
526
- self.label_19 = QLabel(self.groupBox)
527
- self.label_19.setObjectName(u"label_19")
554
+ self.label_vmin = QLabel(self.groupBox)
555
+ self.label_vmin.setObjectName(u"label_vmin")
528
556
 
529
- self.horizontalLayout_21.addWidget(self.label_19)
557
+ self.horizontalLayout_21.addWidget(self.label_vmin)
530
558
 
531
559
  self.sb_vmin = QSpinBox(self.groupBox)
532
560
  self.sb_vmin.setObjectName(u"sb_vmin")
@@ -536,10 +564,10 @@ class Ui_prefDialog(object):
536
564
 
537
565
  self.horizontalLayout_21.addWidget(self.sb_vmin)
538
566
 
539
- self.label_21 = QLabel(self.groupBox)
540
- 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")
541
569
 
542
- self.horizontalLayout_21.addWidget(self.label_21)
570
+ self.horizontalLayout_21.addWidget(self.label_vmin_2)
543
571
 
544
572
  self.horizontalSpacer_7 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
545
573
 
@@ -550,10 +578,10 @@ class Ui_prefDialog(object):
550
578
 
551
579
  self.horizontalLayout_22 = QHBoxLayout()
552
580
  self.horizontalLayout_22.setObjectName(u"horizontalLayout_22")
553
- self.label_20 = QLabel(self.groupBox)
554
- self.label_20.setObjectName(u"label_20")
581
+ self.label_vmax = QLabel(self.groupBox)
582
+ self.label_vmax.setObjectName(u"label_vmax")
555
583
 
556
- self.horizontalLayout_22.addWidget(self.label_20)
584
+ self.horizontalLayout_22.addWidget(self.label_vmax)
557
585
 
558
586
  self.sb_vmax = QSpinBox(self.groupBox)
559
587
  self.sb_vmax.setObjectName(u"sb_vmax")
@@ -563,10 +591,10 @@ class Ui_prefDialog(object):
563
591
 
564
592
  self.horizontalLayout_22.addWidget(self.sb_vmax)
565
593
 
566
- self.label_22 = QLabel(self.groupBox)
567
- 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")
568
596
 
569
- self.horizontalLayout_22.addWidget(self.label_22)
597
+ self.horizontalLayout_22.addWidget(self.label_vmax_2)
570
598
 
571
599
  self.horizontalSpacer_8 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
572
600
 
@@ -695,7 +723,7 @@ class Ui_prefDialog(object):
695
723
 
696
724
  self.retranslateUi(prefDialog)
697
725
 
698
- self.tabWidget.setCurrentIndex(1)
726
+ self.tabWidget.setCurrentIndex(4)
699
727
 
700
728
 
701
729
  QMetaObject.connectSlotsByName(prefDialog)
@@ -739,6 +767,7 @@ class Ui_prefDialog(object):
739
767
  self.pbBrowseFFmpegCacheDir.setText(QCoreApplication.translate("prefDialog", u"...", None))
740
768
  self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_ffmpeg), QCoreApplication.translate("prefDialog", u"FFmpeg framework", None))
741
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))
742
771
  self.label_7.setText(QCoreApplication.translate("prefDialog", u"Color map", None))
743
772
  self.label_12.setText(QCoreApplication.translate("prefDialog", u"Default time interval (s)", None))
744
773
  self.label_16.setText(QCoreApplication.translate("prefDialog", u"Window type", None))
@@ -753,10 +782,11 @@ class Ui_prefDialog(object):
753
782
  self.cb_NFFT.setItemText(3, QCoreApplication.translate("prefDialog", u"2048", None))
754
783
 
755
784
  self.label_18.setText(QCoreApplication.translate("prefDialog", u"noverlap", None))
756
- self.label_19.setText(QCoreApplication.translate("prefDialog", u"vmin", None))
757
- self.label_21.setText(QCoreApplication.translate("prefDialog", u"dBFS", None))
758
- self.label_20.setText(QCoreApplication.translate("prefDialog", u"vmax", None))
759
- 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))
760
790
  self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_spectro), QCoreApplication.translate("prefDialog", u"Spectrogram/Wave form", None))
761
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))
762
792
  self.pb_reset_behav_colors.setText(QCoreApplication.translate("prefDialog", u"Reset colors to default", None))
boris/project.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2025 Olivier Friard
4
+ Copyright 2012-2026 Olivier Friard
5
5
 
6
6
  This file is part of BORIS.
7
7
 
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2025 Olivier Friard
4
+ Copyright 2012-2026 Olivier Friard
5
5
 
6
6
  This program is free software; you can redistribute it and/or modify
7
7
  it under the terms of the GNU General Public License as published by
@@ -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(
@@ -350,7 +347,7 @@ def check_state_events_obs(obsId: str, ethogram: dict, observation: dict, time_f
350
347
  return (False, out) if out else (True, "No problem detected")
351
348
 
352
349
 
353
- def check_state_events(pj: dict, observations_list: list) -> Tuple[bool, tuple]:
350
+ def check_state_events(pj: dict, observations_list: list) -> tuple[bool, list]:
354
351
  """
355
352
  check if state events are paired in a list of observations
356
353
  use check_state_events_obs function
@@ -425,9 +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>"
428
- flag_all_behaviors_defined = False
429
- else:
430
- flag_all_behaviors_defined = True
425
+ # flag_all_behaviors_defined = False
426
+ # else:
427
+ # flag_all_behaviors_defined = True
431
428
 
432
429
  # check for unpaired state events
433
430
  for obs_id in pj[cfg.OBSERVATIONS]:
@@ -733,13 +730,13 @@ def create_subtitles(pj: dict, selected_observations: list, parameters: dict, ex
733
730
  mem_command = dialog.MessageDialog(
734
731
  cfg.programName,
735
732
  f"The file {file_name} already exists.",
736
- [
733
+ (
737
734
  cfg.OVERWRITE,
738
735
  cfg.OVERWRITE_ALL,
739
736
  cfg.SKIP,
740
737
  cfg.SKIP_ALL,
741
738
  cfg.CANCEL,
742
- ],
739
+ ),
743
740
  )
744
741
  if mem_command == cfg.CANCEL:
745
742
  return False, ""
@@ -840,13 +837,13 @@ def create_subtitles(pj: dict, selected_observations: list, parameters: dict, ex
840
837
  mem_command = dialog.MessageDialog(
841
838
  cfg.programName,
842
839
  f"The file {file_name} already exists.",
843
- [
840
+ (
844
841
  cfg.OVERWRITE,
845
842
  cfg.OVERWRITE_ALL,
846
843
  cfg.SKIP,
847
844
  cfg.SKIP_ALL,
848
845
  cfg.CANCEL,
849
- ],
846
+ ),
850
847
  )
851
848
  if mem_command == cfg.CANCEL:
852
849
  return False, ""
@@ -1148,7 +1145,7 @@ def remove_media_files_path(pj: dict, project_file_name: str) -> bool:
1148
1145
  f"{',<br>'.join(file_not_found)}"
1149
1146
  "<br><br>Are you sure to continue?"
1150
1147
  ),
1151
- [cfg.YES, cfg.NO],
1148
+ (cfg.YES, cfg.NO),
1152
1149
  )
1153
1150
  == cfg.NO
1154
1151
  ):
@@ -1218,7 +1215,7 @@ def full_path(path: str, project_file_name: str) -> str:
1218
1215
  return ""
1219
1216
 
1220
1217
 
1221
- def observed_interval(observation: dict) -> Tuple[dec, dec]:
1218
+ def observed_interval(observation: dict) -> tuple[dec, dec] | None:
1222
1219
  """
1223
1220
  Observed interval for observation
1224
1221
 
@@ -1583,7 +1580,7 @@ def open_project_json(project_file_name: str) -> tuple:
1583
1580
  copyfile(project_file_name, old_project_file_name)
1584
1581
  msg += f"\n\nThe old file project was saved as {old_project_file_name}"
1585
1582
  except Exception:
1586
- QMessageBox.critical(cfg.programName, f"Error saving old project to {old_project_file_name}")
1583
+ QMessageBox.critical(None, cfg.programName, f"Error saving old project to {old_project_file_name}")
1587
1584
 
1588
1585
  pj[cfg.PROJECT_VERSION] = cfg.project_format_version
1589
1586
 
@@ -1805,7 +1802,7 @@ def explore_project(self) -> None:
1805
1802
  if results:
1806
1803
  self.results_dialog = dialog.View_explore_project_results()
1807
1804
  self.results_dialog.setWindowTitle("Explore project results")
1808
- self.results_dialog.setWindowFlags(Qt.WindowStaysOnTopHint)
1805
+ self.results_dialog.setWindowFlags(Qt.WindowType.WindowStaysOnTopHint)
1809
1806
  self.results_dialog.double_click_signal.connect(double_click_explore_project)
1810
1807
  txt = f"<b>{len(results)}</b> events"
1811
1808
  txt2 = ""
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2025 Olivier Friard
4
+ Copyright 2012-2026 Olivier Friard
5
5
 
6
6
  This file is part of BORIS.
7
7
 
boris/select_modifiers.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2025 Olivier Friard
4
+ Copyright 2012-2026 Olivier Friard
5
5
 
6
6
  This file is part of BORIS.
7
7