boris-behav-obs 8.27.9__py2.py3-none-any.whl → 9.0.1__py2.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 +7 -5
- boris/add_modifier.py +35 -35
- boris/add_modifier_ui.py +229 -129
- boris/advanced_event_filtering.py +3 -3
- boris/analysis_plugins/__init__.py +0 -0
- boris/analysis_plugins/number_of_occurences.py +60 -0
- boris/analysis_plugins/number_of_occurences_by_independent_variable.py +72 -0
- boris/analysis_plugins/time_budget.py +95 -0
- boris/behav_coding_map_creator.py +103 -108
- boris/behavior_binary_table.py +1 -1
- boris/behaviors_coding_map.py +8 -8
- boris/coding_pad.py +6 -6
- boris/config.py +6 -0
- boris/config_file.py +1 -1
- boris/connections.py +4 -2
- boris/converters.py +2 -3
- boris/converters_ui.py +187 -110
- boris/cooccurence.py +2 -2
- boris/core.py +340 -94
- boris/core_qrc.py +16088 -13246
- boris/core_ui.py +922 -812
- boris/db_functions.py +3 -1
- boris/dialog.py +14 -13
- boris/duration_widget.py +5 -5
- boris/edit_event.py +1 -1
- boris/edit_event_ui.py +162 -88
- boris/event_operations.py +4 -25
- boris/events_cursor.py +17 -9
- boris/events_snapshots.py +5 -5
- boris/exclusion_matrix.py +1 -1
- boris/export_events.py +38 -28
- boris/export_observation.py +1 -1
- boris/external_processes.py +3 -5
- boris/geometric_measurement.py +49 -26
- boris/gui_utilities.py +31 -30
- boris/import_observations.py +2 -4
- boris/irr.py +1 -1
- boris/latency.py +1 -1
- boris/map_creator.py +77 -89
- boris/measurement_widget.py +4 -4
- boris/media_file.py +2 -4
- boris/menu_options.py +1 -3
- boris/modifiers_coding_map.py +4 -4
- boris/mpv2.py +0 -2
- boris/observation.py +124 -29
- boris/observation_operations.py +18 -40
- boris/observation_ui.py +566 -374
- boris/observations_list.py +6 -6
- boris/param_panel.py +2 -2
- boris/param_panel_ui.py +246 -141
- boris/player_dock_widget.py +16 -21
- boris/plot_data_module.py +6 -6
- boris/plot_events_rt.py +7 -8
- boris/plot_spectrogram_rt.py +7 -8
- boris/plot_waveform_rt.py +6 -7
- boris/plugins.py +79 -0
- boris/preferences.py +127 -17
- boris/preferences_ui.py +464 -240
- boris/project.py +69 -72
- boris/project_functions.py +233 -31
- boris/project_import_export.py +59 -67
- boris/project_ui.py +672 -440
- boris/qrc_boris.py +6 -3
- boris/qrc_boris5.py +6 -3
- boris/select_modifiers.py +2 -2
- boris/select_observations.py +2 -2
- boris/select_subj_behav.py +3 -3
- boris/state_events.py +1 -1
- boris/subjects_pad.py +5 -5
- boris/synthetic_time_budget.py +2 -2
- boris/time_budget_functions.py +15 -0
- boris/time_budget_widget.py +4 -4
- boris/transitions.py +34 -25
- boris/utilities.py +95 -2
- boris/version.py +2 -2
- boris/video_equalizer.py +4 -4
- boris/video_equalizer_ui.py +199 -130
- boris/video_operations.py +1 -1
- boris/view_df.py +106 -0
- boris/view_df_ui.py +75 -0
- boris/write_event.py +9 -1
- {boris_behav_obs-8.27.9.dist-info → boris_behav_obs-9.0.1.dist-info}/METADATA +5 -5
- boris_behav_obs-9.0.1.dist-info/RECORD +103 -0
- {boris_behav_obs-8.27.9.dist-info → boris_behav_obs-9.0.1.dist-info}/WHEEL +1 -1
- boris/qdarkstyle/__init__.py +0 -479
- boris/qdarkstyle/__main__.py +0 -66
- boris/qdarkstyle/colorsystem.py +0 -38
- boris/qdarkstyle/dark/__init__.py +0 -1
- boris/qdarkstyle/dark/darkstyle_rc.py +0 -11379
- boris/qdarkstyle/dark/palette.py +0 -38
- boris/qdarkstyle/example/__init__.py +0 -4
- boris/qdarkstyle/example/__main__.py +0 -386
- boris/qdarkstyle/example/ui/__init__.py +0 -4
- boris/qdarkstyle/light/__init__.py +0 -1
- boris/qdarkstyle/light/lightstyle_rc.py +0 -11305
- boris/qdarkstyle/light/palette.py +0 -37
- boris/qdarkstyle/palette.py +0 -102
- boris/qdarkstyle/utils/__init__.py +0 -73
- boris/qdarkstyle/utils/__main__.py +0 -96
- boris/qdarkstyle/utils/images.py +0 -449
- boris/qdarkstyle/utils/scss.py +0 -318
- boris/vlc_local.py +0 -83
- boris_behav_obs-8.27.9.dist-info/RECORD +0 -114
- {boris_behav_obs-8.27.9.dist-info → boris_behav_obs-9.0.1.dist-info}/LICENSE.TXT +0 -0
- {boris_behav_obs-8.27.9.dist-info → boris_behav_obs-9.0.1.dist-info}/entry_points.txt +0 -0
- {boris_behav_obs-8.27.9.dist-info → boris_behav_obs-9.0.1.dist-info}/top_level.txt +0 -0
boris/core.py
CHANGED
|
@@ -26,7 +26,7 @@ import sys
|
|
|
26
26
|
os.environ["PATH"] = os.path.dirname(__file__) + os.sep + "misc" + os.pathsep + os.environ["PATH"]
|
|
27
27
|
|
|
28
28
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".")))
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
|
|
31
31
|
import datetime
|
|
32
32
|
|
|
@@ -56,37 +56,27 @@ import matplotlib
|
|
|
56
56
|
import zipfile
|
|
57
57
|
import shutil
|
|
58
58
|
|
|
59
|
-
matplotlib.use("
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
QAbstractTableModel,
|
|
70
|
-
QT_VERSION_STR,
|
|
71
|
-
PYQT_VERSION_STR,
|
|
72
|
-
)
|
|
73
|
-
from PyQt5.QtGui import QIcon, QPixmap, QFont, QKeyEvent, QDesktopServices, QColor, QPainter, QPolygon
|
|
74
|
-
from PyQt5.QtMultimedia import QSound
|
|
75
|
-
from PyQt5.QtWidgets import (
|
|
76
|
-
QLabel,
|
|
77
|
-
QMessageBox,
|
|
78
|
-
QMainWindow,
|
|
79
|
-
QListWidgetItem,
|
|
59
|
+
matplotlib.use("QtAgg")
|
|
60
|
+
|
|
61
|
+
import PySide6
|
|
62
|
+
from PySide6.QtCore import Qt, QPoint, Signal, QEvent, QDateTime, QUrl, QAbstractTableModel, qVersion, QElapsedTimer
|
|
63
|
+
from PySide6.QtGui import QIcon, QPixmap, QFont, QKeyEvent, QDesktopServices, QColor, QPainter, QPolygon, QAction
|
|
64
|
+
from PySide6.QtMultimedia import QSoundEffect
|
|
65
|
+
from PySide6.QtWidgets import (
|
|
66
|
+
QAbstractItemView,
|
|
67
|
+
QApplication,
|
|
68
|
+
QDockWidget,
|
|
80
69
|
QFileDialog,
|
|
81
|
-
QInputDialog,
|
|
82
|
-
QTableWidgetItem,
|
|
83
70
|
QFrame,
|
|
84
|
-
QDockWidget,
|
|
85
|
-
QApplication,
|
|
86
|
-
QAction,
|
|
87
|
-
QAbstractItemView,
|
|
88
|
-
QSplashScreen,
|
|
89
71
|
QHeaderView,
|
|
72
|
+
QInputDialog,
|
|
73
|
+
QLabel,
|
|
74
|
+
QListWidgetItem,
|
|
75
|
+
QMainWindow,
|
|
76
|
+
QMessageBox,
|
|
77
|
+
QSplashScreen,
|
|
78
|
+
QStyledItemDelegate,
|
|
79
|
+
QTableWidgetItem,
|
|
90
80
|
)
|
|
91
81
|
from PIL.ImageQt import Image
|
|
92
82
|
|
|
@@ -103,6 +93,7 @@ from . import plot_events
|
|
|
103
93
|
from . import plot_spectrogram_rt
|
|
104
94
|
from . import plot_waveform_rt
|
|
105
95
|
from . import plot_events_rt
|
|
96
|
+
from . import plugins
|
|
106
97
|
from . import project_functions
|
|
107
98
|
|
|
108
99
|
from . import select_observations
|
|
@@ -168,7 +159,8 @@ logging.info(f"BORIS version {__version__} release date: {__version_date__}")
|
|
|
168
159
|
logging.info(f"Operating system: {platform.uname().system} {platform.uname().release} {platform.uname().version}")
|
|
169
160
|
logging.info(f"CPU: {platform.uname().machine} {platform.uname().processor}")
|
|
170
161
|
logging.info(f"Python {platform.python_version()} ({'64-bit' if sys.maxsize > 2**32 else '32-bit'})")
|
|
171
|
-
logging.info(f"Qt {
|
|
162
|
+
logging.info(f"Qt {qVersion()} - PySide {PySide6.__version__}")
|
|
163
|
+
|
|
172
164
|
|
|
173
165
|
(r, memory) = util.mem_info()
|
|
174
166
|
if not r:
|
|
@@ -180,6 +172,18 @@ if not r:
|
|
|
180
172
|
)
|
|
181
173
|
|
|
182
174
|
|
|
175
|
+
def excepthook(exception_type, exception_value, traceback_object):
|
|
176
|
+
"""
|
|
177
|
+
global error management
|
|
178
|
+
"""
|
|
179
|
+
logging.debug("excepthook")
|
|
180
|
+
|
|
181
|
+
dialog.global_error_message(exception_type, exception_value, traceback_object)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
sys.excepthook = excepthook
|
|
185
|
+
|
|
186
|
+
|
|
183
187
|
class TableModel(QAbstractTableModel):
|
|
184
188
|
"""
|
|
185
189
|
class for populating table view with events
|
|
@@ -236,9 +240,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
236
240
|
|
|
237
241
|
state_behaviors_codes: tuple = tuple()
|
|
238
242
|
|
|
239
|
-
time_observer_signal =
|
|
240
|
-
mpv_eof_reached_signal =
|
|
241
|
-
video_click_signal =
|
|
243
|
+
time_observer_signal = Signal(float)
|
|
244
|
+
mpv_eof_reached_signal = Signal(float)
|
|
245
|
+
video_click_signal = Signal(int, str)
|
|
242
246
|
|
|
243
247
|
processes: list = [] # list of QProcess processes
|
|
244
248
|
overlays: dict = {} # dict for storing video overlays
|
|
@@ -362,8 +366,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
362
366
|
super(MainWindow, self).__init__(parent)
|
|
363
367
|
self.setupUi(self)
|
|
364
368
|
|
|
365
|
-
sys.excepthook = self.excepthook
|
|
366
|
-
|
|
367
369
|
self.ffmpeg_bin = ffmpeg_bin
|
|
368
370
|
# set icons
|
|
369
371
|
self.setWindowIcon(QIcon(":/small_logo"))
|
|
@@ -387,7 +389,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
387
389
|
self.tb_export.setMenu(self.menu)
|
|
388
390
|
"""
|
|
389
391
|
|
|
390
|
-
gui_utilities.set_icons(self)
|
|
392
|
+
gui_utilities.set_icons(self, theme_mode=self.theme_mode())
|
|
391
393
|
|
|
392
394
|
self.setWindowTitle(f"{cfg.programName} ({__version__})")
|
|
393
395
|
|
|
@@ -453,8 +455,11 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
453
455
|
self.statusbar.addPermanentWidget(self.lbSpeed)
|
|
454
456
|
"""
|
|
455
457
|
|
|
456
|
-
# set painter for
|
|
457
|
-
|
|
458
|
+
# set painter for tv_events to highlight current row
|
|
459
|
+
delegate = self.CustomItemDelegate()
|
|
460
|
+
self.tv_events.setItemDelegate(delegate)
|
|
461
|
+
|
|
462
|
+
# PySide6
|
|
458
463
|
self.tv_events.setItemDelegate(events_cursor.StyledItemDelegateTriangle(self.events_current_row))
|
|
459
464
|
|
|
460
465
|
connections.connections(self)
|
|
@@ -462,11 +467,21 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
462
467
|
config_file.read(self)
|
|
463
468
|
menu_options.update_menu(self)
|
|
464
469
|
|
|
465
|
-
|
|
470
|
+
plugins.load_plugins(self)
|
|
471
|
+
plugins.add_plugins_to_menu(self)
|
|
472
|
+
|
|
473
|
+
def theme_mode(self):
|
|
466
474
|
"""
|
|
467
|
-
|
|
475
|
+
return the theme mode (dark or light) of the OS
|
|
468
476
|
"""
|
|
469
|
-
|
|
477
|
+
palette = QApplication.instance().palette()
|
|
478
|
+
color = palette.window().color()
|
|
479
|
+
return "dark" if color.value() < 128 else "light" # Dark mode if the color value is less than 128
|
|
480
|
+
|
|
481
|
+
class CustomItemDelegate(QStyledItemDelegate):
|
|
482
|
+
def paint(self, painter, option, index):
|
|
483
|
+
# Custom drawing logic here (overriding paint)
|
|
484
|
+
super().paint(painter, option, index)
|
|
470
485
|
|
|
471
486
|
def block_dockwidgets(self):
|
|
472
487
|
"""
|
|
@@ -567,7 +582,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
567
582
|
if (
|
|
568
583
|
dialog.MessageDialog(
|
|
569
584
|
cfg.programName,
|
|
570
|
-
("Removing the path of external data files is irreversible.<br>
|
|
585
|
+
("Removing the path of external data files is irreversible.<br>Are you sure to continue?"),
|
|
571
586
|
[cfg.YES, cfg.NO],
|
|
572
587
|
)
|
|
573
588
|
== cfg.NO
|
|
@@ -1638,9 +1653,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1638
1653
|
|
|
1639
1654
|
self.save_project_activated()
|
|
1640
1655
|
else:
|
|
1641
|
-
logging.debug(
|
|
1642
|
-
(f"project not autosaved: " f"observation id: {self.observationId} " f"project file name: {self.projectFileName}")
|
|
1643
|
-
)
|
|
1656
|
+
logging.debug((f"project not autosaved: observation id: {self.observationId} project file name: {self.projectFileName}"))
|
|
1644
1657
|
|
|
1645
1658
|
def update_subject(self, subject: str) -> None:
|
|
1646
1659
|
"""
|
|
@@ -1677,11 +1690,19 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1677
1690
|
break
|
|
1678
1691
|
return currentMedia, round(frameCurrentMedia)
|
|
1679
1692
|
|
|
1693
|
+
'''
|
|
1680
1694
|
def extract_exif_DateTimeOriginal(self, file_path: str) -> int:
|
|
1681
1695
|
"""
|
|
1682
|
-
extract the
|
|
1696
|
+
extract the EXIF DateTimeOriginal tag
|
|
1683
1697
|
return epoch time
|
|
1684
1698
|
if the tag is not available return -1
|
|
1699
|
+
|
|
1700
|
+
Args:
|
|
1701
|
+
file_path (str): path of the media file
|
|
1702
|
+
|
|
1703
|
+
Returns:
|
|
1704
|
+
int: timestamp
|
|
1705
|
+
|
|
1685
1706
|
"""
|
|
1686
1707
|
try:
|
|
1687
1708
|
with open(file_path, "rb") as f_in:
|
|
@@ -1704,6 +1725,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1704
1725
|
|
|
1705
1726
|
except Exception:
|
|
1706
1727
|
return -1
|
|
1728
|
+
'''
|
|
1707
1729
|
|
|
1708
1730
|
def extract_frame(self, dw):
|
|
1709
1731
|
"""
|
|
@@ -1721,6 +1743,19 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1721
1743
|
)
|
|
1722
1744
|
|
|
1723
1745
|
if self.playerType == cfg.IMAGES:
|
|
1746
|
+
# print(f"{self.images_list=}")
|
|
1747
|
+
# print(f"{self.image_idx=}")
|
|
1748
|
+
|
|
1749
|
+
if self.image_idx >= len(self.images_list):
|
|
1750
|
+
QMessageBox.critical(
|
|
1751
|
+
None,
|
|
1752
|
+
cfg.programName,
|
|
1753
|
+
("The picture directory was changed since the creation of observation."),
|
|
1754
|
+
QMessageBox.Ok | QMessageBox.Default,
|
|
1755
|
+
QMessageBox.NoButton,
|
|
1756
|
+
)
|
|
1757
|
+
return
|
|
1758
|
+
|
|
1724
1759
|
pixmap = QPixmap(self.images_list[self.image_idx])
|
|
1725
1760
|
self.current_image_size = (pixmap.size().width(), pixmap.size().height())
|
|
1726
1761
|
|
|
@@ -1728,7 +1763,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1728
1763
|
|
|
1729
1764
|
# extract EXIF tag
|
|
1730
1765
|
if self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.USE_EXIF_DATE, False):
|
|
1731
|
-
date_time_original =
|
|
1766
|
+
date_time_original = util.extract_exif_DateTimeOriginal(self.images_list[self.image_idx])
|
|
1732
1767
|
if date_time_original != -1:
|
|
1733
1768
|
msg += f"<br>EXIF Date/Time Original: <b>{datetime.datetime.fromtimestamp(date_time_original):%Y-%m-%d %H:%M:%S}</b>"
|
|
1734
1769
|
else:
|
|
@@ -1853,11 +1888,11 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1853
1888
|
|
|
1854
1889
|
return painter
|
|
1855
1890
|
|
|
1856
|
-
output_dir = QFileDialog
|
|
1891
|
+
output_dir = QFileDialog.getExistingDirectory(
|
|
1857
1892
|
self,
|
|
1858
1893
|
"Select a directory to save the frames",
|
|
1859
1894
|
os.path.expanduser("~"),
|
|
1860
|
-
options=QFileDialog
|
|
1895
|
+
options=QFileDialog.ShowDirsOnly,
|
|
1861
1896
|
)
|
|
1862
1897
|
if not output_dir:
|
|
1863
1898
|
return
|
|
@@ -2628,7 +2663,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
2628
2663
|
start_time = f"{self.pj[cfg.OBSERVATIONS][obs_id][cfg.OBSERVATION_TIME_INTERVAL][0]:.3f}"
|
|
2629
2664
|
stop_time = f"{self.pj[cfg.OBSERVATIONS][obs_id][cfg.OBSERVATION_TIME_INTERVAL][1]:.3f}"
|
|
2630
2665
|
|
|
2631
|
-
self.lb_obs_time_interval.setText(("Observation time interval:
|
|
2666
|
+
self.lb_obs_time_interval.setText((f"Observation time interval: {start_time} - {stop_time}"))
|
|
2632
2667
|
else:
|
|
2633
2668
|
self.lb_obs_time_interval.clear()
|
|
2634
2669
|
else:
|
|
@@ -2720,11 +2755,11 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
2720
2755
|
plot_directory = ""
|
|
2721
2756
|
file_format = "png"
|
|
2722
2757
|
if len(selected_observations) > 1:
|
|
2723
|
-
plot_directory = QFileDialog
|
|
2758
|
+
plot_directory = QFileDialog.getExistingDirectory(
|
|
2724
2759
|
self,
|
|
2725
2760
|
"Choose a directory to save the plots",
|
|
2726
2761
|
os.path.expanduser("~"),
|
|
2727
|
-
options=QFileDialog
|
|
2762
|
+
options=QFileDialog.ShowDirsOnly,
|
|
2728
2763
|
)
|
|
2729
2764
|
|
|
2730
2765
|
if not plot_directory:
|
|
@@ -2864,11 +2899,11 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
2864
2899
|
plot_directory = ""
|
|
2865
2900
|
output_format = ""
|
|
2866
2901
|
if len(selected_observations) > 1:
|
|
2867
|
-
plot_directory = QFileDialog
|
|
2902
|
+
plot_directory = QFileDialog.getExistingDirectory(
|
|
2868
2903
|
self,
|
|
2869
2904
|
"Choose a directory to save the plots",
|
|
2870
2905
|
os.path.expanduser("~"),
|
|
2871
|
-
options=QFileDialog
|
|
2906
|
+
options=QFileDialog.ShowDirsOnly,
|
|
2872
2907
|
)
|
|
2873
2908
|
if not plot_directory:
|
|
2874
2909
|
return
|
|
@@ -2899,7 +2934,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
2899
2934
|
|
|
2900
2935
|
def load_project(self, project_path: str, project_changed, pj: dict):
|
|
2901
2936
|
"""
|
|
2902
|
-
load project from pj dict
|
|
2937
|
+
load project into widgets from pj dict
|
|
2903
2938
|
|
|
2904
2939
|
Args:
|
|
2905
2940
|
project_path (str): path of project file
|
|
@@ -2910,10 +2945,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
2910
2945
|
None
|
|
2911
2946
|
"""
|
|
2912
2947
|
self.pj = dict(pj)
|
|
2913
|
-
memProjectChanged = project_changed
|
|
2914
2948
|
self.clear_interface()
|
|
2915
|
-
self.projectChanged =
|
|
2916
|
-
self.projectChanged = memProjectChanged
|
|
2949
|
+
self.projectChanged = project_changed
|
|
2917
2950
|
self.load_behaviors_in_twEthogram([self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] for x in self.pj[cfg.ETHOGRAM]])
|
|
2918
2951
|
self.load_subjects_in_twSubjects([self.pj[cfg.SUBJECTS][x][cfg.SUBJECT_NAME] for x in self.pj[cfg.SUBJECTS]])
|
|
2919
2952
|
self.projectFileName = str(pl.Path(project_path).absolute())
|
|
@@ -2961,13 +2994,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
2961
2994
|
return
|
|
2962
2995
|
|
|
2963
2996
|
if action.text() == "Open project":
|
|
2964
|
-
|
|
2997
|
+
file_name, _ = QFileDialog.getOpenFileName(
|
|
2965
2998
|
self,
|
|
2966
2999
|
"Open project",
|
|
2967
3000
|
"",
|
|
2968
|
-
("Project files (*.boris *.boris.gz);;
|
|
3001
|
+
("Project files (*.boris *.boris.gz);;All files (*)"),
|
|
2969
3002
|
)
|
|
2970
|
-
file_name = fn[0] if type(fn) is tuple else fn
|
|
2971
3003
|
|
|
2972
3004
|
else: # recent project
|
|
2973
3005
|
file_name = action.text()
|
|
@@ -3070,13 +3102,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3070
3102
|
if response == cfg.CANCEL:
|
|
3071
3103
|
return
|
|
3072
3104
|
|
|
3073
|
-
|
|
3105
|
+
file_name, _ = QFileDialog.getOpenFileName(
|
|
3074
3106
|
self,
|
|
3075
3107
|
"Import project from Noldus The Observer",
|
|
3076
3108
|
"",
|
|
3077
3109
|
"Noldus Observer files (*.otx *.otb *.odx);;All files (*)",
|
|
3078
3110
|
)
|
|
3079
|
-
file_name = fn[0] if type(fn) is tuple else fn
|
|
3080
3111
|
|
|
3081
3112
|
if not file_name:
|
|
3082
3113
|
return
|
|
@@ -3145,6 +3176,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3145
3176
|
|
|
3146
3177
|
self.w_obs_info.setVisible(False)
|
|
3147
3178
|
|
|
3179
|
+
def not_editable_column_color(self):
|
|
3180
|
+
"""
|
|
3181
|
+
return a color for the not editable column
|
|
3182
|
+
"""
|
|
3183
|
+
window_color = QApplication.instance().palette().window().color()
|
|
3184
|
+
return QColor(window_color.red() - 5, window_color.green() - 5, window_color.blue() - 5)
|
|
3185
|
+
|
|
3148
3186
|
def edit_project(self, mode: str):
|
|
3149
3187
|
"""
|
|
3150
3188
|
project management
|
|
@@ -3264,13 +3302,16 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3264
3302
|
cfg.MODIFIERS,
|
|
3265
3303
|
):
|
|
3266
3304
|
item.setFlags(Qt.ItemIsEnabled)
|
|
3267
|
-
item.setBackground(QColor(230, 230, 230))
|
|
3305
|
+
# item.setBackground(QColor(230, 230, 230))
|
|
3306
|
+
item.setBackground(self.not_editable_column_color())
|
|
3307
|
+
|
|
3268
3308
|
if field == cfg.COLOR:
|
|
3269
3309
|
item.setFlags(Qt.ItemIsEnabled)
|
|
3270
3310
|
if QColor(newProjectWindow.pj[cfg.ETHOGRAM][i].get(field, "")).isValid():
|
|
3271
3311
|
item.setBackground(QColor(newProjectWindow.pj[cfg.ETHOGRAM][i][field]))
|
|
3272
3312
|
else:
|
|
3273
|
-
item.setBackground(QColor(230, 230, 230))
|
|
3313
|
+
# item.setBackground(QColor(230, 230, 230))
|
|
3314
|
+
item.setBackground(self.not_editable_column_color())
|
|
3274
3315
|
|
|
3275
3316
|
newProjectWindow.twBehaviors.setItem(
|
|
3276
3317
|
newProjectWindow.twBehaviors.rowCount() - 1,
|
|
@@ -3465,7 +3506,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3465
3506
|
self,
|
|
3466
3507
|
"Save project as",
|
|
3467
3508
|
os.path.dirname(self.projectFileName),
|
|
3468
|
-
("Project files (*.boris);;
|
|
3509
|
+
("Project files (*.boris);;Compressed project files (*.boris.gz);;All files (*)"),
|
|
3469
3510
|
)
|
|
3470
3511
|
|
|
3471
3512
|
if not project_new_file_name:
|
|
@@ -3529,7 +3570,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3529
3570
|
self,
|
|
3530
3571
|
"Save project",
|
|
3531
3572
|
txt,
|
|
3532
|
-
("Project files (*.boris);;
|
|
3573
|
+
("Project files (*.boris);;Compressed project files (*.boris.gz);;All files (*)"),
|
|
3533
3574
|
)
|
|
3534
3575
|
|
|
3535
3576
|
if not self.projectFileName:
|
|
@@ -3670,7 +3711,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3670
3711
|
|
|
3671
3712
|
self.pb_live_obs.setText("Stop live observation")
|
|
3672
3713
|
|
|
3673
|
-
self.liveStartTime =
|
|
3714
|
+
self.liveStartTime = QElapsedTimer()
|
|
3674
3715
|
# set to now
|
|
3675
3716
|
self.liveStartTime.start()
|
|
3676
3717
|
# start timer
|
|
@@ -3745,11 +3786,11 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3745
3786
|
)
|
|
3746
3787
|
return
|
|
3747
3788
|
|
|
3748
|
-
export_dir = QFileDialog
|
|
3789
|
+
export_dir = QFileDialog.getExistingDirectory(
|
|
3749
3790
|
self,
|
|
3750
3791
|
"Choose a directory to save subtitles",
|
|
3751
3792
|
os.path.expanduser("~"),
|
|
3752
|
-
options=QFileDialog
|
|
3793
|
+
options=QFileDialog.ShowDirsOnly,
|
|
3753
3794
|
)
|
|
3754
3795
|
if not export_dir:
|
|
3755
3796
|
return
|
|
@@ -3786,7 +3827,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3786
3827
|
if self.geometric_measurements_mode:
|
|
3787
3828
|
geometric_measurement.redraw_measurements(self)
|
|
3788
3829
|
|
|
3789
|
-
self.actionPlay.setIcon(QIcon(":/
|
|
3830
|
+
self.actionPlay.setIcon(QIcon(f":/play_{self.theme_mode()}"))
|
|
3790
3831
|
|
|
3791
3832
|
def previous_frame(self) -> None:
|
|
3792
3833
|
"""
|
|
@@ -3810,7 +3851,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3810
3851
|
if self.geometric_measurements_mode:
|
|
3811
3852
|
geometric_measurement.redraw_measurements(self)
|
|
3812
3853
|
|
|
3813
|
-
self.actionPlay.setIcon(QIcon(":/
|
|
3854
|
+
self.actionPlay.setIcon(QIcon(f":/play_{self.theme_mode()}"))
|
|
3814
3855
|
|
|
3815
3856
|
def run_event_outside(self):
|
|
3816
3857
|
"""
|
|
@@ -3923,7 +3964,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3923
3964
|
QMessageBox.critical(
|
|
3924
3965
|
self,
|
|
3925
3966
|
cfg.programName,
|
|
3926
|
-
("The current observation is opened in VIEW mode.\
|
|
3967
|
+
("The current observation is opened in VIEW mode.\nIt is not allowed to log events in this mode."),
|
|
3927
3968
|
)
|
|
3928
3969
|
return
|
|
3929
3970
|
|
|
@@ -3949,7 +3990,18 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3949
3990
|
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.MEDIA:
|
|
3950
3991
|
event[cfg.FRAME_INDEX] = self.get_frame_index()
|
|
3951
3992
|
|
|
3952
|
-
|
|
3993
|
+
time_, cumulative_time = self.get_obs_time()
|
|
3994
|
+
|
|
3995
|
+
"""
|
|
3996
|
+
print(time_)
|
|
3997
|
+
print(cumulative_time)
|
|
3998
|
+
"""
|
|
3999
|
+
|
|
4000
|
+
if self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.MEDIA_CREATION_DATE_AS_OFFSET, False):
|
|
4001
|
+
write_event.write_event(self, event, time_)
|
|
4002
|
+
else:
|
|
4003
|
+
write_event.write_event(self, event, cumulative_time)
|
|
4004
|
+
# write_event.write_event(self, event, self.getLaps())
|
|
3953
4005
|
|
|
3954
4006
|
def get_frame_index(self, player_idx: int = 0) -> Union[int, str]:
|
|
3955
4007
|
"""
|
|
@@ -4001,7 +4053,14 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4001
4053
|
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.MEDIA:
|
|
4002
4054
|
event[cfg.FRAME_INDEX] = self.get_frame_index()
|
|
4003
4055
|
|
|
4004
|
-
|
|
4056
|
+
time_, cumulative_time = self.get_obs_time()
|
|
4057
|
+
|
|
4058
|
+
if self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.MEDIA_CREATION_DATE_AS_OFFSET, False):
|
|
4059
|
+
write_event.write_event(self, event, time_)
|
|
4060
|
+
else:
|
|
4061
|
+
write_event.write_event(self, event, cumulative_time)
|
|
4062
|
+
|
|
4063
|
+
# write_event.write_event(self.event, self.getLaps())
|
|
4005
4064
|
|
|
4006
4065
|
def keypress_signal_from_behaviors_coding_map(self, event):
|
|
4007
4066
|
"""
|
|
@@ -4088,7 +4147,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4088
4147
|
|
|
4089
4148
|
# print(f"{self.events_current_row=}")
|
|
4090
4149
|
|
|
4091
|
-
# self.twEvents.setItemDelegate(events_cursor.StyledItemDelegateTriangle(self.events_current_row))
|
|
4092
4150
|
self.tv_events.setItemDelegate(events_cursor.StyledItemDelegateTriangle(self.events_current_row))
|
|
4093
4151
|
|
|
4094
4152
|
# print(f"{self.twEvents.item(self.events_current_row, 0)=}")
|
|
@@ -4380,7 +4438,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4380
4438
|
# if many media files
|
|
4381
4439
|
if self.dw_player[0].player.playlist_count > 1:
|
|
4382
4440
|
msg += (
|
|
4383
|
-
f"<br>Total: <b>{util.convertTime(self.timeFormat,cumulative_time_pos)} / "
|
|
4441
|
+
f"<br>Total: <b>{util.convertTime(self.timeFormat, cumulative_time_pos)} / "
|
|
4384
4442
|
f"{util.convertTime(self.timeFormat, all_media_duration)}</b>"
|
|
4385
4443
|
)
|
|
4386
4444
|
|
|
@@ -4391,7 +4449,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4391
4449
|
for data_timer in self.ext_data_timer_list:
|
|
4392
4450
|
data_timer.stop()
|
|
4393
4451
|
|
|
4394
|
-
self.actionPlay.setIcon(QIcon(":/
|
|
4452
|
+
self.actionPlay.setIcon(QIcon(f":/play_{self.theme_mode()}"))
|
|
4395
4453
|
|
|
4396
4454
|
if msg:
|
|
4397
4455
|
self.lb_current_media_time.setText(msg)
|
|
@@ -4657,9 +4715,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4657
4715
|
if "Live observation finished" in self.pb_live_obs.text():
|
|
4658
4716
|
return dec("NaN")
|
|
4659
4717
|
if self.liveObservationStarted:
|
|
4660
|
-
|
|
4661
|
-
now.start() # current time
|
|
4662
|
-
memLaps = dec(str(round(self.liveStartTime.msecsTo(now) / 1000, 3)))
|
|
4718
|
+
memLaps = dec(str(round(self.liveStartTime.elapsed() / 1000, 3)))
|
|
4663
4719
|
return memLaps
|
|
4664
4720
|
else:
|
|
4665
4721
|
return dec("0")
|
|
@@ -4672,9 +4728,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4672
4728
|
time_ = dec("NaN")
|
|
4673
4729
|
if (
|
|
4674
4730
|
self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.USE_EXIF_DATE, False)
|
|
4675
|
-
and
|
|
4731
|
+
and util.extract_exif_DateTimeOriginal(self.images_list[self.image_idx]) != -1
|
|
4676
4732
|
):
|
|
4677
|
-
time_ =
|
|
4733
|
+
time_ = util.extract_exif_DateTimeOriginal(self.images_list[self.image_idx]) - self.image_time_ref
|
|
4678
4734
|
|
|
4679
4735
|
elif self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.TIME_LAPSE, 0):
|
|
4680
4736
|
time_ = (self.image_idx + 1) * self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.TIME_LAPSE, 0)
|
|
@@ -4682,7 +4738,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4682
4738
|
return dec(time_).quantize(dec("0.001"), rounding=ROUND_DOWN)
|
|
4683
4739
|
|
|
4684
4740
|
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.MEDIA:
|
|
4685
|
-
if self.playerType in
|
|
4741
|
+
if self.playerType in (cfg.VIEWER_LIVE, cfg.VIEWER_MEDIA):
|
|
4686
4742
|
return dec(0)
|
|
4687
4743
|
|
|
4688
4744
|
if self.playerType == cfg.MEDIA:
|
|
@@ -4693,6 +4749,60 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4693
4749
|
|
|
4694
4750
|
return dec(str(round(mem_laps / 1000, 3)))
|
|
4695
4751
|
|
|
4752
|
+
def get_obs_time(self, n_player: int = 0) -> Tuple[dec, dec | None]:
|
|
4753
|
+
"""
|
|
4754
|
+
returns time in current media and cumulative time from begining of observation
|
|
4755
|
+
do not add time offset
|
|
4756
|
+
|
|
4757
|
+
Args:
|
|
4758
|
+
n_player (int): player
|
|
4759
|
+
Returns:
|
|
4760
|
+
decimal: cumulative time in seconds
|
|
4761
|
+
|
|
4762
|
+
"""
|
|
4763
|
+
|
|
4764
|
+
if not self.observationId:
|
|
4765
|
+
return dec("0")
|
|
4766
|
+
|
|
4767
|
+
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.LIVE:
|
|
4768
|
+
if "Live observation finished" in self.pb_live_obs.text():
|
|
4769
|
+
return dec("NaN"), None
|
|
4770
|
+
if self.liveObservationStarted:
|
|
4771
|
+
return dec(str(round(self.liveStartTime.elapsed() / 1000, 3))), None
|
|
4772
|
+
else:
|
|
4773
|
+
return dec("0"), None
|
|
4774
|
+
|
|
4775
|
+
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.IMAGES:
|
|
4776
|
+
if self.playerType == cfg.VIEWER_IMAGES:
|
|
4777
|
+
return dec("NaN"), None
|
|
4778
|
+
|
|
4779
|
+
if self.playerType == cfg.IMAGES:
|
|
4780
|
+
time_ = dec("NaN")
|
|
4781
|
+
if (
|
|
4782
|
+
self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.USE_EXIF_DATE, False)
|
|
4783
|
+
and util.extract_exif_DateTimeOriginal(self.images_list[self.image_idx]) != -1
|
|
4784
|
+
):
|
|
4785
|
+
time_ = util.extract_exif_DateTimeOriginal(self.images_list[self.image_idx]) - self.image_time_ref
|
|
4786
|
+
|
|
4787
|
+
elif self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.TIME_LAPSE, 0):
|
|
4788
|
+
time_ = (self.image_idx + 1) * self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.TIME_LAPSE, 0)
|
|
4789
|
+
|
|
4790
|
+
return dec(time_).quantize(dec("0.001"), rounding=ROUND_DOWN), None
|
|
4791
|
+
|
|
4792
|
+
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.MEDIA:
|
|
4793
|
+
if self.playerType in (cfg.VIEWER_LIVE, cfg.VIEWER_MEDIA):
|
|
4794
|
+
return dec(0), dec(0)
|
|
4795
|
+
|
|
4796
|
+
if self.playerType == cfg.MEDIA:
|
|
4797
|
+
# cumulative time
|
|
4798
|
+
mem_laps = sum(self.dw_player[n_player].media_durations[0 : self.dw_player[n_player].player.playlist_pos]) + (
|
|
4799
|
+
0 if self.dw_player[n_player].player.time_pos is None else self.dw_player[n_player].player.time_pos * 1000
|
|
4800
|
+
)
|
|
4801
|
+
|
|
4802
|
+
return dec(0) if self.dw_player[n_player].player.time_pos is None else dec(
|
|
4803
|
+
str(round(self.dw_player[n_player].player.time_pos, 3))
|
|
4804
|
+
), dec(str(round(mem_laps / 1000, 3)))
|
|
4805
|
+
|
|
4696
4806
|
def full_event(self, behavior_idx: str) -> dict:
|
|
4697
4807
|
"""
|
|
4698
4808
|
get event as dict
|
|
@@ -4746,7 +4856,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4746
4856
|
sound_type (str): type of sound
|
|
4747
4857
|
"""
|
|
4748
4858
|
|
|
4749
|
-
|
|
4859
|
+
QSoundEffect.play(f":/{sound_type}")
|
|
4750
4860
|
|
|
4751
4861
|
def is_playing(self) -> bool:
|
|
4752
4862
|
"""
|
|
@@ -4789,7 +4899,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4789
4899
|
def keyPressEvent(self, event) -> None:
|
|
4790
4900
|
"""
|
|
4791
4901
|
http://qt-project.org/doc/qt-5.0/qtcore/qt.html#Key-enum
|
|
4792
|
-
https://github.com/pyqt/python-qt5/blob/master/
|
|
4902
|
+
https://github.com/pyqt/python-qt5/blob/master/PySide6/qml/builtins.qmltypes
|
|
4793
4903
|
|
|
4794
4904
|
ESC: 16777216
|
|
4795
4905
|
"""
|
|
@@ -4835,7 +4945,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4835
4945
|
QMessageBox.critical(
|
|
4836
4946
|
self,
|
|
4837
4947
|
cfg.programName,
|
|
4838
|
-
("The current observation is opened in VIEW mode.\
|
|
4948
|
+
("The current observation is opened in VIEW mode.\nIt is not allowed to log events in this mode."),
|
|
4839
4949
|
)
|
|
4840
4950
|
return
|
|
4841
4951
|
|
|
@@ -5000,7 +5110,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5000
5110
|
if self.playerType == cfg.MEDIA:
|
|
5001
5111
|
event[cfg.FRAME_INDEX] = self.get_frame_index()
|
|
5002
5112
|
|
|
5003
|
-
|
|
5113
|
+
if self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.MEDIA_CREATION_DATE_AS_OFFSET, False):
|
|
5114
|
+
time_, _ = self.get_obs_time()
|
|
5115
|
+
write_event.write_event(self, event, time_)
|
|
5116
|
+
else:
|
|
5117
|
+
write_event.write_event(self, event, memLaps)
|
|
5118
|
+
|
|
5004
5119
|
return
|
|
5005
5120
|
|
|
5006
5121
|
# count key occurence in subjects
|
|
@@ -5087,7 +5202,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5087
5202
|
if self.playerType == cfg.MEDIA:
|
|
5088
5203
|
event[cfg.FRAME_INDEX] = self.get_frame_index()
|
|
5089
5204
|
|
|
5090
|
-
|
|
5205
|
+
if self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.MEDIA_CREATION_DATE_AS_OFFSET, False):
|
|
5206
|
+
time_, _ = self.get_obs_time()
|
|
5207
|
+
write_event.write_event(self, event, time_)
|
|
5208
|
+
else:
|
|
5209
|
+
write_event.write_event(self, event, memLaps)
|
|
5210
|
+
|
|
5211
|
+
# write_event.write_event(self, event, memLaps)
|
|
5091
5212
|
|
|
5092
5213
|
elif subject_idx is not None:
|
|
5093
5214
|
self.update_subject(self.pj[cfg.SUBJECTS][subject_idx][cfg.SUBJECT_NAME])
|
|
@@ -5473,7 +5594,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5473
5594
|
for data_timer in self.ext_data_timer_list:
|
|
5474
5595
|
data_timer.start()
|
|
5475
5596
|
|
|
5476
|
-
self.actionPlay.setIcon(QIcon(":/
|
|
5597
|
+
self.actionPlay.setIcon(QIcon(f":/pause_{self.theme_mode()}"))
|
|
5477
5598
|
self.actionPlay.setText("Pause")
|
|
5478
5599
|
|
|
5479
5600
|
return True
|
|
@@ -5507,7 +5628,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5507
5628
|
for idx in self.plot_data:
|
|
5508
5629
|
self.timer_plot_data_out(self.plot_data[idx])
|
|
5509
5630
|
|
|
5510
|
-
self.actionPlay.setIcon(QIcon(":/
|
|
5631
|
+
self.actionPlay.setIcon(QIcon(f":/play_{self.theme_mode()}"))
|
|
5511
5632
|
self.actionPlay.setText("Play")
|
|
5512
5633
|
|
|
5513
5634
|
def play_activated(self):
|
|
@@ -5599,6 +5720,131 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5599
5720
|
self.image_idx = 0
|
|
5600
5721
|
self.extract_frame(self.dw_player[0])
|
|
5601
5722
|
|
|
5723
|
+
def obs_param(self):
|
|
5724
|
+
_, selected_observations = select_observations.select_observations2(self, mode=cfg.MULTIPLE, windows_title="")
|
|
5725
|
+
|
|
5726
|
+
if not selected_observations:
|
|
5727
|
+
return [], {}
|
|
5728
|
+
|
|
5729
|
+
# check if coded behaviors are defined in ethogram
|
|
5730
|
+
if project_functions.check_coded_behaviors_in_obs_list(self.pj, selected_observations):
|
|
5731
|
+
return [], {}
|
|
5732
|
+
|
|
5733
|
+
# check if state events are paired
|
|
5734
|
+
not_ok, selected_observations = project_functions.check_state_events(self.pj, selected_observations)
|
|
5735
|
+
if not_ok or not selected_observations:
|
|
5736
|
+
return [], {}
|
|
5737
|
+
|
|
5738
|
+
max_media_duration_all_obs, total_media_duration_all_obs = observation_operations.media_duration(
|
|
5739
|
+
self.pj[cfg.OBSERVATIONS], selected_observations
|
|
5740
|
+
)
|
|
5741
|
+
|
|
5742
|
+
logging.debug(
|
|
5743
|
+
f"max_media_duration_all_obs: {max_media_duration_all_obs}, total_media_duration_all_obs={total_media_duration_all_obs}"
|
|
5744
|
+
)
|
|
5745
|
+
|
|
5746
|
+
start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
5747
|
+
|
|
5748
|
+
parameters: dict = select_subj_behav.choose_obs_subj_behav_category(
|
|
5749
|
+
self,
|
|
5750
|
+
selected_observations,
|
|
5751
|
+
start_coding=start_coding,
|
|
5752
|
+
end_coding=end_coding,
|
|
5753
|
+
maxTime=max_media_duration_all_obs,
|
|
5754
|
+
by_category=False,
|
|
5755
|
+
n_observations=len(selected_observations),
|
|
5756
|
+
show_exclude_non_coded_modifiers=True,
|
|
5757
|
+
)
|
|
5758
|
+
if parameters == {}:
|
|
5759
|
+
return [], {}
|
|
5760
|
+
|
|
5761
|
+
if not parameters[cfg.SELECTED_SUBJECTS] or not parameters[cfg.SELECTED_BEHAVIORS]:
|
|
5762
|
+
QMessageBox.warning(None, cfg.programName, "Select subject(s) and behavior(s) to analyze")
|
|
5763
|
+
return [], {}
|
|
5764
|
+
|
|
5765
|
+
logging.debug(f"{parameters=}")
|
|
5766
|
+
return selected_observations, parameters
|
|
5767
|
+
|
|
5768
|
+
def run_plugin(self):
|
|
5769
|
+
"""
|
|
5770
|
+
run plugin
|
|
5771
|
+
"""
|
|
5772
|
+
if not self.project:
|
|
5773
|
+
QMessageBox.warning(
|
|
5774
|
+
self,
|
|
5775
|
+
cfg.programName,
|
|
5776
|
+
"No observations found. Open a project first",
|
|
5777
|
+
QMessageBox.Ok | QMessageBox.Default,
|
|
5778
|
+
QMessageBox.NoButton,
|
|
5779
|
+
)
|
|
5780
|
+
return
|
|
5781
|
+
|
|
5782
|
+
import importlib
|
|
5783
|
+
|
|
5784
|
+
print(f"{self.config_param.get(cfg.ANALYSIS_PLUGINS, {})=}")
|
|
5785
|
+
|
|
5786
|
+
plugin_name = self.sender().text()
|
|
5787
|
+
if plugin_name not in self.config_param.get(cfg.ANALYSIS_PLUGINS, {}):
|
|
5788
|
+
QMessageBox.critical(self, cfg.programName, f"Plugin '{plugin_name}' not found")
|
|
5789
|
+
return
|
|
5790
|
+
|
|
5791
|
+
plugin_path = self.config_param.get(cfg.ANALYSIS_PLUGINS, {})[plugin_name]
|
|
5792
|
+
print(f"{plugin_path=}")
|
|
5793
|
+
if not pl.Path(plugin_path).is_file():
|
|
5794
|
+
QMessageBox.critical(self, cfg.programName, f"The plugin {plugin_path} was not found.")
|
|
5795
|
+
return
|
|
5796
|
+
|
|
5797
|
+
logging.debug(f"run plugin from {plugin_path}")
|
|
5798
|
+
|
|
5799
|
+
module_name = pl.Path(plugin_path).stem
|
|
5800
|
+
|
|
5801
|
+
spec = importlib.util.spec_from_file_location(module_name, plugin_path)
|
|
5802
|
+
plugin_module = importlib.util.module_from_spec(spec)
|
|
5803
|
+
print(f"{plugin_module=}")
|
|
5804
|
+
spec.loader.exec_module(plugin_module)
|
|
5805
|
+
|
|
5806
|
+
print(
|
|
5807
|
+
f"{plugin_module.__plugin_name__} loaded v.{getattr(plugin_module, '__version__')} v. {getattr(plugin_module, '__version_date__')}"
|
|
5808
|
+
)
|
|
5809
|
+
|
|
5810
|
+
"""
|
|
5811
|
+
plugins_dir = pl.Path(__file__).parent / "analysis_plugins"
|
|
5812
|
+
|
|
5813
|
+
print(f"{plugins_dir=}")
|
|
5814
|
+
|
|
5815
|
+
module_path = f"{plugins_dir.name}.{plugin}"
|
|
5816
|
+
|
|
5817
|
+
print(f"{module_path=}")
|
|
5818
|
+
|
|
5819
|
+
try:
|
|
5820
|
+
plugin_module = importlib.import_module(module_path)
|
|
5821
|
+
print(f"{plugin} loaded v.{getattr(plugin_module, '__version__')} v. {getattr(plugin_module, '__version_date__')}")
|
|
5822
|
+
except Exception:
|
|
5823
|
+
QMessageBox.critical(self, cfg.programName, f"Error loding the plugin {plugin}")
|
|
5824
|
+
return
|
|
5825
|
+
|
|
5826
|
+
print(f"{plugin_module=}")
|
|
5827
|
+
"""
|
|
5828
|
+
|
|
5829
|
+
selected_observations, parameters = self.obs_param()
|
|
5830
|
+
if not selected_observations:
|
|
5831
|
+
return
|
|
5832
|
+
|
|
5833
|
+
df = project_functions.project2dataframe(self.pj, selected_observations)
|
|
5834
|
+
|
|
5835
|
+
print(f"{df.head()=}")
|
|
5836
|
+
|
|
5837
|
+
df_results = plugin_module.main(df, observations_list=selected_observations, parameters=parameters)
|
|
5838
|
+
|
|
5839
|
+
from . import view_df
|
|
5840
|
+
|
|
5841
|
+
self.view_dataframe = view_df.View_df(
|
|
5842
|
+
self.sender().text(), f"{plugin_module.__version__} ({plugin_module.__version_date__})", df_results
|
|
5843
|
+
)
|
|
5844
|
+
self.view_dataframe.show()
|
|
5845
|
+
|
|
5846
|
+
# print(f"{results=}")
|
|
5847
|
+
|
|
5602
5848
|
|
|
5603
5849
|
def main():
|
|
5604
5850
|
# QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
|
|
@@ -5637,8 +5883,8 @@ def main():
|
|
|
5637
5883
|
|
|
5638
5884
|
window = MainWindow(ffmpeg_bin)
|
|
5639
5885
|
|
|
5640
|
-
if window.config_param.get(cfg.DARK_MODE, cfg.DARK_MODE_DEFAULT_VALUE):
|
|
5641
|
-
|
|
5886
|
+
# if window.config_param.get(cfg.DARK_MODE, cfg.DARK_MODE_DEFAULT_VALUE):
|
|
5887
|
+
# app.setStyleSheet(qdarkstyle.load_stylesheet(qt_api="PySide6"))
|
|
5642
5888
|
|
|
5643
5889
|
# open project/start observation on command line
|
|
5644
5890
|
|
|
@@ -5686,7 +5932,7 @@ def main():
|
|
|
5686
5932
|
if not options.nosplashscreen and (sys.platform != "darwin"):
|
|
5687
5933
|
splash.finish(window)
|
|
5688
5934
|
|
|
5689
|
-
return_code = app.
|
|
5935
|
+
return_code = app.exec()
|
|
5690
5936
|
|
|
5691
5937
|
del window
|
|
5692
5938
|
|