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