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