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/about.py +5 -5
- boris/add_modifier_ui.py +47 -29
- boris/analysis_plugins/export_to_feral.py +336 -0
- boris/behav_coding_map_creator.py +7 -7
- boris/coding_pad.py +4 -3
- boris/config.py +12 -2
- boris/config_file.py +3 -3
- boris/converters_ui.py +2 -3
- boris/core.py +223 -182
- boris/ipc_mpv.py +52 -31
- boris/modifier_coding_map_creator.py +6 -6
- boris/observation.py +8 -1
- boris/observation_operations.py +68 -43
- boris/plot_data_module.py +2 -0
- boris/plot_spectrogram_rt.py +43 -71
- boris/plot_waveform_rt.py +4 -1
- boris/plugins.py +60 -23
- boris/preferences.py +43 -3
- boris/preferences_ui.py +95 -45
- boris/project.py +1 -1
- boris/project_functions.py +33 -20
- boris/subjects_pad.py +2 -2
- boris/utilities.py +32 -4
- boris/version.py +2 -2
- boris/video_operations.py +9 -1
- {boris_behav_obs-9.7.1.dist-info → boris_behav_obs-9.7.15.dist-info}/METADATA +3 -4
- {boris_behav_obs-9.7.1.dist-info → boris_behav_obs-9.7.15.dist-info}/RECORD +31 -30
- {boris_behav_obs-9.7.1.dist-info → boris_behav_obs-9.7.15.dist-info}/WHEEL +0 -0
- {boris_behav_obs-9.7.1.dist-info → boris_behav_obs-9.7.15.dist-info}/entry_points.txt +0 -0
- {boris_behav_obs-9.7.1.dist-info → boris_behav_obs-9.7.15.dist-info}/licenses/LICENSE.TXT +0 -0
- {boris_behav_obs-9.7.1.dist-info → boris_behav_obs-9.7.15.dist-info}/top_level.txt +0 -0
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
|
-
|
|
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
|
-
|
|
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")
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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 =
|
|
148
|
+
self.sbffSpeed = QDoubleSpinBox(self.tab_observations)
|
|
149
149
|
self.sbffSpeed.setObjectName(u"sbffSpeed")
|
|
150
|
-
self.sbffSpeed.
|
|
151
|
-
self.sbffSpeed.setMaximum(
|
|
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.
|
|
260
|
-
self.
|
|
261
|
-
self.verticalLayout_11 = QVBoxLayout(self.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
305
|
-
self.
|
|
306
|
-
self.verticalLayout_12 = QVBoxLayout(self.
|
|
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.
|
|
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.
|
|
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.
|
|
321
|
-
self.
|
|
322
|
-
self.
|
|
323
|
-
self.verticalLayout_14 = QVBoxLayout(self.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
508
|
-
self.
|
|
554
|
+
self.label_vmin = QLabel(self.groupBox)
|
|
555
|
+
self.label_vmin.setObjectName(u"label_vmin")
|
|
509
556
|
|
|
510
|
-
self.horizontalLayout_21.addWidget(self.
|
|
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.
|
|
521
|
-
self.
|
|
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.
|
|
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.
|
|
535
|
-
self.
|
|
581
|
+
self.label_vmax = QLabel(self.groupBox)
|
|
582
|
+
self.label_vmax.setObjectName(u"label_vmax")
|
|
536
583
|
|
|
537
|
-
self.horizontalLayout_22.addWidget(self.
|
|
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.
|
|
548
|
-
self.
|
|
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.
|
|
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(
|
|
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.
|
|
737
|
-
self.
|
|
738
|
-
self.
|
|
739
|
-
self.
|
|
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(),
|
boris/project_functions.py
CHANGED
|
@@ -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
|
|
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][
|
|
581
|
+
if pj[cfg.ETHOGRAM][idx][cfg.BEHAVIOR_CODE] == event[cfg.EVENT_BEHAVIOR_FIELD_IDX]:
|
|
583
582
|
break
|
|
584
583
|
else:
|
|
585
|
-
|
|
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
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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[
|
|
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 = ""
|