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