boris-behav-obs 8.12__py3-none-any.whl → 9.7.6__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.
Potentially problematic release.
This version of boris-behav-obs might be problematic. Click here for more details.
- boris/__init__.py +1 -1
- boris/__main__.py +1 -1
- boris/about.py +28 -39
- boris/add_modifier.py +122 -109
- boris/add_modifier_ui.py +239 -135
- boris/advanced_event_filtering.py +81 -45
- boris/analysis_plugins/__init__.py +0 -0
- boris/analysis_plugins/_latency.py +59 -0
- boris/analysis_plugins/irr_cohen_kappa.py +109 -0
- boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
- boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
- boris/analysis_plugins/number_of_occurences.py +22 -0
- boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
- boris/analysis_plugins/time_budget.py +61 -0
- boris/behav_coding_map_creator.py +228 -229
- boris/behavior_binary_table.py +33 -50
- boris/behaviors_coding_map.py +17 -18
- boris/boris_cli.py +6 -25
- boris/cmd_arguments.py +12 -1
- boris/coding_pad.py +42 -49
- boris/config.py +141 -65
- boris/config_file.py +58 -67
- boris/connections.py +107 -61
- boris/converters.py +13 -37
- boris/converters_ui.py +187 -110
- boris/cooccurence.py +250 -0
- boris/core.py +2373 -1786
- boris/core_qrc.py +15895 -10743
- boris/core_ui.py +943 -798
- boris/db_functions.py +17 -42
- boris/dev.py +109 -8
- boris/dialog.py +482 -236
- boris/duration_widget.py +9 -14
- boris/edit_event.py +61 -31
- boris/edit_event_ui.py +208 -97
- boris/event_operations.py +408 -293
- boris/events_cursor.py +25 -17
- boris/events_snapshots.py +36 -82
- boris/exclusion_matrix.py +4 -9
- boris/export_events.py +184 -223
- boris/export_observation.py +74 -100
- boris/external_processes.py +123 -98
- boris/geometric_measurement.py +644 -290
- boris/gui_utilities.py +91 -14
- boris/image_overlay.py +4 -4
- boris/import_observations.py +190 -98
- boris/ipc_mpv.py +325 -0
- boris/irr.py +20 -57
- boris/latency.py +31 -24
- boris/measurement_widget.py +14 -18
- boris/media_file.py +17 -19
- boris/menu_options.py +17 -6
- boris/modifier_coding_map_creator.py +1013 -0
- boris/modifiers_coding_map.py +7 -9
- boris/mpv.py +1 -0
- boris/mpv2.py +732 -705
- boris/observation.py +533 -221
- boris/observation_operations.py +1025 -390
- boris/observation_ui.py +572 -362
- boris/observations_list.py +71 -53
- boris/otx_parser.py +74 -68
- boris/param_panel.py +31 -16
- boris/param_panel_ui.py +254 -138
- boris/player_dock_widget.py +90 -60
- boris/plot_data_module.py +25 -33
- boris/plot_events.py +127 -90
- boris/plot_events_rt.py +17 -31
- boris/plot_spectrogram_rt.py +95 -30
- boris/plot_waveform_rt.py +32 -21
- boris/plugins.py +431 -0
- boris/portion/__init__.py +18 -8
- boris/portion/const.py +35 -18
- boris/portion/dict.py +5 -5
- boris/portion/func.py +2 -2
- boris/portion/interval.py +21 -41
- boris/portion/io.py +41 -32
- boris/preferences.py +306 -83
- boris/preferences_ui.py +684 -227
- boris/project.py +448 -293
- boris/project_functions.py +671 -238
- boris/project_import_export.py +213 -222
- boris/project_ui.py +674 -438
- boris/qrc_boris.py +6 -3
- boris/qrc_boris5.py +6 -3
- boris/select_modifiers.py +74 -48
- boris/select_observations.py +20 -198
- boris/select_subj_behav.py +67 -39
- boris/state_events.py +52 -35
- boris/subjects_pad.py +6 -9
- boris/synthetic_time_budget.py +45 -28
- boris/time_budget_functions.py +171 -171
- boris/time_budget_widget.py +84 -114
- boris/transitions.py +41 -47
- boris/utilities.py +627 -236
- boris/version.py +3 -3
- boris/video_equalizer.py +16 -14
- boris/video_equalizer_ui.py +199 -130
- boris/video_operations.py +95 -29
- boris/view_df.py +104 -0
- boris/view_df_ui.py +75 -0
- boris/write_event.py +538 -0
- boris_behav_obs-9.7.6.dist-info/METADATA +139 -0
- boris_behav_obs-9.7.6.dist-info/RECORD +109 -0
- {boris_behav_obs-8.12.dist-info → boris_behav_obs-9.7.6.dist-info}/WHEEL +1 -1
- boris_behav_obs-9.7.6.dist-info/entry_points.txt +2 -0
- boris/README.TXT +0 -22
- boris/add_modifier.ui +0 -323
- boris/converters.ui +0 -289
- boris/core.qrc +0 -36
- boris/core.ui +0 -1556
- boris/edit_event.ui +0 -233
- boris/icons/logo_eye.ico +0 -0
- boris/map_creator.py +0 -850
- boris/observation.ui +0 -814
- boris/param_panel.ui +0 -379
- boris/preferences.ui +0 -537
- boris/project.ui +0 -1069
- boris/project_server.py +0 -236
- boris/vlc.py +0 -10343
- boris/vlc_local.py +0 -90
- boris_behav_obs-8.12.dist-info/LICENSE.TXT +0 -674
- boris_behav_obs-8.12.dist-info/METADATA +0 -128
- boris_behav_obs-8.12.dist-info/RECORD +0 -108
- boris_behav_obs-8.12.dist-info/entry_points.txt +0 -3
- {boris → boris_behav_obs-9.7.6.dist-info/licenses}/LICENSE.TXT +0 -0
- {boris_behav_obs-8.12.dist-info → boris_behav_obs-9.7.6.dist-info}/top_level.txt +0 -0
boris/observation_operations.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
BORIS
|
|
3
3
|
Behavioral Observation Research Interactive Software
|
|
4
|
-
Copyright 2012-
|
|
4
|
+
Copyright 2012-2025 Olivier Friard
|
|
5
5
|
|
|
6
6
|
This program is free software; you can redistribute it and/or modify
|
|
7
7
|
it under the terms of the GNU General Public License as published by
|
|
@@ -19,20 +19,23 @@ Copyright 2012-2023 Olivier Friard
|
|
|
19
19
|
MA 02110-1301, USA.
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
from math import log2
|
|
23
|
-
import os
|
|
24
22
|
import logging
|
|
25
|
-
import
|
|
26
|
-
import
|
|
27
|
-
import subprocess
|
|
28
|
-
import sys
|
|
23
|
+
from collections import deque
|
|
24
|
+
import datetime as dt
|
|
29
25
|
from decimal import Decimal as dec
|
|
26
|
+
import json
|
|
27
|
+
from math import log2, floor
|
|
28
|
+
import os
|
|
30
29
|
import pathlib as pl
|
|
31
|
-
import
|
|
32
|
-
|
|
30
|
+
import socket
|
|
31
|
+
import subprocess
|
|
32
|
+
import sys
|
|
33
|
+
import tempfile
|
|
34
|
+
import time
|
|
35
|
+
from typing import List, Tuple, Optional
|
|
33
36
|
|
|
34
37
|
|
|
35
|
-
from
|
|
38
|
+
from PySide6.QtWidgets import (
|
|
36
39
|
QMessageBox,
|
|
37
40
|
QFileDialog,
|
|
38
41
|
QDateTimeEdit,
|
|
@@ -41,11 +44,12 @@ from PyQt5.QtWidgets import (
|
|
|
41
44
|
QSlider,
|
|
42
45
|
QMainWindow,
|
|
43
46
|
QDockWidget,
|
|
47
|
+
QWidget,
|
|
44
48
|
)
|
|
45
|
-
from
|
|
46
|
-
from
|
|
49
|
+
from PySide6.QtCore import Qt, QDateTime, QTimer
|
|
50
|
+
from PySide6.QtGui import QFont, QIcon, QTextCursor
|
|
47
51
|
|
|
48
|
-
from
|
|
52
|
+
from PySide6 import QtTest
|
|
49
53
|
|
|
50
54
|
from . import menu_options
|
|
51
55
|
from . import config as cfg
|
|
@@ -58,6 +62,7 @@ from . import plot_data_module
|
|
|
58
62
|
from . import player_dock_widget
|
|
59
63
|
from . import gui_utilities
|
|
60
64
|
from . import video_operations
|
|
65
|
+
from . import state_events
|
|
61
66
|
|
|
62
67
|
|
|
63
68
|
def export_observations_list_clicked(self):
|
|
@@ -78,9 +83,7 @@ def export_observations_list_clicked(self):
|
|
|
78
83
|
cfg.HTML,
|
|
79
84
|
]
|
|
80
85
|
|
|
81
|
-
file_name, filter_ = QFileDialog().getSaveFileName(
|
|
82
|
-
self, "Export list of selected observations", "", ";;".join(file_formats)
|
|
83
|
-
)
|
|
86
|
+
file_name, filter_ = QFileDialog().getSaveFileName(self, "Export list of selected observations", "", ";;".join(file_formats))
|
|
84
87
|
|
|
85
88
|
if not file_name:
|
|
86
89
|
return
|
|
@@ -90,12 +93,7 @@ def export_observations_list_clicked(self):
|
|
|
90
93
|
file_name = str(pl.Path(file_name)) + "." + output_format
|
|
91
94
|
# check if file name with extension already exists
|
|
92
95
|
if pl.Path(file_name).is_file():
|
|
93
|
-
if (
|
|
94
|
-
dialog.MessageDialog(
|
|
95
|
-
cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE]
|
|
96
|
-
)
|
|
97
|
-
== cfg.CANCEL
|
|
98
|
-
):
|
|
96
|
+
if dialog.MessageDialog(cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE]) == cfg.CANCEL:
|
|
99
97
|
return
|
|
100
98
|
|
|
101
99
|
if not project_functions.export_observations_list(self.pj, selected_observations, file_name, output_format):
|
|
@@ -107,7 +105,7 @@ def observations_list(self):
|
|
|
107
105
|
show list of all observations of current project
|
|
108
106
|
"""
|
|
109
107
|
|
|
110
|
-
logging.debug(
|
|
108
|
+
logging.debug("observations list")
|
|
111
109
|
|
|
112
110
|
if self.playerType in cfg.VIEWERS:
|
|
113
111
|
close_observation(self)
|
|
@@ -115,20 +113,25 @@ def observations_list(self):
|
|
|
115
113
|
result, selected_obs = select_observations.select_observations2(self, cfg.SINGLE)
|
|
116
114
|
|
|
117
115
|
if not selected_obs:
|
|
116
|
+
# activate main window
|
|
117
|
+
self.activateWindow()
|
|
118
118
|
return
|
|
119
119
|
|
|
120
120
|
if self.observationId:
|
|
121
|
-
|
|
122
121
|
self.hide_data_files()
|
|
123
122
|
response = dialog.MessageDialog(
|
|
124
123
|
cfg.programName, "The current observation will be closed. Do you want to continue?", (cfg.YES, cfg.NO)
|
|
125
124
|
)
|
|
126
125
|
if response == cfg.NO:
|
|
127
126
|
self.show_data_files()
|
|
127
|
+
# activate main window
|
|
128
|
+
self.activateWindow()
|
|
129
|
+
|
|
128
130
|
return ""
|
|
129
131
|
else:
|
|
130
132
|
close_observation(self)
|
|
131
133
|
|
|
134
|
+
|
|
132
135
|
QtTest.QTest.qWait(1000)
|
|
133
136
|
|
|
134
137
|
if result == cfg.OPEN:
|
|
@@ -144,10 +147,12 @@ def observations_list(self):
|
|
|
144
147
|
QMessageBox.warning(
|
|
145
148
|
self,
|
|
146
149
|
cfg.programName,
|
|
147
|
-
(f"The observation <b>{self.observationId}</b> is running!<br>
|
|
150
|
+
(f"The observation <b>{self.observationId}</b> is running!<br>Close it before editing."),
|
|
148
151
|
)
|
|
149
152
|
|
|
150
|
-
logging.debug(
|
|
153
|
+
logging.debug("end observations list")
|
|
154
|
+
# activate main window
|
|
155
|
+
self.activateWindow()
|
|
151
156
|
|
|
152
157
|
|
|
153
158
|
def open_observation(self, mode: str) -> str:
|
|
@@ -159,11 +164,10 @@ def open_observation(self, mode: str) -> str:
|
|
|
159
164
|
"view" to view observation
|
|
160
165
|
"""
|
|
161
166
|
|
|
162
|
-
logging.debug(
|
|
167
|
+
logging.debug("open observation")
|
|
163
168
|
|
|
164
169
|
# check if current observation must be closed to open a new one
|
|
165
170
|
if self.observationId:
|
|
166
|
-
|
|
167
171
|
self.hide_data_files()
|
|
168
172
|
response = dialog.MessageDialog(
|
|
169
173
|
cfg.programName, "The current observation will be closed. Do you want to continue?", (cfg.YES, cfg.NO)
|
|
@@ -195,7 +199,7 @@ def load_observation(self, obs_id: str, mode: str = cfg.OBS_START) -> str:
|
|
|
195
199
|
"view" to view observation
|
|
196
200
|
"""
|
|
197
201
|
|
|
198
|
-
logging.debug(
|
|
202
|
+
logging.debug("load observation")
|
|
199
203
|
|
|
200
204
|
if obs_id not in self.pj[cfg.OBSERVATIONS]:
|
|
201
205
|
return "Error: Observation not found"
|
|
@@ -206,7 +210,6 @@ def load_observation(self, obs_id: str, mode: str = cfg.OBS_START) -> str:
|
|
|
206
210
|
self.observationId = obs_id
|
|
207
211
|
|
|
208
212
|
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.IMAGES:
|
|
209
|
-
|
|
210
213
|
self.image_idx = 0
|
|
211
214
|
self.images_list = []
|
|
212
215
|
|
|
@@ -219,7 +222,6 @@ def load_observation(self, obs_id: str, mode: str = cfg.OBS_START) -> str:
|
|
|
219
222
|
self.dwEvents.setVisible(True)
|
|
220
223
|
|
|
221
224
|
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.LIVE:
|
|
222
|
-
|
|
223
225
|
if mode == cfg.OBS_START:
|
|
224
226
|
initialize_new_live_observation(self)
|
|
225
227
|
|
|
@@ -228,12 +230,12 @@ def load_observation(self, obs_id: str, mode: str = cfg.OBS_START) -> str:
|
|
|
228
230
|
self.dwEvents.setVisible(True)
|
|
229
231
|
|
|
230
232
|
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.MEDIA:
|
|
231
|
-
|
|
232
233
|
if mode == cfg.OBS_START:
|
|
233
234
|
if not initialize_new_media_observation(self):
|
|
234
|
-
self
|
|
235
|
-
self.
|
|
236
|
-
|
|
235
|
+
close_observation(self)
|
|
236
|
+
# self.observationId = ""
|
|
237
|
+
# self.twEvents.setRowCount(0)
|
|
238
|
+
# menu_options.update_menu(self)
|
|
237
239
|
return "Error: loading observation problem"
|
|
238
240
|
|
|
239
241
|
if mode == cfg.VIEW:
|
|
@@ -246,7 +248,7 @@ def load_observation(self, obs_id: str, mode: str = cfg.OBS_START) -> str:
|
|
|
246
248
|
# title of dock widget “ ”
|
|
247
249
|
self.dwEvents.setWindowTitle(f"Events for “{self.observationId}” observation")
|
|
248
250
|
|
|
249
|
-
logging.debug(
|
|
251
|
+
logging.debug("end load observation")
|
|
250
252
|
return ""
|
|
251
253
|
|
|
252
254
|
|
|
@@ -260,9 +262,7 @@ def edit_observation(self):
|
|
|
260
262
|
# hide data plot
|
|
261
263
|
self.hide_data_files()
|
|
262
264
|
if (
|
|
263
|
-
dialog.MessageDialog(
|
|
264
|
-
cfg.programName, "The current observation will be closed. Do you want to continue?", (cfg.YES, cfg.NO)
|
|
265
|
-
)
|
|
265
|
+
dialog.MessageDialog(cfg.programName, "The current observation will be closed. Do you want to continue?", (cfg.YES, cfg.NO))
|
|
266
266
|
== cfg.NO
|
|
267
267
|
):
|
|
268
268
|
# restore plots
|
|
@@ -271,9 +271,7 @@ def edit_observation(self):
|
|
|
271
271
|
else:
|
|
272
272
|
close_observation(self)
|
|
273
273
|
|
|
274
|
-
_, selected_observations = select_observations.select_observations2(
|
|
275
|
-
self, cfg.EDIT, windows_title="Edit observation"
|
|
276
|
-
)
|
|
274
|
+
_, selected_observations = select_observations.select_observations2(self, cfg.EDIT, windows_title="Edit observation")
|
|
277
275
|
|
|
278
276
|
if selected_observations:
|
|
279
277
|
new_observation(self, mode=cfg.EDIT, obsId=selected_observations[0])
|
|
@@ -284,9 +282,7 @@ def remove_observations(self):
|
|
|
284
282
|
remove observations from project file
|
|
285
283
|
"""
|
|
286
284
|
|
|
287
|
-
_, selected_observations = select_observations.select_observations2(
|
|
288
|
-
self, cfg.MULTIPLE, windows_title="Remove observations"
|
|
289
|
-
)
|
|
285
|
+
_, selected_observations = select_observations.select_observations2(self, cfg.MULTIPLE, windows_title="Remove observations")
|
|
290
286
|
if not selected_observations:
|
|
291
287
|
return
|
|
292
288
|
|
|
@@ -329,11 +325,7 @@ def coding_time(observations: dict, observations_list: list) -> Tuple[Optional[d
|
|
|
329
325
|
observation = observations[obs_id]
|
|
330
326
|
if observation[cfg.EVENTS]:
|
|
331
327
|
# check if events contain a NA timestamp
|
|
332
|
-
if [
|
|
333
|
-
event[cfg.EVENT_TIME_FIELD_IDX]
|
|
334
|
-
for event in observation[cfg.EVENTS]
|
|
335
|
-
if event[cfg.EVENT_TIME_FIELD_IDX].is_nan()
|
|
336
|
-
]:
|
|
328
|
+
if [event[cfg.EVENT_TIME_FIELD_IDX] for event in observation[cfg.EVENTS] if event[cfg.EVENT_TIME_FIELD_IDX].is_nan()]:
|
|
337
329
|
return dec("NaN"), dec("NaN"), dec("NaN")
|
|
338
330
|
start_coding_list.append(observation[cfg.EVENTS][0][cfg.EVENT_TIME_FIELD_IDX])
|
|
339
331
|
end_coding_list.append(observation[cfg.EVENTS][-1][cfg.EVENT_TIME_FIELD_IDX])
|
|
@@ -364,6 +356,47 @@ def coding_time(observations: dict, observations_list: list) -> Tuple[Optional[d
|
|
|
364
356
|
return start_coding, end_coding, coding_duration
|
|
365
357
|
|
|
366
358
|
|
|
359
|
+
def time_intervals_range(observations: dict, observations_list: list) -> Tuple[Optional[dec], Optional[dec]]:
|
|
360
|
+
"""
|
|
361
|
+
returns earliest start interval and latest end interval
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
observations (dict): observations of project
|
|
365
|
+
observations_list (list): list of selected observations
|
|
366
|
+
|
|
367
|
+
Returns:
|
|
368
|
+
decimal.Decimal: time of earliest start interval
|
|
369
|
+
decimal.Decimal: time of latest end interval
|
|
370
|
+
|
|
371
|
+
"""
|
|
372
|
+
start_interval_list: list = []
|
|
373
|
+
end_interval_list: list = []
|
|
374
|
+
for obs_id in observations_list:
|
|
375
|
+
observation = observations[obs_id]
|
|
376
|
+
offset = observation[cfg.TIME_OFFSET]
|
|
377
|
+
# check if observation interval is defined
|
|
378
|
+
if (
|
|
379
|
+
not observation.get(cfg.OBSERVATION_TIME_INTERVAL, [None, None])[0]
|
|
380
|
+
and not observation.get(cfg.OBSERVATION_TIME_INTERVAL, [None, None])[1]
|
|
381
|
+
):
|
|
382
|
+
return None, None
|
|
383
|
+
|
|
384
|
+
start_interval_list.append(dec(observation[cfg.OBSERVATION_TIME_INTERVAL][0]) + offset)
|
|
385
|
+
end_interval_list.append(dec(observation[cfg.OBSERVATION_TIME_INTERVAL][1]) + offset)
|
|
386
|
+
|
|
387
|
+
if not start_interval_list:
|
|
388
|
+
earliest_start_interval = None
|
|
389
|
+
else:
|
|
390
|
+
earliest_start_interval = min([x for x in start_interval_list])
|
|
391
|
+
|
|
392
|
+
if not end_interval_list:
|
|
393
|
+
latest_end_interval = None
|
|
394
|
+
else:
|
|
395
|
+
latest_end_interval = max([x for x in end_interval_list])
|
|
396
|
+
|
|
397
|
+
return earliest_start_interval, latest_end_interval
|
|
398
|
+
|
|
399
|
+
|
|
367
400
|
def observation_total_length(observation: dict) -> dec:
|
|
368
401
|
"""
|
|
369
402
|
Observation media duration (if any)
|
|
@@ -394,7 +427,7 @@ def observation_total_length(observation: dict) -> dec:
|
|
|
394
427
|
last_event = obs_length = max(observation[cfg.EVENTS])[cfg.TW_OBS_FIELD[cfg.IMAGES]["time"]]
|
|
395
428
|
obs_length = last_event - first_event
|
|
396
429
|
except Exception:
|
|
397
|
-
logging.critical(
|
|
430
|
+
logging.critical("Length of observation from images not available")
|
|
398
431
|
obs_length = dec(-2)
|
|
399
432
|
else:
|
|
400
433
|
obs_length = dec(0)
|
|
@@ -510,10 +543,7 @@ def observation_length(pj: dict, selected_observations: list) -> tuple:
|
|
|
510
543
|
if (
|
|
511
544
|
dialog.MessageDialog(
|
|
512
545
|
cfg.programName,
|
|
513
|
-
(
|
|
514
|
-
f"The observation length is not available (<b>{obs_id}</b>).<br>"
|
|
515
|
-
"Use last event time as observation length?"
|
|
516
|
-
),
|
|
546
|
+
(f"The observation length is not available (<b>{obs_id}</b>).<br>Use last event time as observation length?"),
|
|
517
547
|
(cfg.YES, cfg.NO),
|
|
518
548
|
)
|
|
519
549
|
== cfg.YES
|
|
@@ -545,7 +575,7 @@ def observation_length(pj: dict, selected_observations: list) -> tuple:
|
|
|
545
575
|
return (max_obs_length, selectedObsTotalMediaLength)
|
|
546
576
|
|
|
547
577
|
|
|
548
|
-
def new_observation(self, mode=cfg.NEW, obsId=""):
|
|
578
|
+
def new_observation(self, mode: str = cfg.NEW, obsId: str = "") -> None:
|
|
549
579
|
"""
|
|
550
580
|
define a new observation or edit an existing observation
|
|
551
581
|
|
|
@@ -553,18 +583,18 @@ def new_observation(self, mode=cfg.NEW, obsId=""):
|
|
|
553
583
|
mode (str): NEW or EDIT
|
|
554
584
|
obsId (str): observation Id to be edited
|
|
555
585
|
|
|
586
|
+
Retruns:
|
|
587
|
+
None
|
|
588
|
+
|
|
556
589
|
"""
|
|
557
590
|
# check if current observation must be closed to create a new one
|
|
558
591
|
if mode == cfg.NEW and self.observationId:
|
|
559
592
|
# hide data plot
|
|
560
593
|
self.hide_data_files()
|
|
561
594
|
if (
|
|
562
|
-
dialog.MessageDialog(
|
|
563
|
-
cfg.programName, "The current observation will be closed. Do you want to continue?", (cfg.YES, cfg.NO)
|
|
564
|
-
)
|
|
595
|
+
dialog.MessageDialog(cfg.programName, "The current observation will be closed. Do you want to continue?", (cfg.YES, cfg.NO))
|
|
565
596
|
== cfg.NO
|
|
566
597
|
):
|
|
567
|
-
|
|
568
598
|
# show data plot
|
|
569
599
|
self.show_data_files()
|
|
570
600
|
return
|
|
@@ -572,9 +602,7 @@ def new_observation(self, mode=cfg.NEW, obsId=""):
|
|
|
572
602
|
close_observation(self)
|
|
573
603
|
|
|
574
604
|
observationWindow = observation.Observation(
|
|
575
|
-
tmp_dir=self.ffmpeg_cache_dir
|
|
576
|
-
if (self.ffmpeg_cache_dir and pl.Path(self.ffmpeg_cache_dir).is_dir())
|
|
577
|
-
else tempfile.gettempdir(),
|
|
605
|
+
tmp_dir=self.ffmpeg_cache_dir if (self.ffmpeg_cache_dir and pl.Path(self.ffmpeg_cache_dir).is_dir()) else tempfile.gettempdir(),
|
|
578
606
|
project_path=self.projectFileName,
|
|
579
607
|
converters=self.pj.get(cfg.CONVERTERS, {}),
|
|
580
608
|
time_format=self.timeFormat,
|
|
@@ -586,16 +614,15 @@ def new_observation(self, mode=cfg.NEW, obsId=""):
|
|
|
586
614
|
observationWindow.mem_obs_id = obsId
|
|
587
615
|
observationWindow.chunk_length = self.chunk_length
|
|
588
616
|
observationWindow.dteDate.setDateTime(QDateTime.currentDateTime())
|
|
617
|
+
# observationWindow.de_date_offset.setDateTime(QDateTime.currentDateTime())
|
|
589
618
|
observationWindow.ffmpeg_bin = self.ffmpeg_bin
|
|
590
619
|
observationWindow.project_file_name = self.projectFileName
|
|
591
620
|
observationWindow.rb_no_time.setChecked(True)
|
|
592
621
|
|
|
593
622
|
# add independent variables
|
|
594
623
|
if cfg.INDEPENDENT_VARIABLES in self.pj:
|
|
595
|
-
|
|
596
624
|
observationWindow.twIndepVariables.setRowCount(0)
|
|
597
625
|
for i in util.sorted_keys(self.pj[cfg.INDEPENDENT_VARIABLES]):
|
|
598
|
-
|
|
599
626
|
observationWindow.twIndepVariables.setRowCount(observationWindow.twIndepVariables.rowCount() + 1)
|
|
600
627
|
|
|
601
628
|
# label
|
|
@@ -630,22 +657,18 @@ def new_observation(self, mode=cfg.NEW, obsId=""):
|
|
|
630
657
|
comboBox = QComboBox()
|
|
631
658
|
comboBox.addItems(self.pj[cfg.INDEPENDENT_VARIABLES][i]["possible values"].split(","))
|
|
632
659
|
if txt in self.pj[cfg.INDEPENDENT_VARIABLES][i]["possible values"].split(","):
|
|
633
|
-
comboBox.setCurrentIndex(
|
|
634
|
-
|
|
635
|
-
)
|
|
636
|
-
observationWindow.twIndepVariables.setCellWidget(
|
|
637
|
-
observationWindow.twIndepVariables.rowCount() - 1, 2, comboBox
|
|
638
|
-
)
|
|
660
|
+
comboBox.setCurrentIndex(self.pj[cfg.INDEPENDENT_VARIABLES][i]["possible values"].split(",").index(txt))
|
|
661
|
+
observationWindow.twIndepVariables.setCellWidget(observationWindow.twIndepVariables.rowCount() - 1, 2, comboBox)
|
|
639
662
|
|
|
640
663
|
elif self.pj[cfg.INDEPENDENT_VARIABLES][i]["type"] == cfg.TIMESTAMP:
|
|
641
664
|
cal = QDateTimeEdit()
|
|
642
|
-
cal.setDisplayFormat("yyyy-MM-dd hh:mm:ss")
|
|
665
|
+
cal.setDisplayFormat("yyyy-MM-dd hh:mm:ss.zzz")
|
|
643
666
|
cal.setCalendarPopup(True)
|
|
644
|
-
if txt:
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
)
|
|
667
|
+
if len(txt) == len("yyyy-MM-ddThh:mm:ss"):
|
|
668
|
+
txt += ".000"
|
|
669
|
+
cal.setDateTime(QDateTime.fromString(txt, "yyyy-MM-ddThh:mm:ss.zzz"))
|
|
670
|
+
|
|
671
|
+
observationWindow.twIndepVariables.setCellWidget(observationWindow.twIndepVariables.rowCount() - 1, 2, cal)
|
|
649
672
|
else:
|
|
650
673
|
item.setText(txt)
|
|
651
674
|
observationWindow.twIndepVariables.setItem(observationWindow.twIndepVariables.rowCount() - 1, 2, item)
|
|
@@ -654,28 +677,34 @@ def new_observation(self, mode=cfg.NEW, obsId=""):
|
|
|
654
677
|
|
|
655
678
|
# adapt time offset for current time format
|
|
656
679
|
if self.timeFormat == cfg.S:
|
|
657
|
-
observationWindow.obs_time_offset.
|
|
680
|
+
observationWindow.obs_time_offset.rb_seconds.setChecked(True)
|
|
658
681
|
if self.timeFormat == cfg.HHMMSS:
|
|
659
|
-
observationWindow.obs_time_offset.set_format_hhmmss()
|
|
682
|
+
# observationWindow.obs_time_offset.set_format_hhmmss()
|
|
683
|
+
observationWindow.obs_time_offset.rb_time.setChecked(True)
|
|
660
684
|
|
|
661
|
-
|
|
685
|
+
observationWindow.obs_time_offset.set_time(0)
|
|
662
686
|
|
|
687
|
+
if mode == cfg.EDIT:
|
|
663
688
|
observationWindow.setWindowTitle(f'Edit observation "{obsId}"')
|
|
664
|
-
mem_obs_id = obsId
|
|
689
|
+
"""mem_obs_id = obsId"""
|
|
665
690
|
observationWindow.leObservationId.setText(obsId)
|
|
666
691
|
|
|
667
692
|
# check date format for old versions of BORIS app
|
|
668
693
|
try:
|
|
669
694
|
time.strptime(self.pj[cfg.OBSERVATIONS][obsId]["date"], "%Y-%m-%d %H:%M")
|
|
670
|
-
self.pj[cfg.OBSERVATIONS][obsId]["date"] = (
|
|
671
|
-
|
|
672
|
-
)
|
|
695
|
+
self.pj[cfg.OBSERVATIONS][obsId]["date"] = self.pj[cfg.OBSERVATIONS][obsId]["date"].replace(" ", "T") + ":00.000"
|
|
696
|
+
logging.info("Old observation date/time format was converted")
|
|
673
697
|
except ValueError:
|
|
674
698
|
pass
|
|
675
699
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
)
|
|
700
|
+
# print(f"{self.pj[cfg.OBSERVATIONS][obsId]['date']=}")
|
|
701
|
+
|
|
702
|
+
# test new date (with msec)
|
|
703
|
+
if len(self.pj[cfg.OBSERVATIONS][obsId]["date"]) == len("yyyy-MM-ddThh:mm:ss.zzz"):
|
|
704
|
+
observationWindow.dteDate.setDateTime(QDateTime.fromString(self.pj[cfg.OBSERVATIONS][obsId]["date"], "yyyy-MM-ddThh:mm:ss.zzz"))
|
|
705
|
+
elif len(self.pj[cfg.OBSERVATIONS][obsId]["date"]) == len("yyyy-MM-ddThh:mm:ss"):
|
|
706
|
+
observationWindow.dteDate.setDateTime(QDateTime.fromString(self.pj[cfg.OBSERVATIONS][obsId]["date"], "yyyy-MM-ddThh:mm:ss"))
|
|
707
|
+
|
|
679
708
|
observationWindow.teDescription.setPlainText(self.pj[cfg.OBSERVATIONS][obsId][cfg.DESCRIPTION])
|
|
680
709
|
|
|
681
710
|
try:
|
|
@@ -694,17 +723,20 @@ def new_observation(self, mode=cfg.NEW, obsId=""):
|
|
|
694
723
|
logging.info("No Video/Audio information")
|
|
695
724
|
|
|
696
725
|
# offset
|
|
697
|
-
|
|
726
|
+
if self.pj[cfg.OBSERVATIONS][obsId][cfg.TIME_OFFSET] > cfg.DATE_CUTOFF:
|
|
727
|
+
observationWindow.obs_time_offset.rb_datetime.setChecked(True)
|
|
728
|
+
|
|
729
|
+
# time offset
|
|
730
|
+
if self.pj[cfg.OBSERVATIONS][obsId][cfg.TIME_OFFSET]:
|
|
731
|
+
observationWindow.cb_time_offset.setChecked(True)
|
|
732
|
+
observationWindow.obs_time_offset.set_time(self.pj[cfg.OBSERVATIONS][obsId][cfg.TIME_OFFSET])
|
|
698
733
|
|
|
699
734
|
if self.pj[cfg.OBSERVATIONS][obsId]["type"] == cfg.MEDIA:
|
|
700
735
|
observationWindow.rb_media_files.setChecked(True)
|
|
701
736
|
|
|
702
737
|
observationWindow.twVideo1.setRowCount(0)
|
|
703
738
|
for player in self.pj[cfg.OBSERVATIONS][obsId][cfg.FILE]:
|
|
704
|
-
if
|
|
705
|
-
player in self.pj[cfg.OBSERVATIONS][obsId][cfg.FILE]
|
|
706
|
-
and self.pj[cfg.OBSERVATIONS][obsId][cfg.FILE][player]
|
|
707
|
-
):
|
|
739
|
+
if player in self.pj[cfg.OBSERVATIONS][obsId][cfg.FILE] and self.pj[cfg.OBSERVATIONS][obsId][cfg.FILE][player]:
|
|
708
740
|
for mediaFile in self.pj[cfg.OBSERVATIONS][obsId][cfg.FILE][player]:
|
|
709
741
|
observationWindow.twVideo1.setRowCount(observationWindow.twVideo1.rowCount() + 1)
|
|
710
742
|
|
|
@@ -713,19 +745,15 @@ def new_observation(self, mode=cfg.NEW, obsId=""):
|
|
|
713
745
|
combobox.setCurrentIndex(int(player) - 1)
|
|
714
746
|
observationWindow.twVideo1.setCellWidget(observationWindow.twVideo1.rowCount() - 1, 0, combobox)
|
|
715
747
|
|
|
716
|
-
# set offset
|
|
748
|
+
# set media file offset
|
|
717
749
|
try:
|
|
718
750
|
observationWindow.twVideo1.setItem(
|
|
719
751
|
observationWindow.twVideo1.rowCount() - 1,
|
|
720
752
|
1,
|
|
721
|
-
QTableWidgetItem(
|
|
722
|
-
str(self.pj[cfg.OBSERVATIONS][obsId][cfg.MEDIA_INFO]["offset"][player])
|
|
723
|
-
),
|
|
753
|
+
QTableWidgetItem(str(self.pj[cfg.OBSERVATIONS][obsId][cfg.MEDIA_INFO]["offset"][player])),
|
|
724
754
|
)
|
|
725
755
|
except Exception:
|
|
726
|
-
observationWindow.twVideo1.setItem(
|
|
727
|
-
observationWindow.twVideo1.rowCount() - 1, 1, QTableWidgetItem("0.0")
|
|
728
|
-
)
|
|
756
|
+
observationWindow.twVideo1.setItem(observationWindow.twVideo1.rowCount() - 1, 1, QTableWidgetItem("0.0"))
|
|
729
757
|
|
|
730
758
|
item = QTableWidgetItem(mediaFile)
|
|
731
759
|
item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
|
|
@@ -734,16 +762,12 @@ def new_observation(self, mode=cfg.NEW, obsId=""):
|
|
|
734
762
|
# duration and FPS
|
|
735
763
|
try:
|
|
736
764
|
item = QTableWidgetItem(
|
|
737
|
-
util.seconds2time(
|
|
738
|
-
self.pj[cfg.OBSERVATIONS][obsId][cfg.MEDIA_INFO][cfg.LENGTH][mediaFile]
|
|
739
|
-
)
|
|
765
|
+
util.seconds2time(self.pj[cfg.OBSERVATIONS][obsId][cfg.MEDIA_INFO][cfg.LENGTH][mediaFile])
|
|
740
766
|
)
|
|
741
767
|
item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
|
|
742
768
|
observationWindow.twVideo1.setItem(observationWindow.twVideo1.rowCount() - 1, 3, item)
|
|
743
769
|
|
|
744
|
-
item = QTableWidgetItem(
|
|
745
|
-
f"{self.pj[cfg.OBSERVATIONS][obsId][cfg.MEDIA_INFO][cfg.FPS][mediaFile]:.2f}"
|
|
746
|
-
)
|
|
770
|
+
item = QTableWidgetItem(f"{self.pj[cfg.OBSERVATIONS][obsId][cfg.MEDIA_INFO][cfg.FPS][mediaFile]:.2f}")
|
|
747
771
|
item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
|
|
748
772
|
observationWindow.twVideo1.setItem(observationWindow.twVideo1.rowCount() - 1, 4, item)
|
|
749
773
|
except Exception:
|
|
@@ -751,41 +775,41 @@ def new_observation(self, mode=cfg.NEW, obsId=""):
|
|
|
751
775
|
|
|
752
776
|
# has_video has_audio
|
|
753
777
|
try:
|
|
754
|
-
item = QTableWidgetItem(
|
|
755
|
-
str(self.pj[cfg.OBSERVATIONS][obsId][cfg.MEDIA_INFO][cfg.HAS_VIDEO][mediaFile])
|
|
756
|
-
)
|
|
778
|
+
item = QTableWidgetItem(str(self.pj[cfg.OBSERVATIONS][obsId][cfg.MEDIA_INFO][cfg.HAS_VIDEO][mediaFile]))
|
|
757
779
|
item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
|
|
758
780
|
observationWindow.twVideo1.setItem(observationWindow.twVideo1.rowCount() - 1, 5, item)
|
|
759
781
|
|
|
760
|
-
item = QTableWidgetItem(
|
|
761
|
-
str(self.pj[cfg.OBSERVATIONS][obsId][cfg.MEDIA_INFO][cfg.HAS_AUDIO][mediaFile])
|
|
762
|
-
)
|
|
782
|
+
item = QTableWidgetItem(str(self.pj[cfg.OBSERVATIONS][obsId][cfg.MEDIA_INFO][cfg.HAS_AUDIO][mediaFile]))
|
|
763
783
|
item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
|
|
764
784
|
observationWindow.twVideo1.setItem(observationWindow.twVideo1.rowCount() - 1, 6, item)
|
|
765
785
|
except Exception:
|
|
766
786
|
pass
|
|
767
787
|
|
|
788
|
+
observationWindow.cbCloseCurrentBehaviorsBetweenVideo.setEnabled(observationWindow.twVideo1.rowCount() > 0)
|
|
768
789
|
# spectrogram
|
|
769
790
|
observationWindow.cbVisualizeSpectrogram.setEnabled(True)
|
|
770
|
-
observationWindow.cbVisualizeSpectrogram.setChecked(
|
|
771
|
-
self.pj[cfg.OBSERVATIONS][obsId].get(cfg.VISUALIZE_SPECTROGRAM, False)
|
|
772
|
-
)
|
|
773
|
-
|
|
791
|
+
observationWindow.cbVisualizeSpectrogram.setChecked(self.pj[cfg.OBSERVATIONS][obsId].get(cfg.VISUALIZE_SPECTROGRAM, False))
|
|
774
792
|
# waveform
|
|
775
793
|
observationWindow.cb_visualize_waveform.setEnabled(True)
|
|
776
|
-
observationWindow.cb_visualize_waveform.setChecked(
|
|
777
|
-
|
|
794
|
+
observationWindow.cb_visualize_waveform.setChecked(self.pj[cfg.OBSERVATIONS][obsId].get(cfg.VISUALIZE_WAVEFORM, False))
|
|
795
|
+
# use Creation date metadata tag as offset
|
|
796
|
+
observationWindow.cb_media_creation_date_as_offset.setEnabled(True)
|
|
797
|
+
|
|
798
|
+
# DEVELOPMENT (REMOVE BEFORE RELEASE)
|
|
799
|
+
# observationWindow.cb_media_creation_date_as_offset.setEnabled(False)
|
|
800
|
+
|
|
801
|
+
observationWindow.cb_media_creation_date_as_offset.setChecked(
|
|
802
|
+
self.pj[cfg.OBSERVATIONS][obsId].get(cfg.MEDIA_CREATION_DATE_AS_OFFSET, False)
|
|
778
803
|
)
|
|
779
804
|
|
|
805
|
+
# scan sampling
|
|
806
|
+
observationWindow.sb_media_scan_sampling.setValue(self.pj[cfg.OBSERVATIONS][obsId].get(cfg.MEDIA_SCAN_SAMPLING_DURATION, 0))
|
|
780
807
|
# image display duration
|
|
781
|
-
observationWindow.sb_image_display_duration.setValue(
|
|
782
|
-
self.pj[cfg.OBSERVATIONS][obsId].get(cfg.IMAGE_DISPLAY_DURATION, 1)
|
|
783
|
-
)
|
|
808
|
+
observationWindow.sb_image_display_duration.setValue(self.pj[cfg.OBSERVATIONS][obsId].get(cfg.IMAGE_DISPLAY_DURATION, 1))
|
|
784
809
|
|
|
785
810
|
# plot data
|
|
786
811
|
if cfg.PLOT_DATA in self.pj[cfg.OBSERVATIONS][obsId]:
|
|
787
812
|
if self.pj[cfg.OBSERVATIONS][obsId][cfg.PLOT_DATA]:
|
|
788
|
-
|
|
789
813
|
observationWindow.tw_data_files.setRowCount(0)
|
|
790
814
|
for idx2 in util.sorted_keys(self.pj[cfg.OBSERVATIONS][obsId][cfg.PLOT_DATA]):
|
|
791
815
|
observationWindow.tw_data_files.setRowCount(observationWindow.tw_data_files.rowCount() + 1)
|
|
@@ -795,9 +819,7 @@ def new_observation(self, mode=cfg.NEW, obsId=""):
|
|
|
795
819
|
combobox.addItems(cfg.DATA_PLOT_STYLES)
|
|
796
820
|
combobox.setCurrentIndex(
|
|
797
821
|
cfg.DATA_PLOT_STYLES.index(
|
|
798
|
-
self.pj[cfg.OBSERVATIONS][obsId][cfg.PLOT_DATA][idx2][
|
|
799
|
-
cfg.DATA_PLOT_FIELDS[idx3]
|
|
800
|
-
]
|
|
822
|
+
self.pj[cfg.OBSERVATIONS][obsId][cfg.PLOT_DATA][idx2][cfg.DATA_PLOT_FIELDS[idx3]]
|
|
801
823
|
)
|
|
802
824
|
)
|
|
803
825
|
|
|
@@ -811,9 +833,7 @@ def new_observation(self, mode=cfg.NEW, obsId=""):
|
|
|
811
833
|
combobox2.addItems(["False", "True"])
|
|
812
834
|
combobox2.setCurrentIndex(
|
|
813
835
|
["False", "True"].index(
|
|
814
|
-
self.pj[cfg.OBSERVATIONS][obsId][cfg.PLOT_DATA][idx2][
|
|
815
|
-
cfg.DATA_PLOT_FIELDS[idx3]
|
|
816
|
-
]
|
|
836
|
+
self.pj[cfg.OBSERVATIONS][obsId][cfg.PLOT_DATA][idx2][cfg.DATA_PLOT_FIELDS[idx3]]
|
|
817
837
|
)
|
|
818
838
|
)
|
|
819
839
|
|
|
@@ -828,11 +848,7 @@ def new_observation(self, mode=cfg.NEW, obsId=""):
|
|
|
828
848
|
observationWindow.tw_data_files.rowCount() - 1,
|
|
829
849
|
idx3,
|
|
830
850
|
QTableWidgetItem(
|
|
831
|
-
str(
|
|
832
|
-
self.pj[cfg.OBSERVATIONS][obsId][cfg.PLOT_DATA][idx2][
|
|
833
|
-
cfg.DATA_PLOT_FIELDS[idx3]
|
|
834
|
-
]
|
|
835
|
-
)
|
|
851
|
+
str(self.pj[cfg.OBSERVATIONS][obsId][cfg.PLOT_DATA][idx2][cfg.DATA_PLOT_FIELDS[idx3]])
|
|
836
852
|
),
|
|
837
853
|
)
|
|
838
854
|
|
|
@@ -840,24 +856,18 @@ def new_observation(self, mode=cfg.NEW, obsId=""):
|
|
|
840
856
|
observationWindow.tw_data_files.setItem(
|
|
841
857
|
observationWindow.tw_data_files.rowCount() - 1,
|
|
842
858
|
idx3,
|
|
843
|
-
QTableWidgetItem(
|
|
844
|
-
self.pj[cfg.OBSERVATIONS][obsId][cfg.PLOT_DATA][idx2][
|
|
845
|
-
cfg.DATA_PLOT_FIELDS[idx3]
|
|
846
|
-
]
|
|
847
|
-
),
|
|
859
|
+
QTableWidgetItem(self.pj[cfg.OBSERVATIONS][obsId][cfg.PLOT_DATA][idx2][cfg.DATA_PLOT_FIELDS[idx3]]),
|
|
848
860
|
)
|
|
849
861
|
|
|
850
862
|
if self.pj[cfg.OBSERVATIONS][obsId]["type"] == cfg.IMAGES:
|
|
851
863
|
observationWindow.rb_images.setChecked(True)
|
|
852
|
-
observationWindow.lw_images_directory.addItems(
|
|
853
|
-
self.pj[cfg.OBSERVATIONS][obsId].get(cfg.DIRECTORIES_LIST, [])
|
|
854
|
-
)
|
|
864
|
+
observationWindow.lw_images_directory.addItems(self.pj[cfg.OBSERVATIONS][obsId].get(cfg.DIRECTORIES_LIST, []))
|
|
855
865
|
observationWindow.rb_use_exif.setChecked(self.pj[cfg.OBSERVATIONS][obsId].get(cfg.USE_EXIF_DATE, False))
|
|
856
866
|
if self.pj[cfg.OBSERVATIONS][obsId].get(cfg.TIME_LAPSE, 0):
|
|
857
867
|
observationWindow.rb_time_lapse.setChecked(True)
|
|
858
868
|
observationWindow.sb_time_lapse.setValue(self.pj[cfg.OBSERVATIONS][obsId].get(cfg.TIME_LAPSE, 0))
|
|
859
869
|
|
|
860
|
-
if self.pj[cfg.OBSERVATIONS][obsId]["type"]
|
|
870
|
+
if self.pj[cfg.OBSERVATIONS][obsId]["type"] == cfg.LIVE:
|
|
861
871
|
observationWindow.rb_live.setChecked(True)
|
|
862
872
|
# sampling time
|
|
863
873
|
observationWindow.sbScanSampling.setValue(self.pj[cfg.OBSERVATIONS][obsId].get(cfg.SCAN_SAMPLING_TIME, 0))
|
|
@@ -867,39 +877,33 @@ def new_observation(self, mode=cfg.NEW, obsId=""):
|
|
|
867
877
|
or self.pj[cfg.OBSERVATIONS][obsId].get(cfg.START_FROM_CURRENT_EPOCH_TIME, False)
|
|
868
878
|
)
|
|
869
879
|
# day/epoch time
|
|
870
|
-
observationWindow.rb_day_time.setChecked(
|
|
871
|
-
|
|
872
|
-
)
|
|
873
|
-
observationWindow.rb_epoch_time.setChecked(
|
|
874
|
-
self.pj[cfg.OBSERVATIONS][obsId].get(cfg.START_FROM_CURRENT_EPOCH_TIME, False)
|
|
875
|
-
)
|
|
880
|
+
observationWindow.rb_day_time.setChecked(self.pj[cfg.OBSERVATIONS][obsId].get(cfg.START_FROM_CURRENT_TIME, False))
|
|
881
|
+
observationWindow.rb_epoch_time.setChecked(self.pj[cfg.OBSERVATIONS][obsId].get(cfg.START_FROM_CURRENT_EPOCH_TIME, False))
|
|
876
882
|
|
|
877
883
|
# observation time interval
|
|
878
884
|
observationWindow.cb_observation_time_interval.setEnabled(True)
|
|
879
885
|
if self.pj[cfg.OBSERVATIONS][obsId].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0]) != [0, 0]:
|
|
880
886
|
observationWindow.cb_observation_time_interval.setChecked(True)
|
|
881
|
-
observationWindow.observation_time_interval = self.pj[cfg.OBSERVATIONS][obsId].get(
|
|
882
|
-
cfg.OBSERVATION_TIME_INTERVAL, [0, 0]
|
|
883
|
-
)
|
|
887
|
+
observationWindow.observation_time_interval = self.pj[cfg.OBSERVATIONS][obsId].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0])
|
|
884
888
|
observationWindow.cb_observation_time_interval.setText(
|
|
885
889
|
(
|
|
886
890
|
"Limit observation to a time interval: "
|
|
887
|
-
f"{self.pj[cfg.OBSERVATIONS][obsId]
|
|
888
|
-
f"{self.pj[cfg.OBSERVATIONS][obsId]
|
|
891
|
+
f"{self.pj[cfg.OBSERVATIONS][obsId].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0])[0]} - "
|
|
892
|
+
f"{self.pj[cfg.OBSERVATIONS][obsId].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0])[1]}"
|
|
889
893
|
)
|
|
890
894
|
)
|
|
891
895
|
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
896
|
+
if cfg.CLOSE_BEHAVIORS_BETWEEN_VIDEOS in self.pj[cfg.OBSERVATIONS][obsId]:
|
|
897
|
+
observationWindow.cbCloseCurrentBehaviorsBetweenVideo.setChecked(
|
|
898
|
+
self.pj[cfg.OBSERVATIONS][obsId][cfg.CLOSE_BEHAVIORS_BETWEEN_VIDEOS]
|
|
899
|
+
)
|
|
895
900
|
|
|
896
|
-
rv = observationWindow.
|
|
901
|
+
rv = observationWindow.exec()
|
|
897
902
|
|
|
898
903
|
# save geometry
|
|
899
904
|
gui_utilities.save_geometry(observationWindow, "new observation")
|
|
900
905
|
|
|
901
906
|
if rv:
|
|
902
|
-
|
|
903
907
|
self.project_changed()
|
|
904
908
|
|
|
905
909
|
new_obs_id = observationWindow.leObservationId.text().strip()
|
|
@@ -918,14 +922,14 @@ def new_observation(self, mode=cfg.NEW, obsId=""):
|
|
|
918
922
|
|
|
919
923
|
# check if id changed
|
|
920
924
|
if mode == cfg.EDIT and new_obs_id != obsId:
|
|
921
|
-
|
|
922
925
|
logging.info(f"observation id {obsId} changed in {new_obs_id}")
|
|
923
926
|
|
|
924
927
|
self.pj[cfg.OBSERVATIONS][new_obs_id] = dict(self.pj[cfg.OBSERVATIONS][obsId])
|
|
925
928
|
del self.pj[cfg.OBSERVATIONS][obsId]
|
|
926
929
|
|
|
927
930
|
# observation date
|
|
928
|
-
self.pj[cfg.OBSERVATIONS][new_obs_id]["date"] = observationWindow.dteDate.dateTime().toString(
|
|
931
|
+
self.pj[cfg.OBSERVATIONS][new_obs_id]["date"] = observationWindow.dteDate.dateTime().toString("yyyy-MM-ddTHH:mm:ss.zzz")
|
|
932
|
+
# observation description
|
|
929
933
|
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.DESCRIPTION] = observationWindow.teDescription.toPlainText()
|
|
930
934
|
|
|
931
935
|
# observation type: read project type from radio buttons
|
|
@@ -939,48 +943,53 @@ def new_observation(self, mode=cfg.NEW, obsId=""):
|
|
|
939
943
|
# independent variables for observation
|
|
940
944
|
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.INDEPENDENT_VARIABLES] = {}
|
|
941
945
|
for r in range(observationWindow.twIndepVariables.rowCount()):
|
|
942
|
-
|
|
943
946
|
# set dictionary as label (col 0) => value (col 2)
|
|
944
947
|
if observationWindow.twIndepVariables.item(r, 1).text() == cfg.SET_OF_VALUES:
|
|
945
|
-
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.INDEPENDENT_VARIABLES][
|
|
946
|
-
observationWindow.twIndepVariables.
|
|
947
|
-
|
|
948
|
+
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.INDEPENDENT_VARIABLES][observationWindow.twIndepVariables.item(r, 0).text()] = (
|
|
949
|
+
observationWindow.twIndepVariables.cellWidget(r, 2).currentText()
|
|
950
|
+
)
|
|
948
951
|
elif observationWindow.twIndepVariables.item(r, 1).text() == cfg.TIMESTAMP:
|
|
949
|
-
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.INDEPENDENT_VARIABLES][
|
|
950
|
-
observationWindow.twIndepVariables.
|
|
951
|
-
|
|
952
|
+
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.INDEPENDENT_VARIABLES][observationWindow.twIndepVariables.item(r, 0).text()] = (
|
|
953
|
+
observationWindow.twIndepVariables.cellWidget(r, 2).dateTime().toString(Qt.ISODate)
|
|
954
|
+
)
|
|
952
955
|
else:
|
|
953
|
-
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.INDEPENDENT_VARIABLES][
|
|
954
|
-
observationWindow.twIndepVariables.item(r,
|
|
955
|
-
|
|
956
|
+
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.INDEPENDENT_VARIABLES][observationWindow.twIndepVariables.item(r, 0).text()] = (
|
|
957
|
+
observationWindow.twIndepVariables.item(r, 2).text()
|
|
958
|
+
)
|
|
956
959
|
|
|
957
960
|
# observation time offset
|
|
958
|
-
|
|
961
|
+
if observationWindow.cb_time_offset.isChecked():
|
|
962
|
+
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.TIME_OFFSET] = observationWindow.obs_time_offset.get_time()
|
|
963
|
+
else:
|
|
964
|
+
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.TIME_OFFSET] = dec("0.0")
|
|
965
|
+
|
|
966
|
+
# add date (epoch) if date offset checked
|
|
967
|
+
# if observationWindow.cb_date_offset.isChecked():
|
|
968
|
+
# print(f"{observationWindow.de_date_offset.date().toString(Qt.ISODate)=}")
|
|
969
|
+
# date_timestamp = dec(dt.datetime.strptime(observationWindow.de_date_offset.date().toString(Qt.ISODate), "%Y-%m-%d").timestamp())
|
|
970
|
+
# self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.TIME_OFFSET] += date_timestamp
|
|
959
971
|
|
|
960
972
|
if observationWindow.cb_observation_time_interval.isChecked():
|
|
961
|
-
self.pj[cfg.OBSERVATIONS][new_obs_id][
|
|
962
|
-
cfg.OBSERVATION_TIME_INTERVAL
|
|
963
|
-
] = observationWindow.observation_time_interval
|
|
973
|
+
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.OBSERVATION_TIME_INTERVAL] = observationWindow.observation_time_interval
|
|
964
974
|
|
|
965
975
|
self.display_statusbar_info(new_obs_id)
|
|
966
976
|
|
|
967
977
|
# visualize spectrogram
|
|
968
|
-
self.pj[cfg.OBSERVATIONS][new_obs_id][
|
|
969
|
-
|
|
970
|
-
] = observationWindow.
|
|
971
|
-
#
|
|
972
|
-
self.pj[cfg.OBSERVATIONS][new_obs_id][
|
|
973
|
-
|
|
974
|
-
|
|
978
|
+
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.VISUALIZE_SPECTROGRAM] = observationWindow.cbVisualizeSpectrogram.isChecked()
|
|
979
|
+
# visualize waveform
|
|
980
|
+
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.VISUALIZE_WAVEFORM] = observationWindow.cb_visualize_waveform.isChecked()
|
|
981
|
+
# use Creation date metadata tag as offset
|
|
982
|
+
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.MEDIA_CREATION_DATE_AS_OFFSET] = (
|
|
983
|
+
observationWindow.cb_media_creation_date_as_offset.isChecked()
|
|
984
|
+
)
|
|
985
|
+
|
|
986
|
+
# media scan sampling
|
|
987
|
+
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.MEDIA_SCAN_SAMPLING_DURATION] = observationWindow.sb_media_scan_sampling.value()
|
|
975
988
|
# image display duration
|
|
976
|
-
self.pj[cfg.OBSERVATIONS][new_obs_id][
|
|
977
|
-
cfg.IMAGE_DISPLAY_DURATION
|
|
978
|
-
] = observationWindow.sb_image_display_duration.value()
|
|
989
|
+
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.IMAGE_DISPLAY_DURATION] = observationWindow.sb_image_display_duration.value()
|
|
979
990
|
|
|
980
991
|
# time interval for observation
|
|
981
|
-
self.pj[cfg.OBSERVATIONS][new_obs_id][
|
|
982
|
-
cfg.OBSERVATION_TIME_INTERVAL
|
|
983
|
-
] = observationWindow.observation_time_interval
|
|
992
|
+
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.OBSERVATION_TIME_INTERVAL] = observationWindow.observation_time_interval
|
|
984
993
|
|
|
985
994
|
# plot data
|
|
986
995
|
if observationWindow.tw_data_files.rowCount():
|
|
@@ -989,30 +998,27 @@ def new_observation(self, mode=cfg.NEW, obsId=""):
|
|
|
989
998
|
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.PLOT_DATA][str(row)] = {}
|
|
990
999
|
for idx2 in cfg.DATA_PLOT_FIELDS:
|
|
991
1000
|
if idx2 in [cfg.PLOT_DATA_PLOTCOLOR_IDX, cfg.PLOT_DATA_SUBSTRACT1STVALUE_IDX]:
|
|
992
|
-
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.PLOT_DATA][str(row)][
|
|
993
|
-
|
|
994
|
-
|
|
1001
|
+
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.PLOT_DATA][str(row)][cfg.DATA_PLOT_FIELDS[idx2]] = (
|
|
1002
|
+
observationWindow.tw_data_files.cellWidget(row, idx2).currentText()
|
|
1003
|
+
)
|
|
995
1004
|
|
|
996
1005
|
elif idx2 == cfg.PLOT_DATA_CONVERTERS_IDX:
|
|
997
1006
|
if observationWindow.tw_data_files.item(row, idx2).text():
|
|
998
|
-
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.PLOT_DATA][str(row)][
|
|
999
|
-
|
|
1000
|
-
|
|
1007
|
+
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.PLOT_DATA][str(row)][cfg.DATA_PLOT_FIELDS[idx2]] = eval(
|
|
1008
|
+
observationWindow.tw_data_files.item(row, idx2).text()
|
|
1009
|
+
)
|
|
1001
1010
|
else:
|
|
1002
|
-
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.PLOT_DATA][str(row)][
|
|
1003
|
-
cfg.DATA_PLOT_FIELDS[idx2]
|
|
1004
|
-
] = {}
|
|
1011
|
+
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.PLOT_DATA][str(row)][cfg.DATA_PLOT_FIELDS[idx2]] = {}
|
|
1005
1012
|
|
|
1006
1013
|
else:
|
|
1007
|
-
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.PLOT_DATA][str(row)][
|
|
1008
|
-
|
|
1009
|
-
|
|
1014
|
+
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.PLOT_DATA][str(row)][cfg.DATA_PLOT_FIELDS[idx2]] = (
|
|
1015
|
+
observationWindow.tw_data_files.item(row, idx2).text()
|
|
1016
|
+
)
|
|
1010
1017
|
|
|
1011
1018
|
# Close current behaviors between video
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.CLOSE_BEHAVIORS_BETWEEN_VIDEOS] = False
|
|
1019
|
+
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.CLOSE_BEHAVIORS_BETWEEN_VIDEOS] = (
|
|
1020
|
+
observationWindow.cbCloseCurrentBehaviorsBetweenVideo.isChecked()
|
|
1021
|
+
)
|
|
1016
1022
|
|
|
1017
1023
|
if self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.TYPE] == cfg.LIVE:
|
|
1018
1024
|
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.SCAN_SAMPLING_TIME] = observationWindow.sbScanSampling.value()
|
|
@@ -1026,10 +1032,23 @@ def new_observation(self, mode=cfg.NEW, obsId=""):
|
|
|
1026
1032
|
# images dir
|
|
1027
1033
|
if self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.TYPE] == cfg.IMAGES:
|
|
1028
1034
|
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.DIRECTORIES_LIST] = [
|
|
1029
|
-
observationWindow.lw_images_directory.item(i).text()
|
|
1030
|
-
for i in range(observationWindow.lw_images_directory.count())
|
|
1035
|
+
observationWindow.lw_images_directory.item(i).text() for i in range(observationWindow.lw_images_directory.count())
|
|
1031
1036
|
]
|
|
1037
|
+
|
|
1038
|
+
# check if exif data must be used
|
|
1032
1039
|
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.USE_EXIF_DATE] = observationWindow.rb_use_exif.isChecked()
|
|
1040
|
+
|
|
1041
|
+
# ask if the value of the exif date time of the first picture must be substracted
|
|
1042
|
+
# TODO: improve this
|
|
1043
|
+
if self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.USE_EXIF_DATE]:
|
|
1044
|
+
response = dialog.MessageDialog(
|
|
1045
|
+
cfg.programName,
|
|
1046
|
+
"You choose to use the EXIF metadata. Do you want to substract the date time value of the first picture?",
|
|
1047
|
+
(cfg.YES, cfg.NO),
|
|
1048
|
+
)
|
|
1049
|
+
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.SUBSTRACT_FIRST_EXIF_DATE] = response == cfg.YES
|
|
1050
|
+
|
|
1051
|
+
# check if time lapse
|
|
1033
1052
|
if observationWindow.rb_time_lapse.isChecked():
|
|
1034
1053
|
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.TIME_LAPSE] = observationWindow.sb_time_lapse.value()
|
|
1035
1054
|
else:
|
|
@@ -1040,17 +1059,19 @@ def new_observation(self, mode=cfg.NEW, obsId=""):
|
|
|
1040
1059
|
|
|
1041
1060
|
# media
|
|
1042
1061
|
if self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.TYPE] == cfg.MEDIA:
|
|
1043
|
-
|
|
1044
1062
|
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.MEDIA_INFO] = {
|
|
1045
1063
|
cfg.LENGTH: observationWindow.mediaDurations,
|
|
1046
1064
|
cfg.FPS: observationWindow.mediaFPS,
|
|
1047
1065
|
}
|
|
1048
1066
|
|
|
1067
|
+
if self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.MEDIA_CREATION_DATE_AS_OFFSET]:
|
|
1068
|
+
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.MEDIA_INFO][cfg.MEDIA_CREATION_TIME] = observationWindow.media_creation_time
|
|
1069
|
+
|
|
1049
1070
|
try:
|
|
1050
1071
|
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.MEDIA_INFO][cfg.HAS_VIDEO] = observationWindow.mediaHasVideo
|
|
1051
1072
|
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.MEDIA_INFO][cfg.HAS_AUDIO] = observationWindow.mediaHasAudio
|
|
1052
1073
|
except Exception:
|
|
1053
|
-
logging.
|
|
1074
|
+
logging.warning("error with media_info information")
|
|
1054
1075
|
|
|
1055
1076
|
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.MEDIA_INFO]["offset"] = {}
|
|
1056
1077
|
|
|
@@ -1060,9 +1081,9 @@ def new_observation(self, mode=cfg.NEW, obsId=""):
|
|
|
1060
1081
|
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.FILE][str(i + 1)] = []
|
|
1061
1082
|
|
|
1062
1083
|
for row in range(observationWindow.twVideo1.rowCount()):
|
|
1063
|
-
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.FILE][
|
|
1064
|
-
observationWindow.twVideo1.
|
|
1065
|
-
|
|
1084
|
+
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.FILE][observationWindow.twVideo1.cellWidget(row, 0).currentText()].append(
|
|
1085
|
+
observationWindow.twVideo1.item(row, 2).text()
|
|
1086
|
+
)
|
|
1066
1087
|
# store offset for media player
|
|
1067
1088
|
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.MEDIA_INFO]["offset"][
|
|
1068
1089
|
observationWindow.twVideo1.cellWidget(row, 0).currentText()
|
|
@@ -1084,11 +1105,11 @@ def new_observation(self, mode=cfg.NEW, obsId=""):
|
|
|
1084
1105
|
|
|
1085
1106
|
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.MEDIA:
|
|
1086
1107
|
self.playerType = cfg.MEDIA
|
|
1087
|
-
|
|
1088
|
-
|
|
1108
|
+
if not initialize_new_media_observation(self):
|
|
1109
|
+
close_observation(self)
|
|
1110
|
+
return "Observation not launched"
|
|
1089
1111
|
|
|
1090
1112
|
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.IMAGES:
|
|
1091
|
-
# QMessageBox.critical(self, cfg.programName, "Observation from images directory is not yet implemented")
|
|
1092
1113
|
initialize_new_images_observation(self)
|
|
1093
1114
|
|
|
1094
1115
|
self.load_tw_events(self.observationId)
|
|
@@ -1100,10 +1121,10 @@ def close_observation(self):
|
|
|
1100
1121
|
close current observation
|
|
1101
1122
|
"""
|
|
1102
1123
|
|
|
1103
|
-
logging.info(f"Close observation {self.playerType}")
|
|
1124
|
+
logging.info(f"Close observation (player type: {self.playerType})")
|
|
1125
|
+
|
|
1126
|
+
# check observation state events
|
|
1104
1127
|
|
|
1105
|
-
logging.info(f"Check state events")
|
|
1106
|
-
# check observation events
|
|
1107
1128
|
flag_ok, msg = project_functions.check_state_events_obs(
|
|
1108
1129
|
self.observationId,
|
|
1109
1130
|
self.pj[cfg.ETHOGRAM],
|
|
@@ -1112,58 +1133,35 @@ def close_observation(self):
|
|
|
1112
1133
|
)
|
|
1113
1134
|
|
|
1114
1135
|
if not flag_ok:
|
|
1115
|
-
|
|
1116
1136
|
out = f"The current observation has state event(s) that are not PAIRED:<br><br>{msg}"
|
|
1117
|
-
results = dialog.
|
|
1137
|
+
results = dialog.Results_dialog_exit_code()
|
|
1118
1138
|
results.setWindowTitle(f"{cfg.programName} - Check selected observations")
|
|
1119
1139
|
results.ptText.setReadOnly(True)
|
|
1120
1140
|
results.ptText.appendHtml(out)
|
|
1121
|
-
results.pbSave.setVisible(False)
|
|
1122
|
-
results.pbCancel.setText("Close observation")
|
|
1123
|
-
results.pbCancel.setVisible(True)
|
|
1124
|
-
results.pbOK.setText("Fix unpaired state events")
|
|
1125
|
-
|
|
1126
|
-
if results.exec_(): # fix events
|
|
1127
|
-
|
|
1128
|
-
w = dialog.Ask_time(self.timeFormat)
|
|
1129
|
-
w.setWindowTitle("Fix UNPAIRED state events")
|
|
1130
|
-
w.label.setText("Fix UNPAIRED events at time")
|
|
1131
|
-
|
|
1132
|
-
if w.exec_():
|
|
1133
|
-
fix_at_time = w.time_widget.get_time()
|
|
1134
|
-
events_to_add = project_functions.fix_unpaired_state_events(
|
|
1135
|
-
self.pj[cfg.ETHOGRAM],
|
|
1136
|
-
self.pj[cfg.OBSERVATIONS][self.observationId],
|
|
1137
|
-
fix_at_time - dec("0.001"),
|
|
1138
|
-
)
|
|
1139
|
-
if events_to_add:
|
|
1140
|
-
self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS].extend(events_to_add)
|
|
1141
|
-
self.project_changed()
|
|
1142
|
-
self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS].sort()
|
|
1143
|
-
|
|
1144
|
-
self.load_tw_events(self.observationId)
|
|
1145
|
-
item = self.twEvents.item(
|
|
1146
|
-
[
|
|
1147
|
-
i
|
|
1148
|
-
for i, t in enumerate(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS])
|
|
1149
|
-
if t[0] == fix_at_time
|
|
1150
|
-
][0],
|
|
1151
|
-
0,
|
|
1152
|
-
)
|
|
1153
|
-
self.twEvents.scrollToItem(item)
|
|
1154
|
-
return
|
|
1155
|
-
else:
|
|
1156
|
-
return
|
|
1157
1141
|
|
|
1158
|
-
|
|
1142
|
+
results.pb1.setText("Close observation")
|
|
1143
|
+
results.pb2.setText("Return to observation")
|
|
1144
|
+
if self.playerType == cfg.IMAGES:
|
|
1145
|
+
results.pb3.setVisible(False)
|
|
1146
|
+
else:
|
|
1147
|
+
results.pb3.setText("Fix unpaired state events")
|
|
1148
|
+
|
|
1149
|
+
r = results.exec()
|
|
1150
|
+
if r == 2: # Return to observation
|
|
1151
|
+
return
|
|
1152
|
+
if r == 3: # Fix unpaired state events
|
|
1153
|
+
state_events.fix_unpaired_events(self, silent_mode=True)
|
|
1159
1154
|
|
|
1160
1155
|
self.saved_state = self.saveState()
|
|
1161
1156
|
|
|
1162
1157
|
if self.playerType == cfg.MEDIA:
|
|
1163
|
-
|
|
1164
|
-
logging.info(
|
|
1158
|
+
self.media_scan_sampling_mem = []
|
|
1159
|
+
logging.info("Stop plot timer")
|
|
1165
1160
|
self.plot_timer.stop()
|
|
1166
1161
|
|
|
1162
|
+
if self.MPV_IPC_MODE:
|
|
1163
|
+
self.main_window_activation_timer.stop()
|
|
1164
|
+
|
|
1167
1165
|
for i, player in enumerate(self.dw_player):
|
|
1168
1166
|
if (
|
|
1169
1167
|
str(i + 1) in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE]
|
|
@@ -1172,6 +1170,16 @@ def close_observation(self):
|
|
|
1172
1170
|
logging.info(f"Stop player #{i + 1}")
|
|
1173
1171
|
player.player.stop()
|
|
1174
1172
|
|
|
1173
|
+
if self.MPV_IPC_MODE:
|
|
1174
|
+
try:
|
|
1175
|
+
player.player.process.terminate()
|
|
1176
|
+
try:
|
|
1177
|
+
player.player.process.wait(timeout=3) # wait up to 3s
|
|
1178
|
+
except subprocess.TimeoutExpired:
|
|
1179
|
+
player.player.process.kill() # force if still alive
|
|
1180
|
+
except Exception as e:
|
|
1181
|
+
logging.warning(f"Error stopping MPV process #{i}: {e}")
|
|
1182
|
+
|
|
1175
1183
|
self.verticalLayout_3.removeWidget(self.video_slider)
|
|
1176
1184
|
|
|
1177
1185
|
if self.video_slider is not None:
|
|
@@ -1186,21 +1194,22 @@ def close_observation(self):
|
|
|
1186
1194
|
self.liveObservationStarted = False
|
|
1187
1195
|
self.liveStartTime = None
|
|
1188
1196
|
|
|
1189
|
-
if
|
|
1190
|
-
cfg.PLOT_DATA in self.pj[cfg.OBSERVATIONS][self.observationId]
|
|
1191
|
-
and self.pj[cfg.OBSERVATIONS][self.observationId][cfg.PLOT_DATA]
|
|
1192
|
-
):
|
|
1197
|
+
if cfg.PLOT_DATA in self.pj[cfg.OBSERVATIONS][self.observationId] and self.pj[cfg.OBSERVATIONS][self.observationId][cfg.PLOT_DATA]:
|
|
1193
1198
|
for x in self.ext_data_timer_list:
|
|
1194
1199
|
x.stop()
|
|
1195
1200
|
for pd in self.plot_data:
|
|
1196
1201
|
self.plot_data[pd].close_plot()
|
|
1197
1202
|
|
|
1198
|
-
logging.info(
|
|
1203
|
+
logging.info("close tool window")
|
|
1199
1204
|
|
|
1200
1205
|
self.close_tool_windows()
|
|
1201
1206
|
|
|
1202
1207
|
self.observationId = ""
|
|
1203
1208
|
|
|
1209
|
+
# delete undo queue
|
|
1210
|
+
self.undo_queue = deque()
|
|
1211
|
+
self.undo_description = deque()
|
|
1212
|
+
|
|
1204
1213
|
if self.playerType in (cfg.MEDIA, cfg.IMAGES):
|
|
1205
1214
|
"""
|
|
1206
1215
|
for idx, _ in enumerate(self.dw_player):
|
|
@@ -1211,10 +1220,11 @@ def close_observation(self):
|
|
|
1211
1220
|
"""
|
|
1212
1221
|
|
|
1213
1222
|
for dw in self.dw_player:
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1223
|
+
logging.info("remove dock widget")
|
|
1224
|
+
dw.player.log_handler = None
|
|
1217
1225
|
self.removeDockWidget(dw)
|
|
1226
|
+
|
|
1227
|
+
del dw
|
|
1218
1228
|
# sip.delete(dw)
|
|
1219
1229
|
# dw = None
|
|
1220
1230
|
|
|
@@ -1226,10 +1236,12 @@ def close_observation(self):
|
|
|
1226
1236
|
|
|
1227
1237
|
self.w_obs_info.setVisible(False)
|
|
1228
1238
|
|
|
1229
|
-
self.twEvents.setRowCount(0)
|
|
1239
|
+
# self.twEvents.setRowCount(0)
|
|
1230
1240
|
|
|
1231
1241
|
self.lb_current_media_time.clear()
|
|
1232
1242
|
self.lb_player_status.clear()
|
|
1243
|
+
self.lb_video_info.clear()
|
|
1244
|
+
self.lb_zoom_level.clear()
|
|
1233
1245
|
|
|
1234
1246
|
self.currentSubject = ""
|
|
1235
1247
|
self.lbFocalSubject.setText(cfg.NO_FOCAL_SUBJECT)
|
|
@@ -1238,7 +1250,7 @@ def close_observation(self):
|
|
|
1238
1250
|
for i in range(self.twSubjects.rowCount()):
|
|
1239
1251
|
self.twSubjects.item(i, len(cfg.subjectsFields)).setText("")
|
|
1240
1252
|
|
|
1241
|
-
for w in
|
|
1253
|
+
for w in (self.lbTimeOffset, self.lb_obs_time_interval):
|
|
1242
1254
|
w.clear()
|
|
1243
1255
|
self.play_rate, self.playerType = 1, ""
|
|
1244
1256
|
|
|
@@ -1247,6 +1259,70 @@ def close_observation(self):
|
|
|
1247
1259
|
logging.info(f"Observation {self.playerType} closed")
|
|
1248
1260
|
|
|
1249
1261
|
|
|
1262
|
+
def check_creation_date(self) -> Tuple[int, dict]:
|
|
1263
|
+
"""
|
|
1264
|
+
check if media file exists
|
|
1265
|
+
check if Creation Date tag is present in metadata of media file
|
|
1266
|
+
|
|
1267
|
+
Returns:
|
|
1268
|
+
int: 0 if OK else error code: 1 -> media file date not used, 2 -> media file not found
|
|
1269
|
+
|
|
1270
|
+
"""
|
|
1271
|
+
|
|
1272
|
+
not_tagged_media_list: list = []
|
|
1273
|
+
media_creation_time: dict = {}
|
|
1274
|
+
|
|
1275
|
+
for nplayer in cfg.ALL_PLAYERS:
|
|
1276
|
+
if nplayer in self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.FILE, {}):
|
|
1277
|
+
for media_file in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE][nplayer]:
|
|
1278
|
+
media_path = project_functions.full_path(media_file, self.projectFileName)
|
|
1279
|
+
media_info = util.accurate_media_analysis(self.ffmpeg_bin, media_path)
|
|
1280
|
+
|
|
1281
|
+
if cfg.MEDIA_CREATION_TIME not in media_info or media_info[cfg.MEDIA_CREATION_TIME] == cfg.NA:
|
|
1282
|
+
not_tagged_media_list.append(media_path)
|
|
1283
|
+
else:
|
|
1284
|
+
creation_time_epoch = int(dt.datetime.strptime(media_info[cfg.MEDIA_CREATION_TIME], "%Y-%m-%d %H:%M:%S").timestamp())
|
|
1285
|
+
media_creation_time[media_path] = creation_time_epoch
|
|
1286
|
+
|
|
1287
|
+
"""
|
|
1288
|
+
for row in range(self.twVideo1.rowCount()):
|
|
1289
|
+
if self.twVideo1.item(row, 2).text() not in media_not_found_list:
|
|
1290
|
+
media_info = util.accurate_media_analysis(self.ffmpeg_bin, self.twVideo1.item(row, 2).text())
|
|
1291
|
+
if cfg.MEDIA_CREATION_TIME not in media_info or media_info[cfg.MEDIA_CREATION_TIME] == cfg.NA:
|
|
1292
|
+
not_tagged_media_list.append(self.twVideo1.item(row, 2).text())
|
|
1293
|
+
else:
|
|
1294
|
+
creation_time_epoch = int(dt.datetime.strptime(media_info[cfg.MEDIA_CREATION_TIME], "%Y-%m-%d %H:%M:%S").timestamp())
|
|
1295
|
+
self.media_creation_time[self.twVideo1.item(row, 2).text()] = creation_time_epoch
|
|
1296
|
+
"""
|
|
1297
|
+
|
|
1298
|
+
if not_tagged_media_list:
|
|
1299
|
+
dlg = dialog.Results_dialog()
|
|
1300
|
+
dlg.setWindowTitle("BORIS")
|
|
1301
|
+
dlg.pbOK.setText("Yes")
|
|
1302
|
+
dlg.pbCancel.setVisible(True)
|
|
1303
|
+
dlg.pbCancel.setText("No")
|
|
1304
|
+
|
|
1305
|
+
dlg.ptText.clear()
|
|
1306
|
+
dlg.ptText.appendHtml(
|
|
1307
|
+
(
|
|
1308
|
+
"Some media file does not contain the <b>Creation date/time</b> metadata tag:<br>"
|
|
1309
|
+
f"{'<br>'.join(not_tagged_media_list)}<br><br>"
|
|
1310
|
+
"Use the media file date/time instead?"
|
|
1311
|
+
)
|
|
1312
|
+
)
|
|
1313
|
+
dlg.ptText.moveCursor(QTextCursor.Start)
|
|
1314
|
+
ret = dlg.exec_()
|
|
1315
|
+
|
|
1316
|
+
if ret == 1: # use file creation time
|
|
1317
|
+
for media in not_tagged_media_list:
|
|
1318
|
+
media_creation_time[media] = pl.Path(media).stat().st_ctime
|
|
1319
|
+
return (0, media_creation_time) # OK use media file creation date/time
|
|
1320
|
+
else:
|
|
1321
|
+
return (1, {})
|
|
1322
|
+
else:
|
|
1323
|
+
return (0, media_creation_time) # OK all media have a 'creation time' tag
|
|
1324
|
+
|
|
1325
|
+
|
|
1250
1326
|
def initialize_new_media_observation(self) -> bool:
|
|
1251
1327
|
"""
|
|
1252
1328
|
initialize new observation from media file(s)
|
|
@@ -1254,12 +1330,10 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
1254
1330
|
|
|
1255
1331
|
logging.debug("function: initialize new observation for media file(s)")
|
|
1256
1332
|
|
|
1257
|
-
for dw in
|
|
1333
|
+
for dw in (self.dwEthogram, self.dwSubjects, self.dwEvents):
|
|
1258
1334
|
dw.setVisible(True)
|
|
1259
1335
|
|
|
1260
|
-
ok, msg = project_functions.check_if_media_available(
|
|
1261
|
-
self.pj[cfg.OBSERVATIONS][self.observationId], self.projectFileName
|
|
1262
|
-
)
|
|
1336
|
+
ok, msg = project_functions.check_if_media_available(self.pj[cfg.OBSERVATIONS][self.observationId], self.projectFileName)
|
|
1263
1337
|
|
|
1264
1338
|
if not ok:
|
|
1265
1339
|
QMessageBox.critical(
|
|
@@ -1287,6 +1361,8 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
1287
1361
|
font = QFont()
|
|
1288
1362
|
font.setPointSize(15)
|
|
1289
1363
|
self.lb_current_media_time.setFont(font)
|
|
1364
|
+
self.lb_video_info.setFont(font)
|
|
1365
|
+
self.lb_zoom_level.setFont(font)
|
|
1290
1366
|
|
|
1291
1367
|
# initialize video slider
|
|
1292
1368
|
self.video_slider = QSlider(Qt.Horizontal, self)
|
|
@@ -1298,9 +1374,20 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
1298
1374
|
|
|
1299
1375
|
# add all media files to media lists
|
|
1300
1376
|
self.setDockOptions(QMainWindow.AnimatedDocks | QMainWindow.AllowNestedDocks)
|
|
1301
|
-
self.dw_player
|
|
1302
|
-
|
|
1377
|
+
self.dw_player = []
|
|
1378
|
+
|
|
1379
|
+
# check if media creation time used as offset
|
|
1380
|
+
# TODO check if cfg.MEDIA_CREATION_TIME dict is present
|
|
1381
|
+
"""
|
|
1382
|
+
if self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.MEDIA_CREATION_DATE_AS_OFFSET, False):
|
|
1383
|
+
r, media_creation_time = check_creation_date(self)
|
|
1303
1384
|
|
|
1385
|
+
if r:
|
|
1386
|
+
return False
|
|
1387
|
+
self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.MEDIA_CREATION_TIME] = dict(media_creation_time)
|
|
1388
|
+
"""
|
|
1389
|
+
|
|
1390
|
+
# create dock widgets for players
|
|
1304
1391
|
for i in range(cfg.N_PLAYER):
|
|
1305
1392
|
n_player = str(i + 1)
|
|
1306
1393
|
if (
|
|
@@ -1309,17 +1396,424 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
1309
1396
|
):
|
|
1310
1397
|
continue
|
|
1311
1398
|
|
|
1399
|
+
# Not pretty but the unique solution I have found to capture the click signal for each player
|
|
1400
|
+
|
|
1312
1401
|
if i == 0: # first player
|
|
1313
|
-
|
|
1314
|
-
self.dw_player.append(p)
|
|
1402
|
+
p0 = player_dock_widget.DW_player(0, self)
|
|
1315
1403
|
|
|
1316
|
-
|
|
1317
|
-
def time_observer(_name, value):
|
|
1318
|
-
if value is not None:
|
|
1319
|
-
self.time_observer_signal.emit(value)
|
|
1404
|
+
if not self.MPV_IPC_MODE:
|
|
1320
1405
|
|
|
1321
|
-
|
|
1322
|
-
|
|
1406
|
+
@p0.player.property_observer("time-pos")
|
|
1407
|
+
def time_observer(_name, value):
|
|
1408
|
+
if value is not None:
|
|
1409
|
+
self.time_observer_signal.emit(value)
|
|
1410
|
+
|
|
1411
|
+
@p0.player.property_observer("eof-reached")
|
|
1412
|
+
def eof_reached(_name, value):
|
|
1413
|
+
if value is not None:
|
|
1414
|
+
self.mpv_eof_reached_signal.emit(value)
|
|
1415
|
+
|
|
1416
|
+
@p0.player.on_key_press("MBTN_LEFT")
|
|
1417
|
+
def mbtn_left0():
|
|
1418
|
+
self.video_click_signal.emit(0, "MBTN_LEFT")
|
|
1419
|
+
|
|
1420
|
+
@p0.player.on_key_press("MBTN_RIGHT")
|
|
1421
|
+
def mbtn_right0():
|
|
1422
|
+
self.video_click_signal.emit(0, "MBTN_RIGHT")
|
|
1423
|
+
|
|
1424
|
+
@p0.player.on_key_press("MBTN_LEFT_DBL")
|
|
1425
|
+
def mbtn_left_dbl0():
|
|
1426
|
+
self.video_click_signal.emit(0, "MBTN_LEFT_DBL")
|
|
1427
|
+
|
|
1428
|
+
@p0.player.on_key_press("MBTN_RIGHT_DBL")
|
|
1429
|
+
def mbtn_right_dbl0():
|
|
1430
|
+
self.video_click_signal.emit(0, "MBTN_RIGHT_DBL")
|
|
1431
|
+
|
|
1432
|
+
@p0.player.on_key_press("Ctrl+WHEEL_UP")
|
|
1433
|
+
def ctrl_wheel_up0():
|
|
1434
|
+
self.video_click_signal.emit(0, "Ctrl+WHEEL_UP")
|
|
1435
|
+
|
|
1436
|
+
@p0.player.on_key_press("Ctrl+WHEEL_DOWN")
|
|
1437
|
+
def ctrl_wheel_down0():
|
|
1438
|
+
self.video_click_signal.emit(0, "Ctrl+WHEEL_DOWN")
|
|
1439
|
+
|
|
1440
|
+
@p0.player.on_key_press("WHEEL_UP")
|
|
1441
|
+
def wheel_up0():
|
|
1442
|
+
self.video_click_signal.emit(0, "WHEEL_UP")
|
|
1443
|
+
|
|
1444
|
+
@p0.player.on_key_press("WHEEL_DOWN")
|
|
1445
|
+
def wheel_down0():
|
|
1446
|
+
self.video_click_signal.emit(0, "WHEEL_DOWN")
|
|
1447
|
+
|
|
1448
|
+
@p0.player.on_key_press("Shift+WHEEL_UP")
|
|
1449
|
+
def shift_wheel_up0():
|
|
1450
|
+
self.video_click_signal.emit(0, "Shift+WHEEL_UP")
|
|
1451
|
+
|
|
1452
|
+
@p0.player.on_key_press("Shift+WHEEL_DOWN")
|
|
1453
|
+
def shift_wheel_down0():
|
|
1454
|
+
self.video_click_signal.emit(0, "Shift+WHEEL_DOWN")
|
|
1455
|
+
|
|
1456
|
+
@p0.player.on_key_press("Shift+MBTN_LEFT")
|
|
1457
|
+
def shift_mbtn_left0():
|
|
1458
|
+
self.video_click_signal.emit(0, "Shift+MBTN_LEFT")
|
|
1459
|
+
|
|
1460
|
+
self.dw_player.append(p0)
|
|
1461
|
+
|
|
1462
|
+
if i == 1: # second player
|
|
1463
|
+
p1 = player_dock_widget.DW_player(1, self)
|
|
1464
|
+
|
|
1465
|
+
if not self.MPV_IPC_MODE:
|
|
1466
|
+
|
|
1467
|
+
@p1.player.on_key_press("MBTN_LEFT")
|
|
1468
|
+
def mbtn_left1():
|
|
1469
|
+
self.video_click_signal.emit(1, "MBTN_LEFT")
|
|
1470
|
+
|
|
1471
|
+
@p1.player.on_key_press("MBTN_RIGHT")
|
|
1472
|
+
def mbtn_right1():
|
|
1473
|
+
self.video_click_signal.emit(1, "MBTN_RIGHT")
|
|
1474
|
+
|
|
1475
|
+
@p1.player.on_key_press("MBTN_LEFT_DBL")
|
|
1476
|
+
def mbtn_left_dbl1():
|
|
1477
|
+
self.video_click_signal.emit(1, "MBTN_LEFT_DBL")
|
|
1478
|
+
|
|
1479
|
+
@p1.player.on_key_press("MBTN_RIGHT_DBL")
|
|
1480
|
+
def mbtn_right_dbl1():
|
|
1481
|
+
self.video_click_signal.emit(1, "MBTN_RIGHT_DBL")
|
|
1482
|
+
|
|
1483
|
+
@p1.player.on_key_press("Ctrl+WHEEL_UP")
|
|
1484
|
+
def ctrl_wheel_up1():
|
|
1485
|
+
self.video_click_signal.emit(1, "Ctrl+WHEEL_UP")
|
|
1486
|
+
|
|
1487
|
+
@p1.player.on_key_press("Ctrl+WHEEL_DOWN")
|
|
1488
|
+
def ctrl_wheel_down1():
|
|
1489
|
+
self.video_click_signal.emit(1, "Ctrl+WHEEL_DOWN")
|
|
1490
|
+
|
|
1491
|
+
@p1.player.on_key_press("WHEEL_UP")
|
|
1492
|
+
def wheel_up1():
|
|
1493
|
+
self.video_click_signal.emit(1, "WHEEL_UP")
|
|
1494
|
+
|
|
1495
|
+
@p1.player.on_key_press("WHEEL_DOWN")
|
|
1496
|
+
def wheel_down1():
|
|
1497
|
+
self.video_click_signal.emit(1, "WHEEL_DOWN")
|
|
1498
|
+
|
|
1499
|
+
@p1.player.on_key_press("Shift+WHEEL_UP")
|
|
1500
|
+
def shift_wheel_up1():
|
|
1501
|
+
self.video_click_signal.emit(1, "Shift+WHEEL_UP")
|
|
1502
|
+
|
|
1503
|
+
@p1.player.on_key_press("Shift+WHEEL_DOWN")
|
|
1504
|
+
def shift_wheel_down1():
|
|
1505
|
+
self.video_click_signal.emit(1, "Shift+WHEEL_DOWN")
|
|
1506
|
+
|
|
1507
|
+
@p1.player.on_key_press("Shift+MBTN_LEFT")
|
|
1508
|
+
def shift_mbtn_left1():
|
|
1509
|
+
self.video_click_signal.emit(1, "Shift+MBTN_LEFT")
|
|
1510
|
+
|
|
1511
|
+
self.dw_player.append(p1)
|
|
1512
|
+
|
|
1513
|
+
if i == 2:
|
|
1514
|
+
p2 = player_dock_widget.DW_player(2, self)
|
|
1515
|
+
|
|
1516
|
+
if not self.MPV_IPC_MODE:
|
|
1517
|
+
|
|
1518
|
+
@p2.player.on_key_press("MBTN_LEFT")
|
|
1519
|
+
def mbtn_left2():
|
|
1520
|
+
self.video_click_signal.emit(2, "MBTN_LEFT")
|
|
1521
|
+
|
|
1522
|
+
@p2.player.on_key_press("MBTN_RIGHT")
|
|
1523
|
+
def mbtn_right2():
|
|
1524
|
+
self.video_click_signal.emit(2, "MBTN_RIGHT")
|
|
1525
|
+
|
|
1526
|
+
@p2.player.on_key_press("MBTN_LEFT_DBL")
|
|
1527
|
+
def mbtn_left_dbl2():
|
|
1528
|
+
self.video_click_signal.emit(2, "MBTN_LEFT_DBL")
|
|
1529
|
+
|
|
1530
|
+
@p2.player.on_key_press("MBTN_RIGHT_DBL")
|
|
1531
|
+
def mbtn_right_dbl2():
|
|
1532
|
+
self.video_click_signal.emit(2, "MBTN_RIGHT_DBL")
|
|
1533
|
+
|
|
1534
|
+
@p2.player.on_key_press("Ctrl+WHEEL_UP")
|
|
1535
|
+
def ctrl_wheel_up2():
|
|
1536
|
+
self.video_click_signal.emit(2, "Ctrl+WHEEL_UP")
|
|
1537
|
+
|
|
1538
|
+
@p2.player.on_key_press("Ctrl+WHEEL_DOWN")
|
|
1539
|
+
def ctrl_wheel_down2():
|
|
1540
|
+
self.video_click_signal.emit(2, "Ctrl+WHEEL_DOWN")
|
|
1541
|
+
|
|
1542
|
+
@p2.player.on_key_press("WHEEL_UP")
|
|
1543
|
+
def wheel_up2():
|
|
1544
|
+
self.video_click_signal.emit(2, "WHEEL_UP")
|
|
1545
|
+
|
|
1546
|
+
@p2.player.on_key_press("WHEEL_DOWN")
|
|
1547
|
+
def wheel_down2():
|
|
1548
|
+
self.video_click_signal.emit(2, "WHEEL_DOWN")
|
|
1549
|
+
|
|
1550
|
+
@p2.player.on_key_press("Shift+WHEEL_UP")
|
|
1551
|
+
def shift_wheel_up2():
|
|
1552
|
+
self.video_click_signal.emit(2, "Shift+WHEEL_UP")
|
|
1553
|
+
|
|
1554
|
+
@p2.player.on_key_press("Shift+WHEEL_DOWN")
|
|
1555
|
+
def shift_wheel_down2():
|
|
1556
|
+
self.video_click_signal.emit(2, "Shift+WHEEL_DOWN")
|
|
1557
|
+
|
|
1558
|
+
@p2.player.on_key_press("Shift+MBTN_LEFT")
|
|
1559
|
+
def shift_mbtn_left2():
|
|
1560
|
+
self.video_click_signal.emit(2, "Shift+MBTN_LEFT")
|
|
1561
|
+
|
|
1562
|
+
self.dw_player.append(p2)
|
|
1563
|
+
|
|
1564
|
+
if i == 3:
|
|
1565
|
+
p3 = player_dock_widget.DW_player(3, self)
|
|
1566
|
+
|
|
1567
|
+
if not self.MPV_IPC_MODE:
|
|
1568
|
+
|
|
1569
|
+
@p3.player.on_key_press("MBTN_LEFT")
|
|
1570
|
+
def mbtn_left3():
|
|
1571
|
+
self.video_click_signal.emit(3, "MBTN_LEFT")
|
|
1572
|
+
|
|
1573
|
+
@p3.player.on_key_press("MBTN_RIGHT")
|
|
1574
|
+
def mbtn_right3():
|
|
1575
|
+
self.video_click_signal.emit(3, "MBTN_RIGHT")
|
|
1576
|
+
|
|
1577
|
+
@p3.player.on_key_press("MBTN_LEFT_DBL")
|
|
1578
|
+
def mbtn_left_dbl3():
|
|
1579
|
+
self.video_click_signal.emit(3, "MBTN_LEFT_DBL")
|
|
1580
|
+
|
|
1581
|
+
@p3.player.on_key_press("MBTN_RIGHT_DBL")
|
|
1582
|
+
def mbtn_right_dbl3():
|
|
1583
|
+
self.video_click_signal.emit(3, "MBTN_RIGHT_DBL")
|
|
1584
|
+
|
|
1585
|
+
@p3.player.on_key_press("Ctrl+WHEEL_UP")
|
|
1586
|
+
def ctrl_wheel_up3():
|
|
1587
|
+
self.video_click_signal.emit(3, "Ctrl+WHEEL_UP")
|
|
1588
|
+
|
|
1589
|
+
@p3.player.on_key_press("Ctrl+WHEEL_DOWN")
|
|
1590
|
+
def ctrl_wheel_down3():
|
|
1591
|
+
self.video_click_signal.emit(3, "Ctrl+WHEEL_DOWN")
|
|
1592
|
+
|
|
1593
|
+
@p3.player.on_key_press("WHEEL_UP")
|
|
1594
|
+
def wheel_up3():
|
|
1595
|
+
self.video_click_signal.emit(3, "WHEEL_UP")
|
|
1596
|
+
|
|
1597
|
+
@p3.player.on_key_press("WHEEL_DOWN")
|
|
1598
|
+
def wheel_down3():
|
|
1599
|
+
self.video_click_signal.emit(3, "WHEEL_DOWN")
|
|
1600
|
+
|
|
1601
|
+
@p3.player.on_key_press("Shift+WHEEL_UP")
|
|
1602
|
+
def shift_wheel_up3():
|
|
1603
|
+
self.video_click_signal.emit(3, "Shift+WHEEL_UP")
|
|
1604
|
+
|
|
1605
|
+
@p3.player.on_key_press("Shift+WHEEL_DOWN")
|
|
1606
|
+
def shift_wheel_down3():
|
|
1607
|
+
self.video_click_signal.emit(3, "Shift+WHEEL_DOWN")
|
|
1608
|
+
|
|
1609
|
+
@p3.player.on_key_press("Shift+MBTN_LEFT")
|
|
1610
|
+
def shift_mbtn_left3():
|
|
1611
|
+
self.video_click_signal.emit(3, "Shift+MBTN_LEFT")
|
|
1612
|
+
|
|
1613
|
+
self.dw_player.append(p3)
|
|
1614
|
+
|
|
1615
|
+
if i == 4:
|
|
1616
|
+
p4 = player_dock_widget.DW_player(4, self)
|
|
1617
|
+
|
|
1618
|
+
if not self.MPV_IPC_MODE:
|
|
1619
|
+
|
|
1620
|
+
@p4.player.on_key_press("MBTN_LEFT")
|
|
1621
|
+
def mbtn_left4():
|
|
1622
|
+
self.video_click_signal.emit(4, "MBTN_LEFT")
|
|
1623
|
+
|
|
1624
|
+
@p4.player.on_key_press("MBTN_RIGHT")
|
|
1625
|
+
def mbtn_right4():
|
|
1626
|
+
self.video_click_signal.emit(4, "MBTN_RIGHT")
|
|
1627
|
+
|
|
1628
|
+
@p4.player.on_key_press("MBTN_LEFT_DBL")
|
|
1629
|
+
def mbtn_left_dbl4():
|
|
1630
|
+
self.video_click_signal.emit(4, "MBTN_LEFT_DBL")
|
|
1631
|
+
|
|
1632
|
+
@p4.player.on_key_press("MBTN_RIGHT_DBL")
|
|
1633
|
+
def mbtn_right_dbl4():
|
|
1634
|
+
self.video_click_signal.emit(4, "MBTN_RIGHT_DBL")
|
|
1635
|
+
|
|
1636
|
+
@p4.player.on_key_press("Ctrl+WHEEL_UP")
|
|
1637
|
+
def ctrl_wheel_up4():
|
|
1638
|
+
self.video_click_signal.emit(4, "Ctrl+WHEEL_UP")
|
|
1639
|
+
|
|
1640
|
+
@p4.player.on_key_press("Ctrl+WHEEL_DOWN")
|
|
1641
|
+
def ctrl_wheel_down4():
|
|
1642
|
+
self.video_click_signal.emit(4, "Ctrl+WHEEL_DOWN")
|
|
1643
|
+
|
|
1644
|
+
@p4.player.on_key_press("WHEEL_UP")
|
|
1645
|
+
def wheel_up4():
|
|
1646
|
+
self.video_click_signal.emit(4, "WHEEL_UP")
|
|
1647
|
+
|
|
1648
|
+
@p4.player.on_key_press("WHEEL_DOWN")
|
|
1649
|
+
def wheel_down4():
|
|
1650
|
+
self.video_click_signal.emit(4, "WHEEL_DOWN")
|
|
1651
|
+
|
|
1652
|
+
@p4.player.on_key_press("Shift+WHEEL_UP")
|
|
1653
|
+
def shift_wheel_up4():
|
|
1654
|
+
self.video_click_signal.emit(4, "Shift+WHEEL_UP")
|
|
1655
|
+
|
|
1656
|
+
@p4.player.on_key_press("Shift+WHEEL_DOWN")
|
|
1657
|
+
def shift_wheel_down4():
|
|
1658
|
+
self.video_click_signal.emit(4, "Shift+WHEEL_DOWN")
|
|
1659
|
+
|
|
1660
|
+
@p4.player.on_key_press("Shift+MBTN_LEFT")
|
|
1661
|
+
def shift_mbtn_left4():
|
|
1662
|
+
self.video_click_signal.emit(4, "Shift+MBTN_LEFT")
|
|
1663
|
+
|
|
1664
|
+
self.dw_player.append(p4)
|
|
1665
|
+
|
|
1666
|
+
if i == 5:
|
|
1667
|
+
p5 = player_dock_widget.DW_player(5, self)
|
|
1668
|
+
|
|
1669
|
+
if not self.MPV_IPC_MODE:
|
|
1670
|
+
|
|
1671
|
+
@p5.player.on_key_press("MBTN_LEFT")
|
|
1672
|
+
def mbtn_left5():
|
|
1673
|
+
self.video_click_signal.emit(5, "MBTN_LEFT")
|
|
1674
|
+
|
|
1675
|
+
@p5.player.on_key_press("MBTN_RIGHT")
|
|
1676
|
+
def mbtn_right5():
|
|
1677
|
+
self.video_click_signal.emit(5, "MBTN_RIGHT")
|
|
1678
|
+
|
|
1679
|
+
@p5.player.on_key_press("MBTN_LEFT_DBL")
|
|
1680
|
+
def mbtn_left_dbl5():
|
|
1681
|
+
self.video_click_signal.emit(5, "MBTN_LEFT_DBL")
|
|
1682
|
+
|
|
1683
|
+
@p5.player.on_key_press("MBTN_RIGHT_DBL")
|
|
1684
|
+
def mbtn_right_dbl5():
|
|
1685
|
+
self.video_click_signal.emit(5, "MBTN_RIGHT_DBL")
|
|
1686
|
+
|
|
1687
|
+
@p5.player.on_key_press("Ctrl+WHEEL_UP")
|
|
1688
|
+
def ctrl_wheel_up5():
|
|
1689
|
+
self.video_click_signal.emit(5, "Ctrl+WHEEL_UP")
|
|
1690
|
+
|
|
1691
|
+
@p5.player.on_key_press("Ctrl+WHEEL_DOWN")
|
|
1692
|
+
def ctrl_wheel_down5():
|
|
1693
|
+
self.video_click_signal.emit(5, "Ctrl+WHEEL_DOWN")
|
|
1694
|
+
|
|
1695
|
+
@p5.player.on_key_press("WHEEL_UP")
|
|
1696
|
+
def wheel_up5():
|
|
1697
|
+
self.video_click_signal.emit(5, "WHEEL_UP")
|
|
1698
|
+
|
|
1699
|
+
@p5.player.on_key_press("WHEEL_DOWN")
|
|
1700
|
+
def wheel_down5():
|
|
1701
|
+
self.video_click_signal.emit(5, "WHEEL_DOWN")
|
|
1702
|
+
|
|
1703
|
+
@p5.player.on_key_press("Shift+WHEEL_UP")
|
|
1704
|
+
def shift_wheel_up5():
|
|
1705
|
+
self.video_click_signal.emit(5, "Shift+WHEEL_UP")
|
|
1706
|
+
|
|
1707
|
+
@p5.player.on_key_press("Shift+WHEEL_DOWN")
|
|
1708
|
+
def shift_wheel_down5():
|
|
1709
|
+
self.video_click_signal.emit(5, "Shift+WHEEL_DOWN")
|
|
1710
|
+
|
|
1711
|
+
@p5.player.on_key_press("Shift+MBTN_LEFT")
|
|
1712
|
+
def shift_mbtn_left5():
|
|
1713
|
+
self.video_click_signal.emit(5, "Shift+MBTN_LEFT")
|
|
1714
|
+
|
|
1715
|
+
self.dw_player.append(p5)
|
|
1716
|
+
|
|
1717
|
+
if i == 6:
|
|
1718
|
+
p6 = player_dock_widget.DW_player(6, self)
|
|
1719
|
+
if not self.MPV_IPC_MODE:
|
|
1720
|
+
|
|
1721
|
+
@p6.player.on_key_press("MBTN_LEFT")
|
|
1722
|
+
def mbtn_left6():
|
|
1723
|
+
self.video_click_signal.emit(6, "MBTN_LEFT")
|
|
1724
|
+
|
|
1725
|
+
@p6.player.on_key_press("MBTN_RIGHT")
|
|
1726
|
+
def mbtn_right6():
|
|
1727
|
+
self.video_click_signal.emit(6, "MBTN_RIGHT")
|
|
1728
|
+
|
|
1729
|
+
@p6.player.on_key_press("MBTN_LEFT_DBL")
|
|
1730
|
+
def mbtn_left_dbl6():
|
|
1731
|
+
self.video_click_signal.emit(6, "MBTN_LEFT_DBL")
|
|
1732
|
+
|
|
1733
|
+
@p6.player.on_key_press("MBTN_RIGHT_DBL")
|
|
1734
|
+
def mbtn_right_dbl6():
|
|
1735
|
+
self.video_click_signal.emit(6, "MBTN_RIGHT_DBL")
|
|
1736
|
+
|
|
1737
|
+
@p6.player.on_key_press("Ctrl+WHEEL_UP")
|
|
1738
|
+
def ctrl_wheel_up6():
|
|
1739
|
+
self.video_click_signal.emit(6, "Ctrl+WHEEL_UP")
|
|
1740
|
+
|
|
1741
|
+
@p6.player.on_key_press("Ctrl+WHEEL_DOWN")
|
|
1742
|
+
def ctrl_wheel_down6():
|
|
1743
|
+
self.video_click_signal.emit(6, "Ctrl+WHEEL_DOWN")
|
|
1744
|
+
|
|
1745
|
+
@p6.player.on_key_press("WHEEL_UP")
|
|
1746
|
+
def wheel_up6():
|
|
1747
|
+
self.video_click_signal.emit(6, "WHEEL_UP")
|
|
1748
|
+
|
|
1749
|
+
@p6.player.on_key_press("WHEEL_DOWN")
|
|
1750
|
+
def wheel_down6():
|
|
1751
|
+
self.video_click_signal.emit(6, "WHEEL_DOWN")
|
|
1752
|
+
|
|
1753
|
+
@p6.player.on_key_press("Shift+WHEEL_UP")
|
|
1754
|
+
def shift_wheel_up6():
|
|
1755
|
+
self.video_click_signal.emit(6, "Shift+WHEEL_UP")
|
|
1756
|
+
|
|
1757
|
+
@p6.player.on_key_press("Shift+WHEEL_DOWN")
|
|
1758
|
+
def shift_wheel_down6():
|
|
1759
|
+
self.video_click_signal.emit(6, "Shift+WHEEL_DOWN")
|
|
1760
|
+
|
|
1761
|
+
@p6.player.on_key_press("Shift+MBTN_LEFT")
|
|
1762
|
+
def shift_mbtn_left6():
|
|
1763
|
+
self.video_click_signal.emit(6, "Shift+MBTN_LEFT")
|
|
1764
|
+
|
|
1765
|
+
self.dw_player.append(p6)
|
|
1766
|
+
|
|
1767
|
+
if i == 7:
|
|
1768
|
+
p7 = player_dock_widget.DW_player(7, self)
|
|
1769
|
+
|
|
1770
|
+
if not self.MPV_IPC_MODE:
|
|
1771
|
+
|
|
1772
|
+
@p7.player.on_key_press("MBTN_LEFT")
|
|
1773
|
+
def mbtn_left7():
|
|
1774
|
+
self.video_click_signal.emit(7, "MBTN_LEFT")
|
|
1775
|
+
|
|
1776
|
+
@p7.player.on_key_press("MBTN_RIGHT")
|
|
1777
|
+
def mbtn_right7():
|
|
1778
|
+
self.video_click_signal.emit(7, "MBTN_RIGHT")
|
|
1779
|
+
|
|
1780
|
+
@p7.player.on_key_press("MBTN_LEFT_DBL")
|
|
1781
|
+
def mbtn_left_dbl7():
|
|
1782
|
+
self.video_click_signal.emit(7, "MBTN_LEFT_DBL")
|
|
1783
|
+
|
|
1784
|
+
@p7.player.on_key_press("MBTN_RIGHT_DBL")
|
|
1785
|
+
def mbtn_right_dbl7():
|
|
1786
|
+
self.video_click_signal.emit(7, "MBTN_RIGHT_DBL")
|
|
1787
|
+
|
|
1788
|
+
@p7.player.on_key_press("Ctrl+WHEEL_UP")
|
|
1789
|
+
def ctrl_wheel_up7():
|
|
1790
|
+
self.video_click_signal.emit(7, "Ctrl+WHEEL_UP")
|
|
1791
|
+
|
|
1792
|
+
@p7.player.on_key_press("Ctrl+WHEEL_DOWN")
|
|
1793
|
+
def ctrl_wheel_down7():
|
|
1794
|
+
self.video_click_signal.emit(7, "Ctrl+WHEEL_DOWN")
|
|
1795
|
+
|
|
1796
|
+
@p7.player.on_key_press("WHEEL_UP")
|
|
1797
|
+
def wheel_up7():
|
|
1798
|
+
self.video_click_signal.emit(7, "WHEEL_UP")
|
|
1799
|
+
|
|
1800
|
+
@p7.player.on_key_press("WHEEL_DOWN")
|
|
1801
|
+
def wheel_down7():
|
|
1802
|
+
self.video_click_signal.emit(7, "WHEEL_DOWN")
|
|
1803
|
+
|
|
1804
|
+
@p7.player.on_key_press("Shift+WHEEL_UP")
|
|
1805
|
+
def shift_wheel_up7():
|
|
1806
|
+
self.video_click_signal.emit(7, "Shift+WHEEL_UP")
|
|
1807
|
+
|
|
1808
|
+
@p7.player.on_key_press("Shift+WHEEL_DOWN")
|
|
1809
|
+
def shift_wheel_down7():
|
|
1810
|
+
self.video_click_signal.emit(7, "Shift+WHEEL_DOWN")
|
|
1811
|
+
|
|
1812
|
+
@p7.player.on_key_press("Shift+MBTN_LEFT")
|
|
1813
|
+
def shift_mbtn_left7():
|
|
1814
|
+
self.video_click_signal.emit(7, "Shift+MBTN_LEFT")
|
|
1815
|
+
|
|
1816
|
+
self.dw_player.append(p7)
|
|
1323
1817
|
|
|
1324
1818
|
self.dw_player[-1].setFloating(False)
|
|
1325
1819
|
self.dw_player[-1].setVisible(False)
|
|
@@ -1339,12 +1833,11 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
1339
1833
|
# for receiving event from volume slider
|
|
1340
1834
|
self.dw_player[i].volume_slider_moved_signal.connect(self.set_volume)
|
|
1341
1835
|
|
|
1836
|
+
# for receiving event from mute toolbutton
|
|
1837
|
+
self.dw_player[i].mute_action_triggered_signal.connect(self.set_mute)
|
|
1838
|
+
|
|
1342
1839
|
# for receiving resize event from dock widget
|
|
1343
1840
|
self.dw_player[i].resize_signal.connect(self.resize_dw)
|
|
1344
|
-
"""
|
|
1345
|
-
# for receiving event resize and clicked (Zoom - crop)
|
|
1346
|
-
self.dw_player[i].view_signal.connect(self.signal_from_dw)
|
|
1347
|
-
"""
|
|
1348
1841
|
|
|
1349
1842
|
# add durations list
|
|
1350
1843
|
self.dw_player[i].media_durations = []
|
|
@@ -1353,8 +1846,21 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
1353
1846
|
# add fps list
|
|
1354
1847
|
self.dw_player[i].fps = {}
|
|
1355
1848
|
|
|
1356
|
-
|
|
1849
|
+
if self.MPV_IPC_MODE:
|
|
1850
|
+
while True:
|
|
1851
|
+
r = util.test_mpv_ipc(f"{cfg.MPV_SOCKET}{i}")
|
|
1852
|
+
logging.debug(f"MPV IPC started: {r}")
|
|
1853
|
+
if r:
|
|
1854
|
+
break
|
|
1855
|
+
|
|
1856
|
+
# start timer for activating the main window
|
|
1857
|
+
self.main_window_activation_timer = QTimer()
|
|
1858
|
+
self.main_window_activation_timer.setInterval(500)
|
|
1859
|
+
self.main_window_activation_timer.timeout.connect(self.activateWindow)
|
|
1860
|
+
self.main_window_activation_timer.start()
|
|
1861
|
+
|
|
1357
1862
|
|
|
1863
|
+
for mediaFile in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE][n_player]:
|
|
1358
1864
|
logging.debug(f"media file: {mediaFile}")
|
|
1359
1865
|
|
|
1360
1866
|
media_full_path = project_functions.full_path(mediaFile, self.projectFileName)
|
|
@@ -1363,13 +1869,10 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
1363
1869
|
|
|
1364
1870
|
# media duration
|
|
1365
1871
|
try:
|
|
1366
|
-
mediaLength =
|
|
1367
|
-
self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.LENGTH][mediaFile] * 1000
|
|
1368
|
-
)
|
|
1872
|
+
mediaLength = self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.LENGTH][mediaFile] * 1000
|
|
1369
1873
|
mediaFPS = self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.FPS][mediaFile]
|
|
1370
1874
|
except Exception:
|
|
1371
|
-
|
|
1372
|
-
logging.debug("media_info key not found")
|
|
1875
|
+
logging.debug("media_info key not found in project")
|
|
1373
1876
|
|
|
1374
1877
|
r = util.accurate_media_analysis(self.ffmpeg_bin, media_full_path)
|
|
1375
1878
|
if "error" not in r:
|
|
@@ -1392,67 +1895,76 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
1392
1895
|
self.project_changed()
|
|
1393
1896
|
|
|
1394
1897
|
self.dw_player[i].media_durations.append(int(mediaLength))
|
|
1395
|
-
self.dw_player[i].cumul_media_durations.append(
|
|
1396
|
-
self.dw_player[i].cumul_media_durations[-1] + int(mediaLength)
|
|
1397
|
-
)
|
|
1898
|
+
self.dw_player[i].cumul_media_durations.append(self.dw_player[i].cumul_media_durations[-1] + int(mediaLength))
|
|
1398
1899
|
|
|
1399
1900
|
self.dw_player[i].fps[mediaFile] = mediaFPS
|
|
1400
1901
|
|
|
1902
|
+
# add media file to playlist
|
|
1401
1903
|
self.dw_player[i].player.playlist_append(media_full_path)
|
|
1402
|
-
# self.dw_player[i].player.loadfile(media_full_path)
|
|
1403
|
-
# self.dw_player[i].player.pause = True
|
|
1404
1904
|
|
|
1405
|
-
|
|
1905
|
+
# add media file name to player window title
|
|
1906
|
+
self.dw_player[i].setWindowTitle(f"Player #{i + 1} ({pl.Path(media_full_path).name})")
|
|
1907
|
+
|
|
1908
|
+
# media duration cumuled in seconds
|
|
1909
|
+
self.dw_player[i].cumul_media_durations_sec = [round(dec(x / 1000), 3) for x in self.dw_player[i].cumul_media_durations]
|
|
1910
|
+
|
|
1911
|
+
# check if BORIS is running on a Windows VM with the 'WMIC COMPUTERSYSTEM GET SERIALNUMBER' command
|
|
1406
1912
|
# because "auto" or "auto-safe" crash in Windows VM
|
|
1407
1913
|
# see https://superuser.com/questions/1128339/how-can-i-detect-if-im-within-a-vm-or-not
|
|
1408
1914
|
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1915
|
+
if not self.MPV_IPC_MODE:
|
|
1916
|
+
flag_vm = False
|
|
1917
|
+
if sys.platform.startswith("win"):
|
|
1918
|
+
p = subprocess.Popen(
|
|
1919
|
+
["WMIC", "BIOS", "GET", "SERIALNUMBER"],
|
|
1920
|
+
stdout=subprocess.PIPE,
|
|
1921
|
+
stderr=subprocess.PIPE,
|
|
1922
|
+
shell=True,
|
|
1923
|
+
)
|
|
1924
|
+
out, _ = p.communicate()
|
|
1925
|
+
flag_vm = b"SerialNumber \r\r\n0 " in out
|
|
1926
|
+
logging.debug(f"Running on Windows VM: {flag_vm}")
|
|
1420
1927
|
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1928
|
+
if not flag_vm:
|
|
1929
|
+
self.dw_player[i].player.hwdec = self.config_param.get(cfg.MPV_HWDEC, cfg.MPV_HWDEC_DEFAULT_VALUE)
|
|
1930
|
+
else:
|
|
1931
|
+
self.dw_player[i].player.hwdec = cfg.MPV_HWDEC_NO
|
|
1932
|
+
|
|
1933
|
+
logging.debug(f"Player hwdec of player #{i} set to: {self.dw_player[i].player.hwdec}")
|
|
1934
|
+
self.config_param[cfg.MPV_HWDEC] = self.dw_player[i].player.hwdec
|
|
1425
1935
|
|
|
1426
1936
|
self.dw_player[i].player.playlist_pos = 0
|
|
1427
1937
|
self.dw_player[i].player.wait_until_playing()
|
|
1428
1938
|
self.dw_player[i].player.pause = True
|
|
1429
|
-
|
|
1939
|
+
time.sleep(0.2)
|
|
1940
|
+
# self.dw_player[i].player.wait_until_paused()
|
|
1430
1941
|
self.dw_player[i].player.seek(0, "absolute")
|
|
1431
1942
|
# do not close when playing finished
|
|
1432
1943
|
self.dw_player[i].player.keep_open = True
|
|
1433
1944
|
self.dw_player[i].player.keep_open_pause = False
|
|
1434
1945
|
|
|
1435
|
-
self.dw_player[i].player.image_display_duration = self.pj[cfg.OBSERVATIONS][self.observationId].get(
|
|
1436
|
-
cfg.IMAGE_DISPLAY_DURATION, 1
|
|
1437
|
-
)
|
|
1946
|
+
self.dw_player[i].player.image_display_duration = self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.IMAGE_DISPLAY_DURATION, 1)
|
|
1438
1947
|
|
|
1439
1948
|
# position media
|
|
1440
|
-
|
|
1441
|
-
self.seek_mediaplayer(
|
|
1442
|
-
int(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.OBSERVATION_TIME_INTERVAL][0]), player=i
|
|
1443
|
-
)
|
|
1949
|
+
self.seek_mediaplayer(int(self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0])[0]), player=i)
|
|
1444
1950
|
|
|
1445
|
-
# restore zoom level
|
|
1951
|
+
# restore video zoom level
|
|
1446
1952
|
if cfg.ZOOM_LEVEL in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO]:
|
|
1447
1953
|
self.dw_player[i].player.video_zoom = log2(
|
|
1448
1954
|
self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.ZOOM_LEVEL].get(n_player, 0)
|
|
1449
1955
|
)
|
|
1450
1956
|
|
|
1957
|
+
# restore video pan
|
|
1958
|
+
if cfg.PAN_X in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO]:
|
|
1959
|
+
self.dw_player[i].player.video_pan_x = self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.PAN_X].get(n_player, 0)
|
|
1960
|
+
if cfg.PAN_Y in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO]:
|
|
1961
|
+
self.dw_player[i].player.video_pan_y = self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.PAN_Y].get(n_player, 0)
|
|
1962
|
+
|
|
1451
1963
|
# restore rotation angle
|
|
1452
1964
|
if cfg.ROTATION_ANGLE in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO]:
|
|
1453
|
-
self.dw_player[i].player.video_rotate = self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][
|
|
1454
|
-
|
|
1455
|
-
|
|
1965
|
+
self.dw_player[i].player.video_rotate = self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.ROTATION_ANGLE].get(
|
|
1966
|
+
n_player, 0
|
|
1967
|
+
)
|
|
1456
1968
|
|
|
1457
1969
|
# restore subtitle visibility
|
|
1458
1970
|
if cfg.DISPLAY_MEDIA_SUBTITLES in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO]:
|
|
@@ -1461,7 +1973,6 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
1461
1973
|
].get(n_player, True)
|
|
1462
1974
|
|
|
1463
1975
|
# restore overlays
|
|
1464
|
-
|
|
1465
1976
|
if cfg.OVERLAY in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO]:
|
|
1466
1977
|
if n_player in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OVERLAY]:
|
|
1467
1978
|
self.overlays[i] = self.dw_player[i].player.create_image_overlay()
|
|
@@ -1469,9 +1980,20 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
1469
1980
|
|
|
1470
1981
|
menu_options.update_menu(self)
|
|
1471
1982
|
|
|
1472
|
-
self.
|
|
1983
|
+
if self.MPV_IPC_MODE:
|
|
1984
|
+
# activate timer
|
|
1985
|
+
self.ipc_mpv_timer = QTimer()
|
|
1986
|
+
self.ipc_mpv_timer.setInterval(500)
|
|
1987
|
+
self.ipc_mpv_timer.timeout.connect(self.mpv_timer_out)
|
|
1988
|
+
|
|
1989
|
+
else:
|
|
1990
|
+
self.ipc_mpv_timer = None
|
|
1991
|
+
self.time_observer_signal.connect(self.mpv_timer_out)
|
|
1992
|
+
|
|
1993
|
+
self.mpv_eof_reached_signal.connect(self.mpv_eof_reached)
|
|
1994
|
+
self.video_click_signal.connect(self.player_clicked)
|
|
1473
1995
|
|
|
1474
|
-
self.actionPlay.setIcon(QIcon(":/
|
|
1996
|
+
self.actionPlay.setIcon(QIcon(f":/play_{gui_utilities.theme_mode()}"))
|
|
1475
1997
|
|
|
1476
1998
|
self.display_statusbar_info(self.observationId)
|
|
1477
1999
|
|
|
@@ -1480,24 +2002,17 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
1480
2002
|
self.state_behaviors_codes = tuple(util.state_behavior_codes(self.pj[cfg.ETHOGRAM]))
|
|
1481
2003
|
|
|
1482
2004
|
video_operations.display_play_rate(self)
|
|
2005
|
+
video_operations.display_zoom_level(self)
|
|
1483
2006
|
|
|
1484
2007
|
# spectrogram
|
|
1485
2008
|
if (
|
|
1486
2009
|
cfg.VISUALIZE_SPECTROGRAM in self.pj[cfg.OBSERVATIONS][self.observationId]
|
|
1487
2010
|
and self.pj[cfg.OBSERVATIONS][self.observationId][cfg.VISUALIZE_SPECTROGRAM]
|
|
1488
2011
|
):
|
|
1489
|
-
|
|
1490
|
-
tmp_dir = (
|
|
1491
|
-
self.ffmpeg_cache_dir
|
|
1492
|
-
if self.ffmpeg_cache_dir and os.path.isdir(self.ffmpeg_cache_dir)
|
|
1493
|
-
else tempfile.gettempdir()
|
|
1494
|
-
)
|
|
2012
|
+
tmp_dir = self.ffmpeg_cache_dir if self.ffmpeg_cache_dir and os.path.isdir(self.ffmpeg_cache_dir) else tempfile.gettempdir()
|
|
1495
2013
|
|
|
1496
2014
|
wav_file_path = (
|
|
1497
|
-
pl.Path(tmp_dir)
|
|
1498
|
-
/ pl.Path(
|
|
1499
|
-
self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"] + ".wav"
|
|
1500
|
-
).name
|
|
2015
|
+
pl.Path(tmp_dir) / pl.Path(self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"] + ".wav").name
|
|
1501
2016
|
)
|
|
1502
2017
|
|
|
1503
2018
|
if not wav_file_path.is_file():
|
|
@@ -1510,18 +2025,10 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
1510
2025
|
cfg.VISUALIZE_WAVEFORM in self.pj[cfg.OBSERVATIONS][self.observationId]
|
|
1511
2026
|
and self.pj[cfg.OBSERVATIONS][self.observationId][cfg.VISUALIZE_WAVEFORM]
|
|
1512
2027
|
):
|
|
1513
|
-
|
|
1514
|
-
tmp_dir = (
|
|
1515
|
-
self.ffmpeg_cache_dir
|
|
1516
|
-
if self.ffmpeg_cache_dir and os.path.isdir(self.ffmpeg_cache_dir)
|
|
1517
|
-
else tempfile.gettempdir()
|
|
1518
|
-
)
|
|
2028
|
+
tmp_dir = self.ffmpeg_cache_dir if self.ffmpeg_cache_dir and os.path.isdir(self.ffmpeg_cache_dir) else tempfile.gettempdir()
|
|
1519
2029
|
|
|
1520
2030
|
wav_file_path = (
|
|
1521
|
-
pl.Path(tmp_dir)
|
|
1522
|
-
/ pl.Path(
|
|
1523
|
-
self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"] + ".wav"
|
|
1524
|
-
).name
|
|
2031
|
+
pl.Path(tmp_dir) / pl.Path(self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"] + ".wav").name
|
|
1525
2032
|
)
|
|
1526
2033
|
|
|
1527
2034
|
if not wav_file_path.is_file():
|
|
@@ -1530,11 +2037,7 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
1530
2037
|
self.show_plot_widget("waveform", warning=False)
|
|
1531
2038
|
|
|
1532
2039
|
# external data plot
|
|
1533
|
-
if
|
|
1534
|
-
cfg.PLOT_DATA in self.pj[cfg.OBSERVATIONS][self.observationId]
|
|
1535
|
-
and self.pj[cfg.OBSERVATIONS][self.observationId][cfg.PLOT_DATA]
|
|
1536
|
-
):
|
|
1537
|
-
|
|
2040
|
+
if cfg.PLOT_DATA in self.pj[cfg.OBSERVATIONS][self.observationId] and self.pj[cfg.OBSERVATIONS][self.observationId][cfg.PLOT_DATA]:
|
|
1538
2041
|
self.plot_data = {}
|
|
1539
2042
|
self.ext_data_timer_list = []
|
|
1540
2043
|
count = 0
|
|
@@ -1549,9 +2052,7 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
1549
2052
|
QMessageBox.critical(
|
|
1550
2053
|
self,
|
|
1551
2054
|
cfg.programName,
|
|
1552
|
-
"Data file not found:\n{}".format(
|
|
1553
|
-
self.pj[cfg.OBSERVATIONS][self.observationId][cfg.PLOT_DATA][idx]["file_path"]
|
|
1554
|
-
),
|
|
2055
|
+
"Data file not found:\n{}".format(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.PLOT_DATA][idx]["file_path"]),
|
|
1555
2056
|
)
|
|
1556
2057
|
data_ok = False
|
|
1557
2058
|
# return False
|
|
@@ -1575,7 +2076,8 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
1575
2076
|
self,
|
|
1576
2077
|
cfg.programName,
|
|
1577
2078
|
(
|
|
1578
|
-
|
|
2079
|
+
"Impossible to plot data from file "
|
|
2080
|
+
f"{os.path.basename(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.PLOT_DATA][idx]['file_path'])}:\n"
|
|
1579
2081
|
f"{w1.error_msg}"
|
|
1580
2082
|
),
|
|
1581
2083
|
)
|
|
@@ -1606,9 +2108,7 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
1606
2108
|
QMessageBox.critical(
|
|
1607
2109
|
self,
|
|
1608
2110
|
cfg.programName,
|
|
1609
|
-
"Data file not found:\n{}".format(
|
|
1610
|
-
self.pj[cfg.OBSERVATIONS][self.observationId][cfg.PLOT_DATA][idx]["file_path"]
|
|
1611
|
-
),
|
|
2111
|
+
"Data file not found:\n{}".format(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.PLOT_DATA][idx]["file_path"]),
|
|
1612
2112
|
)
|
|
1613
2113
|
data_ok = False
|
|
1614
2114
|
# return False
|
|
@@ -1673,9 +2173,12 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
1673
2173
|
for player in self.dw_player:
|
|
1674
2174
|
player.setVisible(True)
|
|
1675
2175
|
|
|
2176
|
+
self.load_tw_events(self.observationId)
|
|
2177
|
+
|
|
1676
2178
|
# initial synchro
|
|
1677
|
-
|
|
1678
|
-
|
|
2179
|
+
if not self.MPV_IPC_MODE:
|
|
2180
|
+
for n_player in range(1, len(self.dw_player)):
|
|
2181
|
+
self.sync_time(n_player, 0)
|
|
1679
2182
|
|
|
1680
2183
|
self.mpv_timer_out(value=0.0)
|
|
1681
2184
|
|
|
@@ -1710,6 +2213,7 @@ def initialize_new_live_observation(self):
|
|
|
1710
2213
|
|
|
1711
2214
|
# button start enabled
|
|
1712
2215
|
self.pb_live_obs.setEnabled(True)
|
|
2216
|
+
|
|
1713
2217
|
self.w_live.setVisible(True)
|
|
1714
2218
|
self.w_obs_info.setVisible(True)
|
|
1715
2219
|
|
|
@@ -1719,9 +2223,9 @@ def initialize_new_live_observation(self):
|
|
|
1719
2223
|
self.pb_live_obs.setText("Start live observation")
|
|
1720
2224
|
|
|
1721
2225
|
if self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.START_FROM_CURRENT_TIME, False):
|
|
1722
|
-
current_time = util.seconds_of_day(
|
|
2226
|
+
current_time = util.seconds_of_day(dt.datetime.now())
|
|
1723
2227
|
elif self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.START_FROM_CURRENT_EPOCH_TIME, False):
|
|
1724
|
-
current_time = time.mktime(
|
|
2228
|
+
current_time = time.mktime(dt.datetime.now().timetuple())
|
|
1725
2229
|
else:
|
|
1726
2230
|
current_time = 0
|
|
1727
2231
|
|
|
@@ -1740,6 +2244,8 @@ def initialize_new_live_observation(self):
|
|
|
1740
2244
|
self.liveStartTime = None
|
|
1741
2245
|
self.liveTimer.stop()
|
|
1742
2246
|
|
|
2247
|
+
self.load_tw_events(self.observationId)
|
|
2248
|
+
|
|
1743
2249
|
self.get_events_current_row()
|
|
1744
2250
|
|
|
1745
2251
|
|
|
@@ -1748,16 +2254,14 @@ def initialize_new_images_observation(self):
|
|
|
1748
2254
|
initialize a new observation from directories of images
|
|
1749
2255
|
"""
|
|
1750
2256
|
|
|
1751
|
-
for dw in
|
|
2257
|
+
for dw in (self.dwEthogram, self.dwSubjects, self.dwEvents):
|
|
1752
2258
|
dw.setVisible(True)
|
|
1753
2259
|
# disable start live button
|
|
1754
2260
|
self.pb_live_obs.setEnabled(False)
|
|
1755
2261
|
self.w_live.setVisible(False)
|
|
1756
2262
|
|
|
1757
2263
|
# check if directories are available
|
|
1758
|
-
ok, msg = project_functions.check_directories_availability(
|
|
1759
|
-
self.pj[cfg.OBSERVATIONS][self.observationId], self.projectFileName
|
|
1760
|
-
)
|
|
2264
|
+
ok, msg = project_functions.check_directories_availability(self.pj[cfg.OBSERVATIONS][self.observationId], self.projectFileName)
|
|
1761
2265
|
|
|
1762
2266
|
if not ok:
|
|
1763
2267
|
QMessageBox.critical(
|
|
@@ -1777,7 +2281,8 @@ def initialize_new_images_observation(self):
|
|
|
1777
2281
|
# count number of images in all directories
|
|
1778
2282
|
tot_images_number = 0
|
|
1779
2283
|
for dir_path in self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.DIRECTORIES_LIST, []):
|
|
1780
|
-
|
|
2284
|
+
full_dir_path = project_functions.full_path(dir_path, self.projectFileName)
|
|
2285
|
+
result = util.dir_images_number(full_dir_path)
|
|
1781
2286
|
tot_images_number += result.get("number of images", 0)
|
|
1782
2287
|
|
|
1783
2288
|
if not tot_images_number:
|
|
@@ -1785,7 +2290,7 @@ def initialize_new_images_observation(self):
|
|
|
1785
2290
|
self,
|
|
1786
2291
|
cfg.programName,
|
|
1787
2292
|
(
|
|
1788
|
-
|
|
2293
|
+
"No images were found in directory(ies).<br><br>The observation will be opened in VIEW mode.<br>"
|
|
1789
2294
|
"It will not be possible to log events.<br>"
|
|
1790
2295
|
"Modify the directoriy path(s) to point existing directory "
|
|
1791
2296
|
),
|
|
@@ -1799,15 +2304,16 @@ def initialize_new_images_observation(self):
|
|
|
1799
2304
|
# load image paths
|
|
1800
2305
|
# directories user order is maintained
|
|
1801
2306
|
# images are sorted inside each directory
|
|
1802
|
-
self.images_list = []
|
|
2307
|
+
self.images_list: list = []
|
|
1803
2308
|
for dir_path in self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.DIRECTORIES_LIST, []):
|
|
2309
|
+
full_dir_path = project_functions.full_path(dir_path, self.projectFileName)
|
|
1804
2310
|
for pattern in cfg.IMAGE_EXTENSIONS:
|
|
1805
2311
|
self.images_list.extend(
|
|
1806
2312
|
sorted(
|
|
1807
2313
|
list(
|
|
1808
2314
|
set(
|
|
1809
|
-
[str(x) for x in pl.Path(
|
|
1810
|
-
+ [str(x) for x in pl.Path(
|
|
2315
|
+
[str(x) for x in pl.Path(full_dir_path).glob(pattern)]
|
|
2316
|
+
+ [str(x) for x in pl.Path(full_dir_path).glob(pattern.upper())]
|
|
1811
2317
|
)
|
|
1812
2318
|
)
|
|
1813
2319
|
)
|
|
@@ -1872,31 +2378,160 @@ def event2media_file_name(observation: dict, timestamp: dec) -> Optional[str]:
|
|
|
1872
2378
|
"""
|
|
1873
2379
|
returns the media file name corresponding to the event (start time in case of state event)
|
|
1874
2380
|
|
|
2381
|
+
Args:
|
|
2382
|
+
observation (dict): observation
|
|
2383
|
+
timestamp (dec): time stamp
|
|
2384
|
+
|
|
1875
2385
|
Returns:
|
|
1876
|
-
str:
|
|
2386
|
+
str: path of media file containing the event
|
|
1877
2387
|
"""
|
|
2388
|
+
if observation.get(cfg.MEDIA_CREATION_DATE_AS_OFFSET, False):
|
|
2389
|
+
# media creation date/time was used for coding
|
|
2390
|
+
video_file_name = None
|
|
2391
|
+
for media_path in observation[cfg.MEDIA_INFO].get(cfg.MEDIA_CREATION_TIME, {}):
|
|
2392
|
+
start_media = observation[cfg.MEDIA_INFO][cfg.MEDIA_CREATION_TIME][media_path]
|
|
2393
|
+
duration = observation[cfg.MEDIA_INFO][cfg.LENGTH][media_path]
|
|
2394
|
+
if start_media <= timestamp <= start_media + duration:
|
|
2395
|
+
video_file_name = media_path
|
|
2396
|
+
break
|
|
2397
|
+
|
|
2398
|
+
else: # no media creation date
|
|
2399
|
+
cumul_media_durations: list = [dec(0)]
|
|
2400
|
+
for media_file in observation[cfg.FILE][cfg.PLAYER1]:
|
|
2401
|
+
try:
|
|
2402
|
+
media_duration = observation[cfg.MEDIA_INFO][cfg.LENGTH][media_file]
|
|
2403
|
+
# cut off media duration to 3 decimal places as that is how fine the player is
|
|
2404
|
+
media_duration = floor(media_duration * 10**3) / dec(10**3)
|
|
2405
|
+
cumul_media_durations.append(floor((cumul_media_durations[-1] + media_duration) * 10**3) / dec(10**3))
|
|
2406
|
+
except KeyError:
|
|
2407
|
+
return None
|
|
2408
|
+
|
|
2409
|
+
"""
|
|
2410
|
+
cumul_media_durations: list = [dec(0)]
|
|
2411
|
+
for media_file in observation[cfg.FILE][cfg.PLAYER1]:
|
|
2412
|
+
try:
|
|
2413
|
+
media_duration = dec(str(observation[cfg.MEDIA_INFO][cfg.LENGTH][media_file]))
|
|
2414
|
+
cumul_media_durations.append(round(cumul_media_durations[-1] + media_duration, 3))
|
|
2415
|
+
except KeyError:
|
|
2416
|
+
return None
|
|
2417
|
+
"""
|
|
2418
|
+
|
|
2419
|
+
cumul_media_durations.remove(dec(0))
|
|
2420
|
+
|
|
2421
|
+
logging.debug(f"{cumul_media_durations=}")
|
|
2422
|
+
|
|
2423
|
+
# test if timestamp is at end of last media
|
|
2424
|
+
if timestamp == cumul_media_durations[-1]:
|
|
2425
|
+
player_idx = len(observation[cfg.FILE][cfg.PLAYER1]) - 1
|
|
2426
|
+
else:
|
|
2427
|
+
player_idx = None
|
|
2428
|
+
for idx, value in enumerate(cumul_media_durations):
|
|
2429
|
+
start = 0 if idx == 0 else cumul_media_durations[idx - 1]
|
|
2430
|
+
if start <= timestamp < value:
|
|
2431
|
+
player_idx = idx
|
|
2432
|
+
break
|
|
1878
2433
|
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
2434
|
+
video_file_name = observation[cfg.FILE][cfg.PLAYER1][player_idx] if player_idx is not None else None
|
|
2435
|
+
|
|
2436
|
+
return video_file_name
|
|
2437
|
+
|
|
2438
|
+
|
|
2439
|
+
def create_observations(self):
|
|
2440
|
+
"""
|
|
2441
|
+
Create observations from a media file directory
|
|
2442
|
+
"""
|
|
2443
|
+
# print(self.pj[cfg.OBSERVATIONS])
|
|
2444
|
+
|
|
2445
|
+
dir_path = QFileDialog.getExistingDirectory(None, "Select directory", os.getenv("HOME"))
|
|
2446
|
+
if not dir_path:
|
|
2447
|
+
return
|
|
2448
|
+
|
|
2449
|
+
dlg = dialog.Input_dialog(
|
|
2450
|
+
label_caption="Set the following observation parameters",
|
|
2451
|
+
elements_list=[
|
|
2452
|
+
("cb", "Recurse the subdirectories", False),
|
|
2453
|
+
("cb", "Save the absolute media file path", True),
|
|
2454
|
+
("cb", "Visualize spectrogram", False),
|
|
2455
|
+
("cb", "Visualize waveform", False),
|
|
2456
|
+
("cb", "Media creation date as offset", False),
|
|
2457
|
+
("cb", "Close behaviors between videos", False),
|
|
2458
|
+
("dsb", "Time offset (in seconds)", 0.0, 86400, 1, 0, 3),
|
|
2459
|
+
("dsb", "Media scan sampling duration (in seconds)", 0.0, 86400, 1, 0, 3),
|
|
2460
|
+
],
|
|
2461
|
+
title="Observation parameters",
|
|
2462
|
+
)
|
|
2463
|
+
if not dlg.exec_():
|
|
2464
|
+
return
|
|
1883
2465
|
|
|
1884
|
-
|
|
2466
|
+
file_count: int = 0
|
|
1885
2467
|
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
player_idx = len(observation[cfg.FILE]["1"]) - 1
|
|
2468
|
+
if dlg.elements["Recurse the subdirectories"].isChecked():
|
|
2469
|
+
files_list = pl.Path(dir_path).rglob("*")
|
|
1889
2470
|
else:
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
2471
|
+
files_list = pl.Path(dir_path).glob("*")
|
|
2472
|
+
|
|
2473
|
+
for file in files_list:
|
|
2474
|
+
if not file.is_file():
|
|
2475
|
+
continue
|
|
2476
|
+
r = util.accurate_media_analysis(ffmpeg_bin=self.ffmpeg_bin, file_name=file)
|
|
2477
|
+
if "error" not in r:
|
|
2478
|
+
if not r.get("frames_number", 0):
|
|
2479
|
+
continue
|
|
2480
|
+
|
|
2481
|
+
if dlg.elements["Save the absolute media file path"].isChecked():
|
|
2482
|
+
media_file = str(file)
|
|
2483
|
+
else:
|
|
2484
|
+
try:
|
|
2485
|
+
media_file = str(file.relative_to(pl.Path(self.projectFileName).parent))
|
|
2486
|
+
except ValueError:
|
|
2487
|
+
QMessageBox.critical(
|
|
2488
|
+
self,
|
|
2489
|
+
cfg.programName,
|
|
2490
|
+
(
|
|
2491
|
+
f"the media file <b>{file}</b> can not be relative to the project directory "
|
|
2492
|
+
f"(<b>{pl.Path(self.projectFileName).parent}</b>)"
|
|
2493
|
+
"<br><br>Aborting the creation of observations"
|
|
2494
|
+
),
|
|
2495
|
+
)
|
|
2496
|
+
return
|
|
2497
|
+
|
|
2498
|
+
if media_file in self.pj[cfg.OBSERVATIONS]:
|
|
2499
|
+
QMessageBox.critical(
|
|
2500
|
+
self,
|
|
2501
|
+
cfg.programName,
|
|
2502
|
+
(f"The observation <b>{media_file}</b> already exists.<br><br>Aborting the creation of observations"),
|
|
2503
|
+
)
|
|
2504
|
+
return
|
|
1896
2505
|
|
|
1897
|
-
|
|
1898
|
-
|
|
2506
|
+
self.pj[cfg.OBSERVATIONS][media_file] = {
|
|
2507
|
+
"file": {"1": [media_file], "2": [], "3": [], "4": [], "5": [], "6": [], "7": [], "8": []},
|
|
2508
|
+
"type": "MEDIA",
|
|
2509
|
+
"date": dt.datetime.now().replace(microsecond=0).isoformat(),
|
|
2510
|
+
"description": "",
|
|
2511
|
+
"time offset": dec(str(round(dlg.elements["Time offset (in seconds)"].value(), 3))),
|
|
2512
|
+
"events": [],
|
|
2513
|
+
"observation time interval": [0, 0],
|
|
2514
|
+
"independent_variables": {},
|
|
2515
|
+
"visualize_spectrogram": dlg.elements["Visualize spectrogram"].isChecked(),
|
|
2516
|
+
"visualize_waveform": dlg.elements["Visualize waveform"].isChecked(),
|
|
2517
|
+
"media_creation_date_as_offset": dlg.elements["Media creation date as offset"].isChecked(),
|
|
2518
|
+
"media_scan_sampling_duration": dec(str(round(dlg.elements["Media scan sampling duration (in seconds)"].value(), 3))),
|
|
2519
|
+
"image_display_duration": 1,
|
|
2520
|
+
"close_behaviors_between_videos": dlg.elements["Close behaviors between videos"].isChecked(),
|
|
2521
|
+
"media_info": {
|
|
2522
|
+
"length": {media_file: r["duration"]},
|
|
2523
|
+
"fps": {media_file: r["duration"]},
|
|
2524
|
+
"hasVideo": {media_file: r["has_video"]},
|
|
2525
|
+
"hasAudio": {media_file: r["has_audio"]},
|
|
2526
|
+
"offset": {"1": 0.0},
|
|
2527
|
+
},
|
|
2528
|
+
}
|
|
2529
|
+
file_count += 1
|
|
2530
|
+
self.project_changed()
|
|
2531
|
+
|
|
2532
|
+
if file_count:
|
|
2533
|
+
message: str = f"{file_count} observation(s) were created" if file_count > 1 else "One observation was created"
|
|
1899
2534
|
else:
|
|
1900
|
-
|
|
2535
|
+
message: str = f"No media file were found in {dir_path}"
|
|
1901
2536
|
|
|
1902
|
-
|
|
2537
|
+
QMessageBox.information(self, cfg.programName, message)
|