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/core.py
CHANGED
|
@@ -31,41 +31,32 @@ os.environ["PATH"] = str(Path(__file__).parent / "misc") + os.pathsep + os.envir
|
|
|
31
31
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".")))
|
|
32
32
|
|
|
33
33
|
import datetime
|
|
34
|
+
import gzip
|
|
34
35
|
import json
|
|
36
|
+
import locale
|
|
35
37
|
import logging
|
|
38
|
+
import math
|
|
36
39
|
import platform
|
|
37
40
|
import re
|
|
38
|
-
import
|
|
39
|
-
import PIL.ImageEnhance
|
|
40
|
-
from PIL.ImageQt import Image
|
|
41
|
+
import shutil
|
|
41
42
|
import subprocess
|
|
42
|
-
import locale
|
|
43
43
|
import tempfile
|
|
44
44
|
import time
|
|
45
45
|
import urllib.request
|
|
46
|
-
|
|
47
|
-
from decimal import Decimal as dec
|
|
48
|
-
from decimal import ROUND_DOWN
|
|
49
|
-
import gzip
|
|
46
|
+
import zipfile
|
|
50
47
|
from collections import deque
|
|
48
|
+
from decimal import ROUND_DOWN
|
|
49
|
+
from decimal import Decimal as dec
|
|
50
|
+
|
|
51
51
|
import matplotlib
|
|
52
|
-
import
|
|
53
|
-
import
|
|
52
|
+
import PIL.Image
|
|
53
|
+
import PIL.ImageEnhance
|
|
54
|
+
from PIL.ImageQt import Image
|
|
54
55
|
|
|
55
56
|
matplotlib.use("QtAgg")
|
|
56
57
|
|
|
57
|
-
from PySide6.QtCore import
|
|
58
|
-
|
|
59
|
-
QPoint,
|
|
60
|
-
Signal,
|
|
61
|
-
QEvent,
|
|
62
|
-
QDateTime,
|
|
63
|
-
QUrl,
|
|
64
|
-
QAbstractTableModel,
|
|
65
|
-
QElapsedTimer,
|
|
66
|
-
QSettings,
|
|
67
|
-
)
|
|
68
|
-
from PySide6.QtGui import QIcon, QPixmap, QFont, QKeyEvent, QDesktopServices, QColor, QPainter, QPolygon, QAction
|
|
58
|
+
from PySide6.QtCore import QAbstractTableModel, QDateTime, QElapsedTimer, QEvent, QPoint, QSettings, Qt, QUrl, Signal
|
|
59
|
+
from PySide6.QtGui import QAction, QColor, QDesktopServices, QFont, QIcon, QKeyEvent, QPainter, QPixmap, QPolygon
|
|
69
60
|
from PySide6.QtMultimedia import QSoundEffect
|
|
70
61
|
from PySide6.QtWidgets import (
|
|
71
62
|
QAbstractItemView,
|
|
@@ -84,7 +75,6 @@ from PySide6.QtWidgets import (
|
|
|
84
75
|
QTableWidgetItem,
|
|
85
76
|
)
|
|
86
77
|
|
|
87
|
-
|
|
88
78
|
from . import cmd_arguments
|
|
89
79
|
|
|
90
80
|
# parse command line arguments
|
|
@@ -104,39 +94,39 @@ else:
|
|
|
104
94
|
level=logging.INFO,
|
|
105
95
|
)
|
|
106
96
|
|
|
107
|
-
from . import
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
97
|
+
from . import (
|
|
98
|
+
advanced_event_filtering,
|
|
99
|
+
config_file,
|
|
100
|
+
core_qrc,
|
|
101
|
+
dialog,
|
|
102
|
+
event_operations,
|
|
103
|
+
events_cursor,
|
|
104
|
+
geometric_measurement,
|
|
105
|
+
gui_utilities,
|
|
106
|
+
modifier_coding_map_creator,
|
|
107
|
+
modifiers_coding_map,
|
|
108
|
+
observation_operations,
|
|
109
|
+
otx_parser,
|
|
110
|
+
param_panel,
|
|
111
|
+
plot_events,
|
|
112
|
+
plot_events_rt,
|
|
113
|
+
plot_spectrogram_rt,
|
|
114
|
+
plot_waveform_rt,
|
|
115
|
+
plugins,
|
|
116
|
+
project,
|
|
117
|
+
project_functions,
|
|
118
|
+
select_observations,
|
|
119
|
+
select_subj_behav,
|
|
120
|
+
subjects_pad,
|
|
121
|
+
version,
|
|
122
|
+
video_operations,
|
|
123
|
+
write_event,
|
|
124
|
+
)
|
|
130
125
|
from . import config as cfg
|
|
131
|
-
from . import video_operations
|
|
132
|
-
from . import project
|
|
133
|
-
from . import menu_options as menu_options
|
|
134
126
|
from . import connections as connections
|
|
135
|
-
from . import
|
|
136
|
-
from . import
|
|
137
|
-
from . import
|
|
138
|
-
from . import write_event
|
|
139
|
-
|
|
127
|
+
from . import menu_options as menu_options
|
|
128
|
+
from . import utilities as util
|
|
129
|
+
from .core_ui import Ui_MainWindow
|
|
140
130
|
|
|
141
131
|
logging.debug("test")
|
|
142
132
|
|
|
@@ -187,8 +177,8 @@ class TableModel(QAbstractTableModel):
|
|
|
187
177
|
self.observation_type = observation_type
|
|
188
178
|
|
|
189
179
|
def headerData(self, section: int, orientation: Qt.Orientation, role: int):
|
|
190
|
-
if role == Qt.DisplayRole:
|
|
191
|
-
if orientation == Qt.Horizontal:
|
|
180
|
+
if role == Qt.ItemDataRole.DisplayRole:
|
|
181
|
+
if orientation == Qt.Orientation.Horizontal:
|
|
192
182
|
return self.header[section]
|
|
193
183
|
else:
|
|
194
184
|
return str(section + 1)
|
|
@@ -199,8 +189,8 @@ class TableModel(QAbstractTableModel):
|
|
|
199
189
|
def columnCount(self, parent=None):
|
|
200
190
|
return len(self._data[0]) if self.rowCount() else 0
|
|
201
191
|
|
|
202
|
-
def data(self, index, role=Qt.DisplayRole):
|
|
203
|
-
if role == Qt.DisplayRole:
|
|
192
|
+
def data(self, index, role=Qt.ItemDataRole.DisplayRole):
|
|
193
|
+
if role == Qt.ItemDataRole.DisplayRole:
|
|
204
194
|
row = index.row()
|
|
205
195
|
if 0 <= row < self.rowCount():
|
|
206
196
|
column = index.column()
|
|
@@ -242,7 +232,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
242
232
|
current_player: int = 0 # id of the selected (left click) video player
|
|
243
233
|
|
|
244
234
|
mem_media_name: str = "" # record current media name. Use to check if media changed
|
|
245
|
-
mem_playlist_index:
|
|
235
|
+
mem_playlist_index: int | None = None
|
|
246
236
|
saved_state = None
|
|
247
237
|
user_move_slider: bool = False
|
|
248
238
|
observationId: str = "" # current observation id
|
|
@@ -358,7 +348,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
358
348
|
super(MainWindow, self).__init__(parent)
|
|
359
349
|
self.setupUi(self)
|
|
360
350
|
|
|
361
|
-
self.pb_live_obs.setFocusPolicy(Qt.NoFocus)
|
|
351
|
+
self.pb_live_obs.setFocusPolicy(Qt.FocusPolicy.NoFocus)
|
|
362
352
|
|
|
363
353
|
self.ffmpeg_bin = ffmpeg_bin
|
|
364
354
|
# set icons
|
|
@@ -389,7 +379,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
389
379
|
|
|
390
380
|
self.lbLogoBoris.setPixmap(QPixmap(":/logo"))
|
|
391
381
|
self.lbLogoBoris.setScaledContents(False)
|
|
392
|
-
self.lbLogoBoris.setAlignment(Qt.AlignCenter)
|
|
382
|
+
self.lbLogoBoris.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
393
383
|
|
|
394
384
|
self.toolBar.setEnabled(True)
|
|
395
385
|
|
|
@@ -423,13 +413,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
423
413
|
|
|
424
414
|
# observation time interval
|
|
425
415
|
self.lb_obs_time_interval = QLabel()
|
|
426
|
-
self.lb_obs_time_interval.setFrameStyle(QFrame.StyledPanel)
|
|
416
|
+
self.lb_obs_time_interval.setFrameStyle(QFrame.Shape.StyledPanel)
|
|
427
417
|
self.lb_obs_time_interval.setMinimumWidth(160)
|
|
428
418
|
self.statusbar.addPermanentWidget(self.lb_obs_time_interval)
|
|
429
419
|
|
|
430
420
|
# time offset
|
|
431
421
|
self.lbTimeOffset = QLabel()
|
|
432
|
-
self.lbTimeOffset.setFrameStyle(QFrame.StyledPanel)
|
|
422
|
+
self.lbTimeOffset.setFrameStyle(QFrame.Shape.StyledPanel)
|
|
433
423
|
self.lbTimeOffset.setMinimumWidth(160)
|
|
434
424
|
self.statusbar.addPermanentWidget(self.lbTimeOffset)
|
|
435
425
|
|
|
@@ -460,9 +450,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
460
450
|
for w in (self.dwEvents, self.dwEthogram, self.dwSubjects):
|
|
461
451
|
if self.action_block_dockwidgets.isChecked():
|
|
462
452
|
w.setFloating(False)
|
|
463
|
-
w.setFeatures(QDockWidget.NoDockWidgetFeatures)
|
|
453
|
+
w.setFeatures(QDockWidget.DockWidgetFeature.NoDockWidgetFeatures)
|
|
464
454
|
else:
|
|
465
|
-
w.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable)
|
|
455
|
+
w.setFeatures(QDockWidget.DockWidgetFeature.DockWidgetMovable | QDockWidget.DockWidgetFeature.DockWidgetFloatable)
|
|
466
456
|
|
|
467
457
|
def advanced_event_filtering(self):
|
|
468
458
|
"""
|
|
@@ -534,7 +524,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
534
524
|
"Removing the path of media files and image directories from the project file is irreversible.<br>"
|
|
535
525
|
"Are you sure to continue?"
|
|
536
526
|
),
|
|
537
|
-
|
|
527
|
+
(cfg.YES, cfg.NO),
|
|
538
528
|
)
|
|
539
529
|
== cfg.NO
|
|
540
530
|
):
|
|
@@ -552,13 +542,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
552
542
|
dialog.MessageDialog(
|
|
553
543
|
cfg.programName,
|
|
554
544
|
("Removing the path of external data files is irreversible.<br>Are you sure to continue?"),
|
|
555
|
-
|
|
545
|
+
(cfg.YES, cfg.NO),
|
|
556
546
|
)
|
|
557
547
|
== cfg.NO
|
|
558
548
|
):
|
|
559
549
|
return
|
|
560
550
|
|
|
561
|
-
if project_functions.remove_data_files_path(self.pj
|
|
551
|
+
if project_functions.remove_data_files_path(self.pj):
|
|
562
552
|
self.project_changed()
|
|
563
553
|
|
|
564
554
|
def set_media_files_path_relative_to_project_dir(self):
|
|
@@ -570,7 +560,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
570
560
|
dialog.MessageDialog(
|
|
571
561
|
cfg.programName,
|
|
572
562
|
("Are you sure to continue?"),
|
|
573
|
-
|
|
563
|
+
(cfg.YES, cfg.NO),
|
|
574
564
|
)
|
|
575
565
|
== cfg.NO
|
|
576
566
|
):
|
|
@@ -587,7 +577,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
587
577
|
dialog.MessageDialog(
|
|
588
578
|
cfg.programName,
|
|
589
579
|
("Are you sure to continue?"),
|
|
590
|
-
|
|
580
|
+
(cfg.YES, cfg.NO),
|
|
591
581
|
)
|
|
592
582
|
== cfg.NO
|
|
593
583
|
):
|
|
@@ -640,7 +630,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
640
630
|
"""
|
|
641
631
|
handle click received from coding pad
|
|
642
632
|
"""
|
|
643
|
-
q = QKeyEvent(QEvent.KeyPress, Qt.Key_Enter, Qt.NoModifier, text=behaviorCode)
|
|
633
|
+
q = QKeyEvent(QEvent.Type.KeyPress, Qt.Key.Key_Enter, Qt.KeyboardModifier.NoModifier, text=behaviorCode)
|
|
634
|
+
|
|
644
635
|
self.keyPressEvent(q)
|
|
645
636
|
|
|
646
637
|
def close_signal_from_coding_pad(self, geometry, preferences):
|
|
@@ -654,7 +645,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
654
645
|
"""
|
|
655
646
|
handle click received from subjects pad
|
|
656
647
|
"""
|
|
657
|
-
q = QKeyEvent(QEvent.KeyPress, Qt.Key_Enter, Qt.NoModifier, text="#subject#" + subject)
|
|
648
|
+
q = QKeyEvent(QEvent.Type.KeyPress, Qt.Key.Key_Enter, Qt.KeyboardModifier.NoModifier, text="#subject#" + subject)
|
|
658
649
|
self.keyPressEvent(q)
|
|
659
650
|
|
|
660
651
|
def signal_from_subjects_pad(self, event):
|
|
@@ -706,9 +697,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
706
697
|
QMessageBox.warning(self, cfg.programName, "No subjects to show")
|
|
707
698
|
return
|
|
708
699
|
self.subjects_pad = subjects_pad.SubjectsPad(self.pj, filtered_subjects)
|
|
709
|
-
self.subjects_pad.setWindowFlags(Qt.WindowStaysOnTopHint)
|
|
700
|
+
self.subjects_pad.setWindowFlags(Qt.WindowType.WindowStaysOnTopHint)
|
|
710
701
|
self.subjects_pad.sendEventSignal.connect(self.signal_from_subjects_pad)
|
|
711
|
-
self.subjects_pad.
|
|
702
|
+
self.subjects_pad.click_signal.connect(self.click_signal_from_subjects_pad)
|
|
712
703
|
self.subjects_pad.close_signal.connect(self.close_signal_from_subjects_pad)
|
|
713
704
|
self.subjects_pad.show()
|
|
714
705
|
|
|
@@ -739,7 +730,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
739
730
|
text: str = "Behaviors to show in ethogram list",
|
|
740
731
|
table: str = cfg.ETHOGRAM,
|
|
741
732
|
behavior_type: list = cfg.STATE_EVENT_TYPES,
|
|
742
|
-
) ->
|
|
733
|
+
) -> tuple[bool, list]:
|
|
743
734
|
"""
|
|
744
735
|
allow user to:
|
|
745
736
|
filter behaviors in ethogram widget
|
|
@@ -927,7 +918,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
927
918
|
w = dialog.Info_widget()
|
|
928
919
|
w.lwi.setVisible(False)
|
|
929
920
|
w.resize(350, 100)
|
|
930
|
-
w.setWindowFlags(Qt.WindowStaysOnTopHint)
|
|
921
|
+
w.setWindowFlags(Qt.WindowType.WindowStaysOnTopHint)
|
|
931
922
|
w.setWindowTitle(cfg.programName)
|
|
932
923
|
w.label.setText("Extracting WAV from media files...")
|
|
933
924
|
|
|
@@ -1009,7 +1000,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1009
1000
|
f"You choose to visualize the {plot_type} during this observation.<br>"
|
|
1010
1001
|
f"{plot_type} generation can take some time for long media, be patient"
|
|
1011
1002
|
),
|
|
1012
|
-
|
|
1003
|
+
(cfg.YES, cfg.NO),
|
|
1013
1004
|
)
|
|
1014
1005
|
== cfg.NO
|
|
1015
1006
|
):
|
|
@@ -1027,8 +1018,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1027
1018
|
|
|
1028
1019
|
self.spectro = plot_spectrogram_rt.Plot_spectrogram_RT()
|
|
1029
1020
|
|
|
1030
|
-
self.spectro.setWindowFlags(Qt.WindowStaysOnTopHint)
|
|
1031
|
-
self.spectro.setWindowFlags(self.spectro.windowFlags() & ~Qt.WindowMinimizeButtonHint)
|
|
1021
|
+
self.spectro.setWindowFlags(Qt.WindowType.WindowStaysOnTopHint)
|
|
1022
|
+
self.spectro.setWindowFlags(self.spectro.windowFlags() & ~Qt.WindowType.WindowMinimizeButtonHint)
|
|
1032
1023
|
|
|
1033
1024
|
self.spectro.interval = self.spectrogram_time_interval
|
|
1034
1025
|
self.spectro.cursor_color = cfg.REALTIME_PLOT_CURSOR_COLOR
|
|
@@ -1048,8 +1039,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1048
1039
|
self,
|
|
1049
1040
|
cfg.programName,
|
|
1050
1041
|
f"Error in spectrogram generation: {r['error']}",
|
|
1051
|
-
QMessageBox.Ok
|
|
1052
|
-
QMessageBox.NoButton,
|
|
1042
|
+
QMessageBox.StandardButton.Ok,
|
|
1043
|
+
QMessageBox.StandardButton.NoButton,
|
|
1053
1044
|
)
|
|
1054
1045
|
del self.spectro
|
|
1055
1046
|
return
|
|
@@ -1100,7 +1091,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1100
1091
|
"You choose to visualize the waveform during this observation.<br>"
|
|
1101
1092
|
"The waveform generation can take some time for long media, be patient"
|
|
1102
1093
|
),
|
|
1103
|
-
|
|
1094
|
+
(cfg.YES, cfg.NO),
|
|
1104
1095
|
)
|
|
1105
1096
|
== cfg.NO
|
|
1106
1097
|
):
|
|
@@ -1118,7 +1109,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1118
1109
|
|
|
1119
1110
|
self.waveform = plot_waveform_rt.Plot_waveform_RT()
|
|
1120
1111
|
|
|
1121
|
-
self.waveform.setWindowFlags(Qt.WindowStaysOnTopHint)
|
|
1112
|
+
self.waveform.setWindowFlags(Qt.WindowType.WindowStaysOnTopHint)
|
|
1122
1113
|
self.waveform.setWindowFlags(self.waveform.windowFlags() & ~Qt.WindowMinimizeButtonHint)
|
|
1123
1114
|
|
|
1124
1115
|
self.waveform.interval = self.spectrogram_time_interval
|
|
@@ -1131,8 +1122,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1131
1122
|
self,
|
|
1132
1123
|
cfg.programName,
|
|
1133
1124
|
f"Error in waveform generation: {r['error']}",
|
|
1134
|
-
QMessageBox.Ok
|
|
1135
|
-
QMessageBox.NoButton,
|
|
1125
|
+
QMessageBox.StandardButton.Ok,
|
|
1126
|
+
QMessageBox.StandardButton.NoButton,
|
|
1136
1127
|
)
|
|
1137
1128
|
del self.waveform
|
|
1138
1129
|
return
|
|
@@ -1155,7 +1146,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1155
1146
|
|
|
1156
1147
|
self.plot_events = plot_events_rt.Plot_events_RT()
|
|
1157
1148
|
|
|
1158
|
-
self.plot_events.setWindowFlags(Qt.WindowStaysOnTopHint)
|
|
1149
|
+
self.plot_events.setWindowFlags(Qt.WindowType.WindowStaysOnTopHint)
|
|
1159
1150
|
self.plot_events.setWindowFlags(self.plot_events.windowFlags() & ~Qt.WindowMinimizeButtonHint)
|
|
1160
1151
|
|
|
1161
1152
|
self.plot_events.groupby = "behaviors"
|
|
@@ -1294,8 +1285,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1294
1285
|
self,
|
|
1295
1286
|
cfg.programName,
|
|
1296
1287
|
"No project found",
|
|
1297
|
-
QMessageBox.Ok
|
|
1298
|
-
QMessageBox.NoButton,
|
|
1288
|
+
QMessageBox.StandardButton.Ok,
|
|
1289
|
+
QMessageBox.StandardButton.NoButton,
|
|
1299
1290
|
)
|
|
1300
1291
|
return
|
|
1301
1292
|
|
|
@@ -1310,7 +1301,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1310
1301
|
f"with the same name (<b>{behav_coding_map['name']}</b>).<br>"
|
|
1311
1302
|
"What do you want to do?"
|
|
1312
1303
|
),
|
|
1313
|
-
|
|
1304
|
+
("Replace the coding map", cfg.CANCEL),
|
|
1314
1305
|
)
|
|
1315
1306
|
if response == cfg.CANCEL:
|
|
1316
1307
|
return
|
|
@@ -1510,15 +1501,17 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1510
1501
|
logging.debug("previous media file")
|
|
1511
1502
|
|
|
1512
1503
|
if self.playerType == cfg.MEDIA:
|
|
1513
|
-
if len(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE][cfg.PLAYER1]) == 1:
|
|
1514
|
-
|
|
1504
|
+
# if len(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE][cfg.PLAYER1]) == 1:
|
|
1505
|
+
# self.seek_mediaplayer(dec(0))
|
|
1506
|
+
# return
|
|
1515
1507
|
|
|
1516
1508
|
# check if media not first media
|
|
1517
1509
|
if self.dw_player[0].player.playlist_pos > 0:
|
|
1518
1510
|
self.dw_player[0].player.playlist_prev()
|
|
1519
1511
|
|
|
1520
1512
|
elif self.dw_player[0].player.playlist_count == 1:
|
|
1521
|
-
self.
|
|
1513
|
+
self.seek_mediaplayer(dec(0))
|
|
1514
|
+
# self.statusbar.showMessage("There is only one media file", 5000)
|
|
1522
1515
|
|
|
1523
1516
|
if hasattr(self, "spectro"):
|
|
1524
1517
|
self.spectro.memChunk = -1
|
|
@@ -1665,7 +1658,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1665
1658
|
time.sleep(0.3) # required for correct frame number
|
|
1666
1659
|
|
|
1667
1660
|
dw.frame_viewer.setPixmap(
|
|
1668
|
-
util.pil2pixmap(dw.player.screenshot_raw()).scaled(
|
|
1661
|
+
util.pil2pixmap(dw.player.screenshot_raw()).scaled(
|
|
1662
|
+
dw.frame_viewer.size(), Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation
|
|
1663
|
+
)
|
|
1669
1664
|
)
|
|
1670
1665
|
|
|
1671
1666
|
if self.playerType == cfg.IMAGES:
|
|
@@ -1674,8 +1669,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1674
1669
|
None,
|
|
1675
1670
|
cfg.programName,
|
|
1676
1671
|
("The picture directory has changed since the creation of observation."),
|
|
1677
|
-
QMessageBox.Ok
|
|
1678
|
-
QMessageBox.NoButton,
|
|
1672
|
+
QMessageBox.StandardButton.Ok,
|
|
1673
|
+
QMessageBox.StandardButton.NoButton,
|
|
1679
1674
|
)
|
|
1680
1675
|
return
|
|
1681
1676
|
|
|
@@ -1813,7 +1808,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1813
1808
|
self,
|
|
1814
1809
|
"Select a directory to save the frames",
|
|
1815
1810
|
os.path.expanduser("~"),
|
|
1816
|
-
options=QFileDialog.ShowDirsOnly,
|
|
1811
|
+
options=QFileDialog.Option.ShowDirsOnly,
|
|
1817
1812
|
)
|
|
1818
1813
|
if not output_dir:
|
|
1819
1814
|
return
|
|
@@ -2195,8 +2190,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
2195
2190
|
None,
|
|
2196
2191
|
cfg.programName,
|
|
2197
2192
|
("This function is not yet implemented"),
|
|
2198
|
-
QMessageBox.Ok
|
|
2199
|
-
QMessageBox.NoButton,
|
|
2193
|
+
QMessageBox.StandardButton.Ok,
|
|
2194
|
+
QMessageBox.StandardButton.NoButton,
|
|
2200
2195
|
)
|
|
2201
2196
|
|
|
2202
2197
|
return
|
|
@@ -2287,7 +2282,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
2287
2282
|
self.tv_events.setModel(model)
|
|
2288
2283
|
|
|
2289
2284
|
# column width
|
|
2290
|
-
self.tv_events.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
|
|
2285
|
+
self.tv_events.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Interactive)
|
|
2291
2286
|
|
|
2292
2287
|
def load_tw_events(self, obs_id) -> None:
|
|
2293
2288
|
"""
|
|
@@ -2551,8 +2546,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
2551
2546
|
None,
|
|
2552
2547
|
cfg.programName,
|
|
2553
2548
|
("This function is not available for observations with events that do not have timestamp"),
|
|
2554
|
-
QMessageBox.Ok
|
|
2555
|
-
QMessageBox.NoButton,
|
|
2549
|
+
QMessageBox.StandardButton.Ok,
|
|
2550
|
+
QMessageBox.StandardButton.NoButton,
|
|
2556
2551
|
)
|
|
2557
2552
|
return
|
|
2558
2553
|
|
|
@@ -2564,7 +2559,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
2564
2559
|
self,
|
|
2565
2560
|
"Choose a directory to save the plots",
|
|
2566
2561
|
os.path.expanduser("~"),
|
|
2567
|
-
options=QFileDialog.ShowDirsOnly,
|
|
2562
|
+
options=QFileDialog.Option.ShowDirsOnly,
|
|
2568
2563
|
)
|
|
2569
2564
|
|
|
2570
2565
|
if not plot_directory:
|
|
@@ -2667,8 +2662,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
2667
2662
|
None,
|
|
2668
2663
|
cfg.programName,
|
|
2669
2664
|
("The duration of one or more observation is not available"),
|
|
2670
|
-
QMessageBox.Ok
|
|
2671
|
-
QMessageBox.NoButton,
|
|
2665
|
+
QMessageBox.StandardButton.Ok,
|
|
2666
|
+
QMessageBox.StandardButton.NoButton,
|
|
2672
2667
|
)
|
|
2673
2668
|
return
|
|
2674
2669
|
|
|
@@ -2680,8 +2675,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
2680
2675
|
None,
|
|
2681
2676
|
cfg.programName,
|
|
2682
2677
|
("This function is not available for observations with events that do not have timestamp"),
|
|
2683
|
-
QMessageBox.Ok
|
|
2684
|
-
QMessageBox.NoButton,
|
|
2678
|
+
QMessageBox.StandardButton.Ok,
|
|
2679
|
+
QMessageBox.StandardButton.NoButton,
|
|
2685
2680
|
)
|
|
2686
2681
|
return
|
|
2687
2682
|
|
|
@@ -2716,7 +2711,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
2716
2711
|
self,
|
|
2717
2712
|
"Choose a directory to save the plots",
|
|
2718
2713
|
os.path.expanduser("~"),
|
|
2719
|
-
options=QFileDialog.ShowDirsOnly,
|
|
2714
|
+
options=QFileDialog.Option.ShowDirsOnly,
|
|
2720
2715
|
)
|
|
2721
2716
|
if not plot_directory:
|
|
2722
2717
|
return
|
|
@@ -2784,7 +2779,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
2784
2779
|
dialog.MessageDialog(
|
|
2785
2780
|
cfg.programName,
|
|
2786
2781
|
"There is a current observation. What do you want to do?",
|
|
2787
|
-
|
|
2782
|
+
("Close observation", "Continue observation"),
|
|
2788
2783
|
)
|
|
2789
2784
|
== "Close observation"
|
|
2790
2785
|
):
|
|
@@ -2796,7 +2791,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
2796
2791
|
response = dialog.MessageDialog(
|
|
2797
2792
|
cfg.programName,
|
|
2798
2793
|
"What to do about the current unsaved project?",
|
|
2799
|
-
|
|
2794
|
+
(cfg.SAVE, cfg.DISCARD, cfg.CANCEL),
|
|
2800
2795
|
)
|
|
2801
2796
|
|
|
2802
2797
|
if response == cfg.SAVE:
|
|
@@ -2856,7 +2851,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
2856
2851
|
"In this project all the behavior and subject keys are upper case.<br>"
|
|
2857
2852
|
"Do you want to convert them in lower case?"
|
|
2858
2853
|
),
|
|
2859
|
-
|
|
2854
|
+
(cfg.YES, cfg.NO),
|
|
2860
2855
|
)
|
|
2861
2856
|
== cfg.YES
|
|
2862
2857
|
):
|
|
@@ -2908,7 +2903,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
2908
2903
|
dialog.MessageDialog(
|
|
2909
2904
|
cfg.programName,
|
|
2910
2905
|
"There is a current observation. What do you want to do?",
|
|
2911
|
-
|
|
2906
|
+
("Close observation", "Continue observation"),
|
|
2912
2907
|
)
|
|
2913
2908
|
== "Close observation"
|
|
2914
2909
|
):
|
|
@@ -2920,7 +2915,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
2920
2915
|
response = dialog.MessageDialog(
|
|
2921
2916
|
cfg.programName,
|
|
2922
2917
|
"What to do about the current unsaved project?",
|
|
2923
|
-
|
|
2918
|
+
(cfg.SAVE, cfg.DISCARD, cfg.CANCEL),
|
|
2924
2919
|
)
|
|
2925
2920
|
|
|
2926
2921
|
if response == cfg.SAVE:
|
|
@@ -2971,7 +2966,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
2971
2966
|
response = dialog.MessageDialog(
|
|
2972
2967
|
cfg.programName,
|
|
2973
2968
|
"There is a current observation. What do you want to do?",
|
|
2974
|
-
|
|
2969
|
+
("Close observation", "Continue observation"),
|
|
2975
2970
|
)
|
|
2976
2971
|
if response == "Close observation":
|
|
2977
2972
|
observation_operations.close_observation(self)
|
|
@@ -2982,7 +2977,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
2982
2977
|
response = dialog.MessageDialog(
|
|
2983
2978
|
cfg.programName,
|
|
2984
2979
|
"What to do about the current unsaved project?",
|
|
2985
|
-
|
|
2980
|
+
(cfg.SAVE, cfg.DISCARD, cfg.CANCEL),
|
|
2986
2981
|
)
|
|
2987
2982
|
if response == cfg.SAVE:
|
|
2988
2983
|
if self.save_project_activated() == "not saved":
|
|
@@ -3026,7 +3021,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3026
3021
|
response = dialog.MessageDialog(
|
|
3027
3022
|
cfg.programName,
|
|
3028
3023
|
"The current observation will be closed. Do you want to continue?",
|
|
3029
|
-
|
|
3024
|
+
(cfg.YES, cfg.NO),
|
|
3030
3025
|
)
|
|
3031
3026
|
if response == cfg.NO:
|
|
3032
3027
|
self.show_data_files()
|
|
@@ -3039,7 +3034,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3039
3034
|
response = dialog.MessageDialog(
|
|
3040
3035
|
cfg.programName,
|
|
3041
3036
|
"What to do with the current unsaved project?",
|
|
3042
|
-
|
|
3037
|
+
(cfg.SAVE, cfg.DISCARD, cfg.CANCEL),
|
|
3043
3038
|
)
|
|
3044
3039
|
|
|
3045
3040
|
if response == cfg.SAVE:
|
|
@@ -3240,7 +3235,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3240
3235
|
|
|
3241
3236
|
del newProjectWindow
|
|
3242
3237
|
|
|
3243
|
-
def save_project_json(self, project_file_name: str) -> int:
|
|
3238
|
+
def save_project_json(self, project_file_name: str) -> int | None:
|
|
3244
3239
|
"""
|
|
3245
3240
|
save project to JSON file
|
|
3246
3241
|
convert Decimal type in float
|
|
@@ -3306,21 +3301,21 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3306
3301
|
None,
|
|
3307
3302
|
cfg.programName,
|
|
3308
3303
|
"Permission denied to save the project file. Try another directory",
|
|
3309
|
-
QMessageBox.Ok
|
|
3310
|
-
QMessageBox.NoButton,
|
|
3304
|
+
QMessageBox.StandardButton.Ok,
|
|
3305
|
+
QMessageBox.StandardButton.NoButton,
|
|
3311
3306
|
)
|
|
3312
3307
|
self.save_project_json_started = False
|
|
3313
3308
|
return 1
|
|
3314
3309
|
|
|
3315
3310
|
except OSError:
|
|
3316
3311
|
_, value, _ = sys.exc_info()
|
|
3317
|
-
QMessageBox.critical(None, cfg.programName, f"Error saving the project file: {value}", QMessageBox.Ok)
|
|
3312
|
+
QMessageBox.critical(None, cfg.programName, f"Error saving the project file: {value}", QMessageBox.StandardButton.Ok)
|
|
3318
3313
|
self.save_project_json_started = False
|
|
3319
3314
|
return 4
|
|
3320
3315
|
|
|
3321
3316
|
except Exception:
|
|
3322
3317
|
_, value, _ = sys.exc_info()
|
|
3323
|
-
QMessageBox.critical(None, cfg.programName, f"Error saving the project file: {value}", QMessageBox.Ok)
|
|
3318
|
+
QMessageBox.critical(None, cfg.programName, f"Error saving the project file: {value}", QMessageBox.StandardButton.Ok)
|
|
3324
3319
|
self.save_project_json_started = False
|
|
3325
3320
|
return 2
|
|
3326
3321
|
|
|
@@ -3351,7 +3346,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3351
3346
|
dialog.MessageDialog(
|
|
3352
3347
|
cfg.programName,
|
|
3353
3348
|
f"The file {project_new_file_name} already exists.",
|
|
3354
|
-
|
|
3349
|
+
(cfg.CANCEL, cfg.OVERWRITE),
|
|
3355
3350
|
)
|
|
3356
3351
|
== cfg.CANCEL
|
|
3357
3352
|
):
|
|
@@ -3367,7 +3362,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3367
3362
|
dialog.MessageDialog(
|
|
3368
3363
|
cfg.programName,
|
|
3369
3364
|
f"The file {project_new_file_name} already exists.",
|
|
3370
|
-
|
|
3365
|
+
(cfg.CANCEL, cfg.OVERWRITE),
|
|
3371
3366
|
)
|
|
3372
3367
|
== cfg.CANCEL
|
|
3373
3368
|
):
|
|
@@ -3447,7 +3442,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3447
3442
|
dialog.MessageDialog(
|
|
3448
3443
|
cfg.programName,
|
|
3449
3444
|
f"The file {self.projectFileName} already exists.",
|
|
3450
|
-
|
|
3445
|
+
(cfg.CANCEL, cfg.OVERWRITE),
|
|
3451
3446
|
)
|
|
3452
3447
|
== cfg.CANCEL
|
|
3453
3448
|
):
|
|
@@ -3466,7 +3461,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3466
3461
|
dialog.MessageDialog(
|
|
3467
3462
|
cfg.programName,
|
|
3468
3463
|
f"The file {self.projectFileName} already exists.",
|
|
3469
|
-
|
|
3464
|
+
(cfg.CANCEL, cfg.OVERWRITE),
|
|
3470
3465
|
)
|
|
3471
3466
|
== cfg.CANCEL
|
|
3472
3467
|
):
|
|
@@ -3629,8 +3624,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3629
3624
|
None,
|
|
3630
3625
|
cfg.programName,
|
|
3631
3626
|
("This function is not available for observations with events that do not have timestamp"),
|
|
3632
|
-
QMessageBox.Ok
|
|
3633
|
-
QMessageBox.NoButton,
|
|
3627
|
+
QMessageBox.StandardButton.Ok,
|
|
3628
|
+
QMessageBox.StandardButton.NoButton,
|
|
3634
3629
|
)
|
|
3635
3630
|
return
|
|
3636
3631
|
|
|
@@ -3663,7 +3658,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3663
3658
|
self,
|
|
3664
3659
|
"Choose a directory to save subtitles",
|
|
3665
3660
|
os.path.expanduser("~"),
|
|
3666
|
-
options=QFileDialog.ShowDirsOnly,
|
|
3661
|
+
options=QFileDialog.Option.ShowDirsOnly,
|
|
3667
3662
|
)
|
|
3668
3663
|
if not export_dir:
|
|
3669
3664
|
return
|
|
@@ -3674,22 +3669,26 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3674
3669
|
None,
|
|
3675
3670
|
cfg.programName,
|
|
3676
3671
|
f"Error creating subtitles: {msg}",
|
|
3677
|
-
QMessageBox.Ok
|
|
3678
|
-
QMessageBox.NoButton,
|
|
3672
|
+
QMessageBox.StandardButton.Ok,
|
|
3673
|
+
QMessageBox.StandardButton.NoButton,
|
|
3679
3674
|
)
|
|
3680
3675
|
|
|
3681
3676
|
def next_frame(self) -> None:
|
|
3682
3677
|
"""
|
|
3683
3678
|
show next frame
|
|
3684
3679
|
"""
|
|
3680
|
+
# frame_step_size = self.config_param.get(cfg.FRAME_STEP_SIZE, cfg.FRAME_STEP_SIZE_DEFAULT_VALUE)
|
|
3681
|
+
|
|
3685
3682
|
if self.playerType == cfg.IMAGES:
|
|
3686
3683
|
if self.image_idx < len(self.images_list) - 1:
|
|
3687
|
-
self.image_idx += 1
|
|
3684
|
+
self.image_idx += 1 # frame_step_size
|
|
3688
3685
|
self.extract_frame(self.dw_player[0])
|
|
3689
3686
|
|
|
3690
3687
|
if self.playerType == cfg.MEDIA:
|
|
3691
3688
|
for dw in self.dw_player:
|
|
3689
|
+
# for _ in range(frame_step_size):
|
|
3692
3690
|
dw.player.frame_step()
|
|
3691
|
+
# time.sleep(0.5)
|
|
3693
3692
|
|
|
3694
3693
|
if self.geometric_measurements_mode:
|
|
3695
3694
|
self.extract_frame(dw)
|
|
@@ -3866,7 +3865,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3866
3865
|
write_event.write_event(self, event, cumulative_time)
|
|
3867
3866
|
# write_event.write_event(self, event, self.getLaps())
|
|
3868
3867
|
|
|
3869
|
-
def get_frame_index(self, player_idx: int = 0) ->
|
|
3868
|
+
def get_frame_index(self, player_idx: int = 0) -> int | str:
|
|
3870
3869
|
"""
|
|
3871
3870
|
returns frame index for player player_idx
|
|
3872
3871
|
"""
|
|
@@ -3903,7 +3902,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3903
3902
|
msg_box = QMessageBox(
|
|
3904
3903
|
QMessageBox.Critical, cfg.programName, f"The code <b>{code}</b> of behavior coding map does not exist in ethogram."
|
|
3905
3904
|
)
|
|
3906
|
-
msg_box.setWindowFlags(msg_box.windowFlags() | Qt.WindowStaysOnTopHint)
|
|
3905
|
+
msg_box.setWindowFlags(msg_box.windowFlags() | Qt.WindowType.WindowStaysOnTopHint)
|
|
3907
3906
|
msg_box.exec()
|
|
3908
3907
|
return
|
|
3909
3908
|
|
|
@@ -3947,7 +3946,14 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3947
3946
|
slider_position = self.video_slider.value() / (cfg.SLIDER_MAXIMUM - 1)
|
|
3948
3947
|
if self.dw_player[0].player.duration is None:
|
|
3949
3948
|
return
|
|
3950
|
-
|
|
3949
|
+
print(f"{slider_position=}")
|
|
3950
|
+
|
|
3951
|
+
d = self.dw_player[0].player.duration
|
|
3952
|
+
print(f"{d=}")
|
|
3953
|
+
if d is None:
|
|
3954
|
+
return
|
|
3955
|
+
video_position = slider_position * d
|
|
3956
|
+
# video_position = slider_position * self.dw_player[0].player.duration
|
|
3951
3957
|
# self.dw_player[0].player.command("seek", str(video_position), "absolute")
|
|
3952
3958
|
self.dw_player[0].player.seek(video_position, "absolute")
|
|
3953
3959
|
|
|
@@ -4058,17 +4064,18 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4058
4064
|
|
|
4059
4065
|
Args:
|
|
4060
4066
|
n_player (int): player
|
|
4061
|
-
new_time (
|
|
4067
|
+
new_time (float): new time in ms
|
|
4062
4068
|
"""
|
|
4069
|
+
new_time_dec = dec(new_time)
|
|
4063
4070
|
|
|
4064
4071
|
if self.dw_player[n_player].player.playlist_count == 1:
|
|
4065
4072
|
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)]:
|
|
4066
4073
|
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)] > 0:
|
|
4067
|
-
if
|
|
4074
|
+
if new_time_dec < self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)]:
|
|
4068
4075
|
# hide video and mute audio if time < offset
|
|
4069
4076
|
self.media_player_enabled(n_player, enable=False)
|
|
4070
4077
|
else:
|
|
4071
|
-
if
|
|
4078
|
+
if new_time_dec - dec(
|
|
4072
4079
|
self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)]
|
|
4073
4080
|
) > sum(self.dw_player[n_player].media_durations):
|
|
4074
4081
|
# hide video and mute audio if required time > video time + offset
|
|
@@ -4077,26 +4084,27 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4077
4084
|
# show video and enable audio
|
|
4078
4085
|
self.media_player_enabled(n_player, enable=True)
|
|
4079
4086
|
self.seek_mediaplayer(
|
|
4080
|
-
|
|
4087
|
+
new_time_dec
|
|
4081
4088
|
- dec(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)]),
|
|
4082
4089
|
player=n_player,
|
|
4083
4090
|
)
|
|
4084
4091
|
|
|
4085
4092
|
elif self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)] < 0:
|
|
4086
|
-
if
|
|
4087
|
-
self.
|
|
4088
|
-
):
|
|
4093
|
+
if new_time_dec - dec(
|
|
4094
|
+
self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)]
|
|
4095
|
+
) > sum(self.dw_player[n_player].media_durations):
|
|
4089
4096
|
# hide video and mute audio if required time > video time + offset
|
|
4090
4097
|
self.media_player_enabled(n_player, enable=False)
|
|
4091
4098
|
else:
|
|
4092
4099
|
self.media_player_enabled(n_player, enable=True)
|
|
4093
4100
|
self.seek_mediaplayer(
|
|
4094
|
-
|
|
4101
|
+
new_time_dec
|
|
4102
|
+
- dec(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)]),
|
|
4095
4103
|
player=n_player,
|
|
4096
4104
|
)
|
|
4097
4105
|
|
|
4098
4106
|
else: # no offset
|
|
4099
|
-
self.seek_mediaplayer(
|
|
4107
|
+
self.seek_mediaplayer(new_time_dec, player=n_player)
|
|
4100
4108
|
|
|
4101
4109
|
elif self.dw_player[n_player].player.playlist_count > 1:
|
|
4102
4110
|
# check if new time is before the end of last video
|
|
@@ -4142,7 +4150,22 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4142
4150
|
self.dw_player[n_player].player.playlist_pos = self.dw_player[n_player].player.playlist_count - 1
|
|
4143
4151
|
self.seek_mediaplayer(self.dw_player[n_player].media_durations[-1], player=n_player)
|
|
4144
4152
|
|
|
4145
|
-
def
|
|
4153
|
+
def activate_main_window(self):
|
|
4154
|
+
"""
|
|
4155
|
+
activate main window in order to capture keyboard events
|
|
4156
|
+
called only in IPC mode
|
|
4157
|
+
"""
|
|
4158
|
+
# check if eof reached
|
|
4159
|
+
# print(f"{self.dw_player[0].player.playlist_pos=}")
|
|
4160
|
+
# print(f"{self.dw_player[0].player.playlist_count=}")
|
|
4161
|
+
if self.dw_player[0].player.eof_reached and self.dw_player[0].player.core_idle:
|
|
4162
|
+
logging.debug("end of playlist reached")
|
|
4163
|
+
if self.dw_player[0].player.playlist_pos is not None and self.dw_player[0].player.playlist_count is not None:
|
|
4164
|
+
if self.dw_player[0].player.playlist_pos == self.dw_player[0].player.playlist_count - 1:
|
|
4165
|
+
self.pause_video()
|
|
4166
|
+
self.activateWindow()
|
|
4167
|
+
|
|
4168
|
+
def mpv_timer_out(self, value: float | None = None, scroll_slider=True):
|
|
4146
4169
|
"""
|
|
4147
4170
|
print the media current position and total length for MPV player
|
|
4148
4171
|
scroll video slider to video position
|
|
@@ -4152,13 +4175,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4152
4175
|
if not self.observationId:
|
|
4153
4176
|
return
|
|
4154
4177
|
|
|
4155
|
-
print("mpv timer out")
|
|
4156
|
-
|
|
4157
4178
|
cumulative_time_pos = self.getLaps()
|
|
4158
|
-
print(f"{cumulative_time_pos=}")
|
|
4159
4179
|
# get frame index
|
|
4160
4180
|
frame_idx = self.get_frame_index()
|
|
4161
|
-
print(f"{frame_idx=}")
|
|
4162
4181
|
# frame_idx = 0
|
|
4163
4182
|
|
|
4164
4183
|
if value is None: # ipc mpv
|
|
@@ -4205,8 +4224,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4205
4224
|
ct = self.getLaps(n_player=n_player)
|
|
4206
4225
|
|
|
4207
4226
|
# sync players 2..8 if time diff >= 1 s
|
|
4208
|
-
if
|
|
4209
|
-
|
|
4227
|
+
if not math.isnan(ct) and not math.isnan(ct0):
|
|
4228
|
+
if (
|
|
4229
|
+
abs(ct0 - (ct + dec(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)])))
|
|
4230
|
+
>= 1
|
|
4231
|
+
):
|
|
4232
|
+
self.sync_time(n_player, float(ct0)) # self.seek_mediaplayer(ct0, n_player)
|
|
4210
4233
|
|
|
4211
4234
|
currentTimeOffset = dec(cumulative_time_pos + self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TIME_OFFSET])
|
|
4212
4235
|
|
|
@@ -4237,12 +4260,11 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4237
4260
|
self.show_current_states_in_subjects_table()
|
|
4238
4261
|
|
|
4239
4262
|
# current media name
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
current_playlist_index = self.dw_player[0].player.playlist_pos
|
|
4263
|
+
playlist = self.dw_player[0].player.playlist
|
|
4264
|
+
playlist_pos = self.dw_player[0].player.playlist_pos
|
|
4265
|
+
if playlist is not None and playlist_pos is not None and playlist:
|
|
4266
|
+
current_media_name = Path(playlist[playlist_pos]["filename"]).name
|
|
4267
|
+
current_playlist_index = playlist_pos
|
|
4246
4268
|
else:
|
|
4247
4269
|
current_media_name = ""
|
|
4248
4270
|
current_playlist_index = None
|
|
@@ -4279,9 +4301,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4279
4301
|
self.mem_media_name = current_media_name
|
|
4280
4302
|
self.mem_playlist_index = current_playlist_index
|
|
4281
4303
|
|
|
4282
|
-
playlist_length = len(self.dw_player[0].player.playlist)
|
|
4283
|
-
|
|
4284
4304
|
# update observation info
|
|
4305
|
+
playlist_length = len(playlist) if playlist else 0
|
|
4285
4306
|
msg = ""
|
|
4286
4307
|
if self.dw_player[0].player.time_pos is not None: # check if video
|
|
4287
4308
|
msg = f"Current media name: <b>{current_media_name}</b> (#{self.dw_player[0].player.playlist_pos + 1} / {playlist_length})<br>"
|
|
@@ -4315,15 +4336,14 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4315
4336
|
|
|
4316
4337
|
self.actionPlay.setIcon(QIcon(f":/play_{gui_utilities.theme_mode()}"))
|
|
4317
4338
|
|
|
4318
|
-
print(f"{msg=}")
|
|
4319
|
-
|
|
4320
4339
|
if msg:
|
|
4321
4340
|
self.lb_current_media_time.setText(msg)
|
|
4322
4341
|
|
|
4323
4342
|
# set video scroll bar
|
|
4324
4343
|
|
|
4325
4344
|
if scroll_slider and not self.user_move_slider:
|
|
4326
|
-
|
|
4345
|
+
if current_media_time_pos is not None and current_media_duration is not None:
|
|
4346
|
+
self.video_slider.setValue(round(current_media_time_pos / current_media_duration * (cfg.SLIDER_MAXIMUM - 1)))
|
|
4327
4347
|
|
|
4328
4348
|
def mpv_eof_reached(self):
|
|
4329
4349
|
"""
|
|
@@ -4488,7 +4508,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4488
4508
|
for x in self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]
|
|
4489
4509
|
)
|
|
4490
4510
|
|
|
4491
|
-
def choose_behavior(self, obs_key) ->
|
|
4511
|
+
def choose_behavior(self, obs_key) -> None | str:
|
|
4492
4512
|
"""
|
|
4493
4513
|
fill listwidget with all behaviors coded by key
|
|
4494
4514
|
|
|
@@ -4520,7 +4540,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4520
4540
|
else:
|
|
4521
4541
|
return None
|
|
4522
4542
|
|
|
4523
|
-
def choose_subject(self, subject_key) ->
|
|
4543
|
+
def choose_subject(self, subject_key) -> None | str:
|
|
4524
4544
|
"""
|
|
4525
4545
|
fill listwidget with all subjects coded by key
|
|
4526
4546
|
|
|
@@ -4602,13 +4622,15 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4602
4622
|
|
|
4603
4623
|
if self.playerType == cfg.MEDIA:
|
|
4604
4624
|
# cumulative time
|
|
4625
|
+
if self.dw_player[n_player].player.time_pos is None:
|
|
4626
|
+
return dec("NaN")
|
|
4605
4627
|
mem_laps = sum(self.dw_player[n_player].media_durations[0 : self.dw_player[n_player].player.playlist_pos]) + (
|
|
4606
4628
|
0 if self.dw_player[n_player].player.time_pos is None else self.dw_player[n_player].player.time_pos * 1000
|
|
4607
4629
|
)
|
|
4608
4630
|
|
|
4609
4631
|
return dec(str(round(mem_laps / 1000, 3)))
|
|
4610
4632
|
|
|
4611
|
-
def get_obs_time(self, n_player: int = 0) ->
|
|
4633
|
+
def get_obs_time(self, n_player: int = 0) -> tuple[dec, dec | None]:
|
|
4612
4634
|
"""
|
|
4613
4635
|
returns time in current media and cumulative time from begining of observation
|
|
4614
4636
|
do not add time offset
|
|
@@ -4621,7 +4643,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4621
4643
|
"""
|
|
4622
4644
|
|
|
4623
4645
|
if not self.observationId:
|
|
4624
|
-
return dec("0")
|
|
4646
|
+
return dec("0"), dec("0")
|
|
4625
4647
|
|
|
4626
4648
|
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.LIVE:
|
|
4627
4649
|
if "finished" in self.pb_live_obs.text():
|
|
@@ -4828,7 +4850,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4828
4850
|
|
|
4829
4851
|
# play / pause with space bar
|
|
4830
4852
|
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.LIVE:
|
|
4831
|
-
if ek in (Qt.Key_Space, Qt.Key_Enter, Qt.Key_Return):
|
|
4853
|
+
# if ek in (Qt.Key_Space, Qt.Key_Enter, Qt.Key_Return):
|
|
4854
|
+
if ek == Qt.Key_Space:
|
|
4832
4855
|
if self.liveObservationStarted:
|
|
4833
4856
|
if (
|
|
4834
4857
|
dialog.MessageDialog(cfg.programName, "Are you sure to stop the current live observation?", [cfg.YES, cfg.NO])
|
|
@@ -5457,7 +5480,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5457
5480
|
check if first player ended
|
|
5458
5481
|
"""
|
|
5459
5482
|
|
|
5460
|
-
|
|
5483
|
+
logging.debug("play_video")
|
|
5461
5484
|
|
|
5462
5485
|
if self.geometric_measurements_mode:
|
|
5463
5486
|
return
|
|
@@ -5465,6 +5488,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5465
5488
|
if self.playerType != cfg.MEDIA:
|
|
5466
5489
|
return
|
|
5467
5490
|
|
|
5491
|
+
# check if playlist is ended. If it is ended restart playlist from beginning
|
|
5492
|
+
if self.dw_player[0].player.eof_reached and self.dw_player[0].player.core_idle:
|
|
5493
|
+
if self.dw_player[0].player.playlist_pos is not None and self.dw_player[0].player.playlist_count is not None:
|
|
5494
|
+
if self.dw_player[0].player.playlist_pos == self.dw_player[0].player.playlist_count - 1:
|
|
5495
|
+
self.seek_mediaplayer(dec(0))
|
|
5496
|
+
|
|
5468
5497
|
# check if player 1 is ended
|
|
5469
5498
|
for i, dw in enumerate(self.dw_player):
|
|
5470
5499
|
if (
|
|
@@ -5475,9 +5504,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5475
5504
|
|
|
5476
5505
|
self.lb_player_status.clear()
|
|
5477
5506
|
|
|
5478
|
-
# if self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.VISUALIZE_WAVEFORM, False) \
|
|
5479
|
-
# or self.pj[cfg.OBSERVATIONS][self.observationId].get(VISUALIZE_SPECTROGRAM, False):
|
|
5480
|
-
|
|
5481
5507
|
self.statusbar.showMessage("", 0)
|
|
5482
5508
|
|
|
5483
5509
|
if self.ipc_mpv_timer is not None:
|
|
@@ -5554,16 +5580,19 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5554
5580
|
|
|
5555
5581
|
decrement = self.fast * self.play_rate if self.config_param.get(cfg.ADAPT_FAST_JUMP, cfg.ADAPT_FAST_JUMP_DEFAULT) else self.fast
|
|
5556
5582
|
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5583
|
+
try:
|
|
5584
|
+
new_time = (
|
|
5585
|
+
sum(self.dw_player[0].media_durations[0 : self.dw_player[0].player.playlist_pos]) / 1000
|
|
5586
|
+
+ self.dw_player[0].player.playback_time
|
|
5587
|
+
- decrement
|
|
5588
|
+
)
|
|
5589
|
+
except Exception:
|
|
5590
|
+
return
|
|
5562
5591
|
|
|
5563
5592
|
if new_time < decrement:
|
|
5564
5593
|
new_time = 0
|
|
5565
5594
|
|
|
5566
|
-
self.seek_mediaplayer(new_time)
|
|
5595
|
+
self.seek_mediaplayer(dec(new_time))
|
|
5567
5596
|
|
|
5568
5597
|
self.update_visualizations()
|
|
5569
5598
|
|
|
@@ -5577,13 +5606,16 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5577
5606
|
|
|
5578
5607
|
logging.info(f"Jump forward for {increment} seconds")
|
|
5579
5608
|
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5609
|
+
try:
|
|
5610
|
+
new_time = (
|
|
5611
|
+
sum(self.dw_player[0].media_durations[0 : self.dw_player[0].player.playlist_pos]) / 1000
|
|
5612
|
+
+ self.dw_player[0].player.playback_time
|
|
5613
|
+
+ increment
|
|
5614
|
+
)
|
|
5615
|
+
except Exception:
|
|
5616
|
+
return
|
|
5585
5617
|
|
|
5586
|
-
self.seek_mediaplayer(new_time)
|
|
5618
|
+
self.seek_mediaplayer(dec(new_time))
|
|
5587
5619
|
|
|
5588
5620
|
self.update_visualizations()
|
|
5589
5621
|
|
|
@@ -5839,6 +5871,15 @@ def main():
|
|
|
5839
5871
|
)
|
|
5840
5872
|
sys.exit()
|
|
5841
5873
|
|
|
5874
|
+
if sys.platform.startswith("darwin"):
|
|
5875
|
+
QMessageBox.warning(
|
|
5876
|
+
None,
|
|
5877
|
+
cfg.programName,
|
|
5878
|
+
("This version of BORIS for macOS is still EXPERIMENTAL and should be used at your own risk."),
|
|
5879
|
+
QMessageBox.Ok | QMessageBox.Default,
|
|
5880
|
+
QMessageBox.NoButton,
|
|
5881
|
+
)
|
|
5882
|
+
|
|
5842
5883
|
window.show()
|
|
5843
5884
|
window.raise_() # for overlapping widget (?)
|
|
5844
5885
|
|