boris-behav-obs 9.7.1__py3-none-any.whl → 9.7.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- boris/about.py +5 -5
- boris/add_modifier_ui.py +47 -29
- boris/analysis_plugins/_export_to_feral.py +225 -0
- boris/behav_coding_map_creator.py +7 -7
- boris/coding_pad.py +4 -3
- boris/config.py +7 -0
- boris/config_file.py +3 -3
- boris/core.py +98 -60
- 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 +3 -0
- boris/plot_waveform_rt.py +4 -1
- boris/plugins.py +60 -23
- boris/preferences.py +9 -0
- boris/preferences_ui.py +48 -28
- boris/project.py +1 -1
- boris/project_functions.py +26 -10
- 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.12.dist-info}/METADATA +3 -4
- {boris_behav_obs-9.7.1.dist-info → boris_behav_obs-9.7.12.dist-info}/RECORD +30 -29
- {boris_behav_obs-9.7.1.dist-info → boris_behav_obs-9.7.12.dist-info}/WHEEL +0 -0
- {boris_behav_obs-9.7.1.dist-info → boris_behav_obs-9.7.12.dist-info}/entry_points.txt +0 -0
- {boris_behav_obs-9.7.1.dist-info → boris_behav_obs-9.7.12.dist-info}/licenses/LICENSE.TXT +0 -0
- {boris_behav_obs-9.7.1.dist-info → boris_behav_obs-9.7.12.dist-info}/top_level.txt +0 -0
boris/observation_operations.py
CHANGED
|
@@ -26,7 +26,7 @@ from decimal import Decimal as dec
|
|
|
26
26
|
import json
|
|
27
27
|
from math import log2, floor
|
|
28
28
|
import os
|
|
29
|
-
|
|
29
|
+
from pathlib import Path
|
|
30
30
|
import socket
|
|
31
31
|
import subprocess
|
|
32
32
|
import sys
|
|
@@ -89,10 +89,10 @@ def export_observations_list_clicked(self):
|
|
|
89
89
|
return
|
|
90
90
|
|
|
91
91
|
output_format = cfg.FILE_NAME_SUFFIX[filter_]
|
|
92
|
-
if
|
|
93
|
-
file_name = str(
|
|
92
|
+
if Path(file_name).suffix != "." + output_format:
|
|
93
|
+
file_name = str(Path(file_name)) + "." + output_format
|
|
94
94
|
# check if file name with extension already exists
|
|
95
|
-
if
|
|
95
|
+
if Path(file_name).is_file():
|
|
96
96
|
if dialog.MessageDialog(cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE]) == cfg.CANCEL:
|
|
97
97
|
return
|
|
98
98
|
|
|
@@ -113,6 +113,8 @@ def observations_list(self):
|
|
|
113
113
|
result, selected_obs = select_observations.select_observations2(self, cfg.SINGLE)
|
|
114
114
|
|
|
115
115
|
if not selected_obs:
|
|
116
|
+
# activate main window
|
|
117
|
+
self.activateWindow()
|
|
116
118
|
return
|
|
117
119
|
|
|
118
120
|
if self.observationId:
|
|
@@ -122,6 +124,9 @@ def observations_list(self):
|
|
|
122
124
|
)
|
|
123
125
|
if response == cfg.NO:
|
|
124
126
|
self.show_data_files()
|
|
127
|
+
# activate main window
|
|
128
|
+
self.activateWindow()
|
|
129
|
+
|
|
125
130
|
return ""
|
|
126
131
|
else:
|
|
127
132
|
close_observation(self)
|
|
@@ -145,6 +150,8 @@ def observations_list(self):
|
|
|
145
150
|
)
|
|
146
151
|
|
|
147
152
|
logging.debug("end observations list")
|
|
153
|
+
# activate main window
|
|
154
|
+
self.activateWindow()
|
|
148
155
|
|
|
149
156
|
|
|
150
157
|
def open_observation(self, mode: str) -> str:
|
|
@@ -594,7 +601,7 @@ def new_observation(self, mode: str = cfg.NEW, obsId: str = "") -> None:
|
|
|
594
601
|
close_observation(self)
|
|
595
602
|
|
|
596
603
|
observationWindow = observation.Observation(
|
|
597
|
-
tmp_dir=self.ffmpeg_cache_dir if (self.ffmpeg_cache_dir and
|
|
604
|
+
tmp_dir=self.ffmpeg_cache_dir if (self.ffmpeg_cache_dir and Path(self.ffmpeg_cache_dir).is_dir()) else tempfile.gettempdir(),
|
|
598
605
|
project_path=self.projectFileName,
|
|
599
606
|
converters=self.pj.get(cfg.CONVERTERS, {}),
|
|
600
607
|
time_format=self.timeFormat,
|
|
@@ -1151,6 +1158,9 @@ def close_observation(self):
|
|
|
1151
1158
|
logging.info("Stop plot timer")
|
|
1152
1159
|
self.plot_timer.stop()
|
|
1153
1160
|
|
|
1161
|
+
if self.MPV_IPC_MODE:
|
|
1162
|
+
self.main_window_activation_timer.stop()
|
|
1163
|
+
|
|
1154
1164
|
for i, player in enumerate(self.dw_player):
|
|
1155
1165
|
if (
|
|
1156
1166
|
str(i + 1) in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE]
|
|
@@ -1304,7 +1314,7 @@ def check_creation_date(self) -> Tuple[int, dict]:
|
|
|
1304
1314
|
|
|
1305
1315
|
if ret == 1: # use file creation time
|
|
1306
1316
|
for media in not_tagged_media_list:
|
|
1307
|
-
media_creation_time[media] =
|
|
1317
|
+
media_creation_time[media] = Path(media).stat().st_ctime
|
|
1308
1318
|
return (0, media_creation_time) # OK use media file creation date/time
|
|
1309
1319
|
else:
|
|
1310
1320
|
return (1, {})
|
|
@@ -1340,8 +1350,6 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
1340
1350
|
self.playerType = cfg.VIEWER_MEDIA
|
|
1341
1351
|
return True
|
|
1342
1352
|
|
|
1343
|
-
# print(f"{self.process=}")
|
|
1344
|
-
|
|
1345
1353
|
self.playerType = cfg.MEDIA
|
|
1346
1354
|
self.fps = 0
|
|
1347
1355
|
|
|
@@ -1365,7 +1373,7 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
1365
1373
|
|
|
1366
1374
|
# add all media files to media lists
|
|
1367
1375
|
self.setDockOptions(QMainWindow.AnimatedDocks | QMainWindow.AllowNestedDocks)
|
|
1368
|
-
self.dw_player
|
|
1376
|
+
self.dw_player = []
|
|
1369
1377
|
|
|
1370
1378
|
# check if media creation time used as offset
|
|
1371
1379
|
# TODO check if cfg.MEDIA_CREATION_TIME dict is present
|
|
@@ -1831,8 +1839,8 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
1831
1839
|
self.dw_player[i].resize_signal.connect(self.resize_dw)
|
|
1832
1840
|
|
|
1833
1841
|
# add durations list
|
|
1834
|
-
self.dw_player[i].media_durations
|
|
1835
|
-
self.dw_player[i].cumul_media_durations
|
|
1842
|
+
self.dw_player[i].media_durations = []
|
|
1843
|
+
self.dw_player[i].cumul_media_durations = [0] # [idx for idx,x in enumerate(l) if l[idx-1]<pos<=x]
|
|
1836
1844
|
|
|
1837
1845
|
# add fps list
|
|
1838
1846
|
self.dw_player[i].fps = {}
|
|
@@ -1844,6 +1852,13 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
1844
1852
|
if r:
|
|
1845
1853
|
break
|
|
1846
1854
|
|
|
1855
|
+
# start timer for activating the main window
|
|
1856
|
+
self.main_window_activation_timer = QTimer()
|
|
1857
|
+
self.main_window_activation_timer.setInterval(500)
|
|
1858
|
+
# self.main_window_activation_timer.timeout.connect(self.activateWindow)
|
|
1859
|
+
self.main_window_activation_timer.timeout.connect(self.activate_main_window)
|
|
1860
|
+
self.main_window_activation_timer.start()
|
|
1861
|
+
|
|
1847
1862
|
for mediaFile in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE][n_player]:
|
|
1848
1863
|
logging.debug(f"media file: {mediaFile}")
|
|
1849
1864
|
|
|
@@ -1887,7 +1902,7 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
1887
1902
|
self.dw_player[i].player.playlist_append(media_full_path)
|
|
1888
1903
|
|
|
1889
1904
|
# add media file name to player window title
|
|
1890
|
-
self.dw_player[i].setWindowTitle(f"Player #{i + 1} ({
|
|
1905
|
+
self.dw_player[i].setWindowTitle(f"Player #{i + 1} ({Path(media_full_path).name})")
|
|
1891
1906
|
|
|
1892
1907
|
# media duration cumuled in seconds
|
|
1893
1908
|
self.dw_player[i].cumul_media_durations_sec = [round(dec(x / 1000), 3) for x in self.dw_player[i].cumul_media_durations]
|
|
@@ -1964,7 +1979,7 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
1964
1979
|
|
|
1965
1980
|
menu_options.update_menu(self)
|
|
1966
1981
|
|
|
1967
|
-
if
|
|
1982
|
+
if self.MPV_IPC_MODE:
|
|
1968
1983
|
# activate timer
|
|
1969
1984
|
self.ipc_mpv_timer = QTimer()
|
|
1970
1985
|
self.ipc_mpv_timer.setInterval(500)
|
|
@@ -1996,7 +2011,7 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
1996
2011
|
tmp_dir = self.ffmpeg_cache_dir if self.ffmpeg_cache_dir and os.path.isdir(self.ffmpeg_cache_dir) else tempfile.gettempdir()
|
|
1997
2012
|
|
|
1998
2013
|
wav_file_path = (
|
|
1999
|
-
|
|
2014
|
+
Path(tmp_dir) / Path(self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"] + ".wav").name
|
|
2000
2015
|
)
|
|
2001
2016
|
|
|
2002
2017
|
if not wav_file_path.is_file():
|
|
@@ -2012,7 +2027,7 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
2012
2027
|
tmp_dir = self.ffmpeg_cache_dir if self.ffmpeg_cache_dir and os.path.isdir(self.ffmpeg_cache_dir) else tempfile.gettempdir()
|
|
2013
2028
|
|
|
2014
2029
|
wav_file_path = (
|
|
2015
|
-
|
|
2030
|
+
Path(tmp_dir) / Path(self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"] + ".wav").name
|
|
2016
2031
|
)
|
|
2017
2032
|
|
|
2018
2033
|
if not wav_file_path.is_file():
|
|
@@ -2296,8 +2311,8 @@ def initialize_new_images_observation(self):
|
|
|
2296
2311
|
sorted(
|
|
2297
2312
|
list(
|
|
2298
2313
|
set(
|
|
2299
|
-
[str(x) for x in
|
|
2300
|
-
+ [str(x) for x in
|
|
2314
|
+
[str(x) for x in Path(full_dir_path).glob(pattern)]
|
|
2315
|
+
+ [str(x) for x in Path(full_dir_path).glob(pattern.upper())]
|
|
2301
2316
|
)
|
|
2302
2317
|
)
|
|
2303
2318
|
)
|
|
@@ -2422,26 +2437,31 @@ def event2media_file_name(observation: dict, timestamp: dec) -> Optional[str]:
|
|
|
2422
2437
|
|
|
2423
2438
|
def create_observations(self):
|
|
2424
2439
|
"""
|
|
2425
|
-
Create observations from a media
|
|
2440
|
+
Create observations from a directory of media files
|
|
2426
2441
|
"""
|
|
2427
|
-
# print(self.pj[cfg.OBSERVATIONS])
|
|
2428
2442
|
|
|
2429
|
-
dir_path
|
|
2430
|
-
if not dir_path:
|
|
2443
|
+
if not (dir_path := QFileDialog.getExistingDirectory(None, "Select directory", os.getenv("HOME"))):
|
|
2431
2444
|
return
|
|
2432
2445
|
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
elements_list
|
|
2446
|
+
elements_list: list = []
|
|
2447
|
+
if util.is_subdir(Path(dir_path), Path(self.projectFileName).parent):
|
|
2448
|
+
elements_list.append(("cb", "Use relative paths", False))
|
|
2449
|
+
|
|
2450
|
+
elements_list.extend(
|
|
2451
|
+
[
|
|
2436
2452
|
("cb", "Recurse the subdirectories", False),
|
|
2437
|
-
("cb", "Save the absolute media file path", True),
|
|
2438
2453
|
("cb", "Visualize spectrogram", False),
|
|
2439
2454
|
("cb", "Visualize waveform", False),
|
|
2440
2455
|
("cb", "Media creation date as offset", False),
|
|
2441
2456
|
("cb", "Close behaviors between videos", False),
|
|
2442
2457
|
("dsb", "Time offset (in seconds)", 0.0, 86400, 1, 0, 3),
|
|
2443
2458
|
("dsb", "Media scan sampling duration (in seconds)", 0.0, 86400, 1, 0, 3),
|
|
2444
|
-
]
|
|
2459
|
+
]
|
|
2460
|
+
)
|
|
2461
|
+
|
|
2462
|
+
dlg = dialog.Input_dialog(
|
|
2463
|
+
label_caption="Set the following observation parameters",
|
|
2464
|
+
elements_list=elements_list,
|
|
2445
2465
|
title="Observation parameters",
|
|
2446
2466
|
)
|
|
2447
2467
|
if not dlg.exec_():
|
|
@@ -2450,9 +2470,9 @@ def create_observations(self):
|
|
|
2450
2470
|
file_count: int = 0
|
|
2451
2471
|
|
|
2452
2472
|
if dlg.elements["Recurse the subdirectories"].isChecked():
|
|
2453
|
-
files_list =
|
|
2473
|
+
files_list = Path(dir_path).rglob("*")
|
|
2454
2474
|
else:
|
|
2455
|
-
files_list =
|
|
2475
|
+
files_list = Path(dir_path).glob("*")
|
|
2456
2476
|
|
|
2457
2477
|
for file in files_list:
|
|
2458
2478
|
if not file.is_file():
|
|
@@ -2462,22 +2482,25 @@ def create_observations(self):
|
|
|
2462
2482
|
if not r.get("frames_number", 0):
|
|
2463
2483
|
continue
|
|
2464
2484
|
|
|
2465
|
-
if dlg.elements["
|
|
2466
|
-
media_file = str(file)
|
|
2485
|
+
if "Use relative paths" in dlg.elements and dlg.elements["Use relative paths"].isChecked():
|
|
2486
|
+
media_file = str(file.relative_to(Path(self.projectFileName).parent))
|
|
2467
2487
|
else:
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2488
|
+
media_file = str(file)
|
|
2489
|
+
|
|
2490
|
+
# else:
|
|
2491
|
+
# try:
|
|
2492
|
+
# media_file = str(file.relative_to(Path(self.projectFileName).parent))
|
|
2493
|
+
# except ValueError:
|
|
2494
|
+
# QMessageBox.critical(
|
|
2495
|
+
# self,
|
|
2496
|
+
# cfg.programName,
|
|
2497
|
+
# (
|
|
2498
|
+
# f"the media file <b>{file}</b> can not be relative to the project directory "
|
|
2499
|
+
# f"(<b>{Path(self.projectFileName).parent}</b>)"
|
|
2500
|
+
# "<br><br>Aborting the creation of observations"
|
|
2501
|
+
# ),
|
|
2502
|
+
# )
|
|
2503
|
+
# return
|
|
2481
2504
|
|
|
2482
2505
|
if media_file in self.pj[cfg.OBSERVATIONS]:
|
|
2483
2506
|
QMessageBox.critical(
|
|
@@ -2518,4 +2541,6 @@ def create_observations(self):
|
|
|
2518
2541
|
else:
|
|
2519
2542
|
message: str = f"No media file were found in {dir_path}"
|
|
2520
2543
|
|
|
2544
|
+
menu_options.update_menu(self)
|
|
2545
|
+
|
|
2521
2546
|
QMessageBox.information(self, cfg.programName, message)
|
boris/plot_data_module.py
CHANGED
boris/plot_spectrogram_rt.py
CHANGED
|
@@ -205,6 +205,9 @@ class Plot_spectrogram_RT(QWidget):
|
|
|
205
205
|
vmin = self.config_param.get(cfg.SPECTROGRAM_VMIN, cfg.SPECTROGRAM_DEFAULT_VMIN)
|
|
206
206
|
vmax = self.config_param.get(cfg.SPECTROGRAM_VMAX, cfg.SPECTROGRAM_DEFAULT_VMAX)
|
|
207
207
|
|
|
208
|
+
if current_time is None:
|
|
209
|
+
return
|
|
210
|
+
|
|
208
211
|
# start
|
|
209
212
|
if current_time <= self.interval / 2:
|
|
210
213
|
self.ax.specgram(
|
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
|
@@ -221,6 +221,9 @@ def preferences(self):
|
|
|
221
221
|
preferencesWindow.cbConfirmSound.setChecked(self.confirmSound)
|
|
222
222
|
# beep every
|
|
223
223
|
preferencesWindow.sbBeepEvery.setValue(self.beep_every)
|
|
224
|
+
# frame step size
|
|
225
|
+
# preferencesWindow.sb_frame_step_size.setValue(self.config_param.get(cfg.FRAME_STEP_SIZE, cfg.FRAME_STEP_SIZE_DEFAULT_VALUE))
|
|
226
|
+
|
|
224
227
|
# alert no focal subject
|
|
225
228
|
preferencesWindow.cbAlertNoFocalSubject.setChecked(self.alertNoFocalSubject)
|
|
226
229
|
# tracking cursor above event
|
|
@@ -418,6 +421,9 @@ def preferences(self):
|
|
|
418
421
|
|
|
419
422
|
self.beep_every = preferencesWindow.sbBeepEvery.value()
|
|
420
423
|
|
|
424
|
+
# frame step size
|
|
425
|
+
# self.config_param[cfg.FRAME_STEP_SIZE] = preferencesWindow.sb_frame_step_size.value()
|
|
426
|
+
|
|
421
427
|
self.alertNoFocalSubject = preferencesWindow.cbAlertNoFocalSubject.isChecked()
|
|
422
428
|
|
|
423
429
|
self.trackingCursorAboveEvent = preferencesWindow.cbTrackingCursorAboveEvent.isChecked()
|
|
@@ -499,3 +505,6 @@ def preferences(self):
|
|
|
499
505
|
|
|
500
506
|
else:
|
|
501
507
|
break
|
|
508
|
+
|
|
509
|
+
# activate main window
|
|
510
|
+
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
|
################################################################################
|
|
@@ -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)
|
|
@@ -676,7 +695,7 @@ class Ui_prefDialog(object):
|
|
|
676
695
|
|
|
677
696
|
self.retranslateUi(prefDialog)
|
|
678
697
|
|
|
679
|
-
self.tabWidget.setCurrentIndex(
|
|
698
|
+
self.tabWidget.setCurrentIndex(1)
|
|
680
699
|
|
|
681
700
|
|
|
682
701
|
QMetaObject.connectSlotsByName(prefDialog)
|
|
@@ -707,6 +726,7 @@ class Ui_prefDialog(object):
|
|
|
707
726
|
self.cbTrackingCursorAboveEvent.setText(QCoreApplication.translate("prefDialog", u"Tracking cursor above current event", None))
|
|
708
727
|
self.cbAlertNoFocalSubject.setText(QCoreApplication.translate("prefDialog", u"Alert if focal subject is not set", None))
|
|
709
728
|
self.cb_pause_before_addevent.setText(QCoreApplication.translate("prefDialog", u"Pause media before \"Add event\" command", None))
|
|
729
|
+
self.label_24.setText(QCoreApplication.translate("prefDialog", u"Frame step size", None))
|
|
710
730
|
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_observations), QCoreApplication.translate("prefDialog", u"Observations", None))
|
|
711
731
|
self.label_13.setText(QCoreApplication.translate("prefDialog", u"BORIS plugins", None))
|
|
712
732
|
self.label_15.setText(QCoreApplication.translate("prefDialog", u"Personal plugins", 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(),
|