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/latency.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
|
|
@@ -18,9 +18,7 @@ Copyright 2012-2023 Olivier Friard
|
|
|
18
18
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
19
19
|
MA 02110-1301, USA.
|
|
20
20
|
|
|
21
|
-
"""
|
|
22
21
|
|
|
23
|
-
"""
|
|
24
22
|
Module for analyzing the latency of behaviors after another behavior(s) (marker)
|
|
25
23
|
|
|
26
24
|
"""
|
|
@@ -31,7 +29,7 @@ from . import dialog
|
|
|
31
29
|
from . import select_observations
|
|
32
30
|
from . import project_functions, observation_operations
|
|
33
31
|
|
|
34
|
-
from
|
|
32
|
+
from PySide6.QtWidgets import QMessageBox
|
|
35
33
|
|
|
36
34
|
|
|
37
35
|
def get_latency(self):
|
|
@@ -43,10 +41,9 @@ def get_latency(self):
|
|
|
43
41
|
None,
|
|
44
42
|
cfg.programName,
|
|
45
43
|
(
|
|
46
|
-
|
|
44
|
+
"This function is experimental. Please test it and report any bug at <br>"
|
|
47
45
|
'<a href="https://github.com/olivierfriard/BORIS/issues">'
|
|
48
46
|
"https://github.com/olivierfriard/BORIS/issues</a><br>"
|
|
49
|
-
"or by email (See the About page on the BORIS web site.<br><br>"
|
|
50
47
|
"Thank you for your collaboration!"
|
|
51
48
|
),
|
|
52
49
|
QMessageBox.Ok | QMessageBox.Default,
|
|
@@ -83,10 +80,10 @@ def get_latency(self):
|
|
|
83
80
|
)
|
|
84
81
|
return
|
|
85
82
|
|
|
86
|
-
parameters = select_subj_behav.choose_obs_subj_behav_category(
|
|
83
|
+
parameters: dict = select_subj_behav.choose_obs_subj_behav_category(
|
|
87
84
|
self,
|
|
88
85
|
selected_observations,
|
|
89
|
-
|
|
86
|
+
show_exclude_non_coded_behaviors=False,
|
|
90
87
|
window_title="Select the marker behaviors (stimulus)",
|
|
91
88
|
n_observations=len(selected_observations),
|
|
92
89
|
)
|
|
@@ -102,10 +99,10 @@ def get_latency(self):
|
|
|
102
99
|
marker_subjects = parameters[cfg.SELECTED_SUBJECTS]
|
|
103
100
|
include_marker_modifiers = parameters[cfg.INCLUDE_MODIFIERS]
|
|
104
101
|
|
|
105
|
-
|
|
102
|
+
print(f"{marker_behaviors=} {marker_subjects=} {include_marker_modifiers=}")
|
|
106
103
|
|
|
107
|
-
parameters = select_subj_behav.choose_obs_subj_behav_category(
|
|
108
|
-
self, selected_observations,
|
|
104
|
+
parameters: dict = select_subj_behav.choose_obs_subj_behav_category(
|
|
105
|
+
self, selected_observations, show_exclude_non_coded_behaviors=False, window_title="Select the latency behaviors"
|
|
109
106
|
)
|
|
110
107
|
if not parameters[cfg.SELECTED_SUBJECTS] or not parameters[cfg.SELECTED_BEHAVIORS]:
|
|
111
108
|
return
|
|
@@ -113,17 +110,29 @@ def get_latency(self):
|
|
|
113
110
|
latency_subjects = parameters[cfg.SELECTED_SUBJECTS]
|
|
114
111
|
include_latency_modifiers = parameters[cfg.INCLUDE_MODIFIERS]
|
|
115
112
|
|
|
116
|
-
|
|
113
|
+
print(f"{latency_behaviors=} {latency_subjects=} {include_latency_modifiers=}")
|
|
117
114
|
|
|
118
|
-
results = {}
|
|
115
|
+
results: dict = {}
|
|
119
116
|
for obs_id in selected_observations:
|
|
120
|
-
|
|
117
|
+
print(f"{obs_id=}")
|
|
121
118
|
|
|
122
119
|
events_with_status = project_functions.events_start_stop(
|
|
123
|
-
self.pj[cfg.ETHOGRAM],
|
|
120
|
+
self.pj[cfg.ETHOGRAM],
|
|
121
|
+
self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS],
|
|
122
|
+
self.pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE],
|
|
124
123
|
)
|
|
125
124
|
|
|
125
|
+
print(f"{events_with_status=}")
|
|
126
|
+
|
|
126
127
|
for idx, event in enumerate(events_with_status):
|
|
128
|
+
print(f"{event=}")
|
|
129
|
+
|
|
130
|
+
print(f"{event[cfg.EVENT_STATUS_FIELD_IDX]=}")
|
|
131
|
+
|
|
132
|
+
print(f"{event[cfg.EVENT_BEHAVIOR_FIELD_IDX]=}")
|
|
133
|
+
|
|
134
|
+
print(f"{event[cfg.EVENT_SUBJECT_FIELD_IDX]=}")
|
|
135
|
+
|
|
127
136
|
if all(
|
|
128
137
|
(
|
|
129
138
|
event[cfg.EVENT_STATUS_FIELD_IDX] in (cfg.START, cfg.POINT),
|
|
@@ -136,11 +145,13 @@ def get_latency(self):
|
|
|
136
145
|
),
|
|
137
146
|
)
|
|
138
147
|
):
|
|
139
|
-
|
|
140
148
|
if include_marker_modifiers:
|
|
141
149
|
marker = event[cfg.EVENT_TIME_FIELD_IDX : cfg.EVENT_MODIFIER_FIELD_IDX + 1]
|
|
142
150
|
else:
|
|
143
151
|
marker = event[cfg.EVENT_TIME_FIELD_IDX : cfg.EVENT_BEHAVIOR_FIELD_IDX + 1]
|
|
152
|
+
|
|
153
|
+
print(f"{marker=}")
|
|
154
|
+
|
|
144
155
|
if marker not in results:
|
|
145
156
|
results[marker] = {}
|
|
146
157
|
|
|
@@ -162,20 +173,19 @@ def get_latency(self):
|
|
|
162
173
|
),
|
|
163
174
|
)
|
|
164
175
|
):
|
|
165
|
-
|
|
166
|
-
# print(event, event2)
|
|
176
|
+
print(event, event2)
|
|
167
177
|
if include_latency_modifiers:
|
|
168
178
|
latency = event2[cfg.EVENT_SUBJECT_FIELD_IDX : cfg.EVENT_MODIFIER_FIELD_IDX + 1]
|
|
169
179
|
else:
|
|
170
180
|
latency = event2[cfg.EVENT_SUBJECT_FIELD_IDX : cfg.EVENT_BEHAVIOR_FIELD_IDX + 1]
|
|
171
181
|
|
|
172
182
|
# print(f"{marker=}")
|
|
173
|
-
|
|
174
|
-
if not
|
|
183
|
+
print(f"{latency=}")
|
|
184
|
+
if latency not in results[marker]:
|
|
175
185
|
results[marker][latency] = []
|
|
176
|
-
results[marker][latency].append(
|
|
177
|
-
|
|
178
|
-
)
|
|
186
|
+
results[marker][latency].append(event2[cfg.EVENT_TIME_FIELD_IDX] - event[cfg.EVENT_TIME_FIELD_IDX])
|
|
187
|
+
|
|
188
|
+
print(f"{results[marker][latency]=}")
|
|
179
189
|
|
|
180
190
|
# check if new marker
|
|
181
191
|
if all(
|
|
@@ -206,7 +216,6 @@ def get_latency(self):
|
|
|
206
216
|
out = ""
|
|
207
217
|
|
|
208
218
|
for marker in sorted(results.keys()):
|
|
209
|
-
|
|
210
219
|
subject = cfg.NO_FOCAL_SUBJECT if marker[cfg.EVENT_SUBJECT_FIELD_IDX] == "" else marker[1]
|
|
211
220
|
if include_marker_modifiers:
|
|
212
221
|
out += f"Marker: <b>{marker[cfg.EVENT_BEHAVIOR_FIELD_IDX]}</b> at {marker[cfg.EVENT_TIME_FIELD_IDX]} s (subject: {subject} - modifiers: {marker[cfg.EVENT_MODIFIER_FIELD_IDX]})<br><br>"
|
|
@@ -221,7 +230,7 @@ def get_latency(self):
|
|
|
221
230
|
|
|
222
231
|
out += "first occurrence: "
|
|
223
232
|
out += f"{sorted(results[marker][behav])[0]} s<br>"
|
|
224
|
-
out +=
|
|
233
|
+
out += "all occurrences: "
|
|
225
234
|
|
|
226
235
|
out += ", ".join([f"{x} s" for x in sorted(results[marker][behav])])
|
|
227
236
|
out += "<br><br>"
|
boris/measurement_widget.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
1
|
"""
|
|
3
2
|
BORIS
|
|
4
3
|
Behavioral Observation Research Interactive Software
|
|
5
|
-
Copyright 2012-
|
|
4
|
+
Copyright 2012-2025 Olivier Friard
|
|
6
5
|
|
|
7
6
|
This file is part of BORIS.
|
|
8
7
|
|
|
@@ -23,10 +22,10 @@ This file is part of BORIS.
|
|
|
23
22
|
|
|
24
23
|
import logging
|
|
25
24
|
|
|
26
|
-
from
|
|
25
|
+
from PySide6.QtCore import Signal
|
|
27
26
|
|
|
28
|
-
# from
|
|
29
|
-
from
|
|
27
|
+
# from PySide6.QtGui import *
|
|
28
|
+
from PySide6.QtWidgets import (
|
|
30
29
|
QApplication,
|
|
31
30
|
QWidget,
|
|
32
31
|
QRadioButton,
|
|
@@ -40,14 +39,14 @@ from PyQt5.QtWidgets import (
|
|
|
40
39
|
QFileDialog,
|
|
41
40
|
QMessageBox,
|
|
42
41
|
)
|
|
43
|
-
from
|
|
44
|
-
from
|
|
42
|
+
from . import dialog
|
|
43
|
+
from . import config as cfg
|
|
45
44
|
|
|
46
45
|
|
|
47
46
|
class wgMeasurement(QWidget):
|
|
48
47
|
""" """
|
|
49
48
|
|
|
50
|
-
closeSignal, clearSignal =
|
|
49
|
+
closeSignal, clearSignal = Signal(), Signal()
|
|
51
50
|
flagSaved = True
|
|
52
51
|
draw_mem = []
|
|
53
52
|
|
|
@@ -112,7 +111,7 @@ class wgMeasurement(QWidget):
|
|
|
112
111
|
self.pbSave = QPushButton("Save results", clicked=self.pbSave_clicked)
|
|
113
112
|
hbox3.addWidget(self.pbSave)
|
|
114
113
|
|
|
115
|
-
self.pbClose = QPushButton(
|
|
114
|
+
self.pbClose = QPushButton(cfg.CLOSE, clicked=self.pbClose_clicked)
|
|
116
115
|
hbox3.addWidget(self.pbClose)
|
|
117
116
|
|
|
118
117
|
vbox.addLayout(hbox3)
|
|
@@ -128,13 +127,13 @@ class wgMeasurement(QWidget):
|
|
|
128
127
|
def pbClose_clicked(self):
|
|
129
128
|
if not self.flagSaved:
|
|
130
129
|
response = dialog.MessageDialog(
|
|
131
|
-
programName,
|
|
130
|
+
cfg.programName,
|
|
132
131
|
"The current results are not saved. Do you want to save results before closing?",
|
|
133
|
-
[YES, NO, CANCEL],
|
|
132
|
+
[cfg.YES, cfg.NO, cfg.CANCEL],
|
|
134
133
|
)
|
|
135
|
-
if response == YES:
|
|
134
|
+
if response == cfg.YES:
|
|
136
135
|
self.pbSave_clicked()
|
|
137
|
-
if response == CANCEL:
|
|
136
|
+
if response == cfg.CANCEL:
|
|
138
137
|
return
|
|
139
138
|
self.closeSignal.emit()
|
|
140
139
|
|
|
@@ -143,19 +142,16 @@ class wgMeasurement(QWidget):
|
|
|
143
142
|
save results
|
|
144
143
|
"""
|
|
145
144
|
if self.pte.toPlainText():
|
|
146
|
-
fileName, _ = QFileDialog().getSaveFileName(
|
|
147
|
-
self, "Save measurement results", "", "Text files (*.txt);;All files (*)"
|
|
148
|
-
)
|
|
145
|
+
fileName, _ = QFileDialog().getSaveFileName(self, "Save measurement results", "", "Text files (*.txt);;All files (*)")
|
|
149
146
|
if fileName:
|
|
150
147
|
with open(fileName, "w") as f:
|
|
151
148
|
f.write(self.pte.toPlainText())
|
|
152
149
|
self.flagSaved = True
|
|
153
150
|
else:
|
|
154
|
-
QMessageBox.information(self, programName, "There are no results to save")
|
|
151
|
+
QMessageBox.information(self, cfg.programName, "There are no results to save")
|
|
155
152
|
|
|
156
153
|
|
|
157
154
|
if __name__ == "__main__":
|
|
158
|
-
|
|
159
155
|
import sys
|
|
160
156
|
|
|
161
157
|
app = QApplication(sys.argv)
|
boris/media_file.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 file is part of BORIS.
|
|
7
7
|
|
|
@@ -20,13 +20,13 @@ This file is part of BORIS.
|
|
|
20
20
|
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
-
import
|
|
23
|
+
from PySide6.QtWidgets import QFileDialog
|
|
24
24
|
|
|
25
25
|
from . import config as cfg
|
|
26
26
|
from . import utilities as util
|
|
27
27
|
from . import dialog
|
|
28
28
|
from . import project_functions
|
|
29
|
-
from
|
|
29
|
+
from . import utilities as util
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
def get_info(self) -> None:
|
|
@@ -34,9 +34,34 @@ def get_info(self) -> None:
|
|
|
34
34
|
show info about media file (current media file if an observation is opened)
|
|
35
35
|
"""
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
def media_analysis_str(ffmpeg_bin: str, media_full_path: str) -> str:
|
|
38
|
+
r = util.accurate_media_analysis(ffmpeg_bin, media_full_path)
|
|
39
|
+
|
|
40
|
+
if "error" in r:
|
|
41
|
+
ffmpeg_output = f"File path: {media_full_path}<br><br>{r['error']}<br><br>"
|
|
42
|
+
else:
|
|
43
|
+
ffmpeg_output = f"<br><b>{r['analysis_program']} analysis</b><br>"
|
|
44
|
+
|
|
45
|
+
ffmpeg_output += (
|
|
46
|
+
f"File path: <b>{media_full_path}</b><br><br>"
|
|
47
|
+
f"Duration: {r['duration']} seconds ({util.convertTime(self.timeFormat, r['duration'])})<br>"
|
|
48
|
+
f"FPS: {r['fps']}<br>"
|
|
49
|
+
f"Resolution: {r['resolution']} pixels<br>"
|
|
50
|
+
f"Format long name: {r.get('format_long_name', cfg.NA)}<br>"
|
|
51
|
+
f"Creation time: {r.get('creation_time', cfg.NA)}<br>"
|
|
52
|
+
f"Number of frames: {r['frames_number']}<br>"
|
|
53
|
+
f"Bitrate: {util.smart_size_format(r['bitrate'])} <br>"
|
|
54
|
+
f"Has video: {r['has_video']}<br>"
|
|
55
|
+
f"Has audio: {r['has_audio']}<br>"
|
|
56
|
+
f"File size: {util.smart_size_format(r.get('file size', cfg.NA))}<br>"
|
|
57
|
+
f"Video codec: {r.get('video_codec', cfg.NA)}<br>"
|
|
58
|
+
f"Audio codec: {r.get('audio_codec', cfg.NA)}<br>"
|
|
59
|
+
)
|
|
38
60
|
|
|
39
|
-
|
|
61
|
+
return ffmpeg_output
|
|
62
|
+
|
|
63
|
+
if self.observationId and self.playerType == cfg.MEDIA:
|
|
64
|
+
tot_output: str = ""
|
|
40
65
|
|
|
41
66
|
for i, dw in enumerate(self.dw_player):
|
|
42
67
|
if not (
|
|
@@ -45,21 +70,14 @@ def get_info(self) -> None:
|
|
|
45
70
|
):
|
|
46
71
|
continue
|
|
47
72
|
|
|
48
|
-
logging.info(f"Video format: {dw.player.video_format}")
|
|
49
|
-
logging.info(f"number of media in media list: {dw.player.playlist_count}")
|
|
50
|
-
logging.info(f"Current time position: {dw.player.time_pos} duration: {dw.player.duration}")
|
|
51
|
-
|
|
52
|
-
logging.info(f"FPS: {dw.player.container_fps}")
|
|
53
|
-
|
|
54
|
-
# logging.info("Rate: {}".format(player.mediaplayer.get_rate()))
|
|
55
|
-
logging.info(f"Video size: {dw.player.width}x{dw.player.height} ratio: ")
|
|
56
|
-
|
|
57
|
-
logging.info(f"Aspect ratio: {round(dw.player.width / dw.player.height, 3)}")
|
|
58
|
-
# logging.info("is seekable? {0}".format(player.mediaplayer.is_seekable()))
|
|
59
|
-
# logging.info("has_vout? {0}".format(player.mediaplayer.has_vout()))
|
|
60
|
-
|
|
61
73
|
mpv_output = (
|
|
62
74
|
"<b>MPV information</b><br>"
|
|
75
|
+
f"Duration: {dw.player.duration} seconds ({util.seconds2time(dw.player.duration)})<br>"
|
|
76
|
+
# "Position: {} %<br>"
|
|
77
|
+
f"FPS: {dw.player.container_fps}<br>"
|
|
78
|
+
# "Rate: {}<br>"
|
|
79
|
+
f"Resolution: {dw.player.width}x{dw.player.height} pixels<br>"
|
|
80
|
+
# "Scale: {}<br>"
|
|
63
81
|
f"Video format: {dw.player.video_format}<br>"
|
|
64
82
|
# "State: {}<br>"
|
|
65
83
|
# "Media Resource Location: {}<br>"
|
|
@@ -67,81 +85,31 @@ def get_info(self) -> None:
|
|
|
67
85
|
# "Track: {}/{}<br>"
|
|
68
86
|
f"Number of media in media list: {dw.player.playlist_count}<br>"
|
|
69
87
|
f"Current time position: {dw.player.time_pos}<br>"
|
|
70
|
-
f"duration: {dw.player.duration}<br>"
|
|
71
|
-
# "Position: {} %<br>"
|
|
72
|
-
f"FPS: {dw.player.container_fps}<br>"
|
|
73
|
-
# "Rate: {}<br>"
|
|
74
|
-
f"Video size: {dw.player.width}x{dw.player.height}<br>"
|
|
75
|
-
# "Scale: {}<br>"
|
|
76
88
|
f"Aspect ratio: {round(dw.player.width / dw.player.height, 3)}<br>"
|
|
77
89
|
# "is seekable? {}<br>"
|
|
78
90
|
# "has_vout? {}<br>"
|
|
79
91
|
)
|
|
80
92
|
|
|
81
93
|
# FFmpeg/FFprobe analysis
|
|
82
|
-
|
|
94
|
+
ffmpeg_output: str = ""
|
|
83
95
|
for file_path in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE][str(i + 1)]:
|
|
84
96
|
media_full_path = project_functions.full_path(file_path, self.projectFileName)
|
|
85
|
-
|
|
86
|
-
nframes = r["frames_number"]
|
|
87
|
-
if "error" in r:
|
|
88
|
-
ffmpeg_output += "File path: {filePath}<br><br>{error}<br><br>".format(
|
|
89
|
-
filePath=media_full_path, error=r["error"]
|
|
90
|
-
)
|
|
91
|
-
else:
|
|
92
|
-
ffmpeg_output = f"<br><b>{r['analysis_program'] } analysis</b><br>"
|
|
93
|
-
|
|
94
|
-
ffmpeg_output += (
|
|
95
|
-
f"File path: <b>{media_full_path}</b><br><br>"
|
|
96
|
-
f"Duration: {r['duration']} seconds ({util.convertTime(self.timeFormat, r['duration'])})<br>"
|
|
97
|
-
f"Resolution: {r['resolution']}<br>"
|
|
98
|
-
f"Number of frames: {r['frames_number']}<br>"
|
|
99
|
-
f"Bitrate: {r['bitrate']} k<br>"
|
|
100
|
-
f"FPS: {r['fps']}<br>"
|
|
101
|
-
f"Has video: {r['has_video']}<br>"
|
|
102
|
-
f"Has audio: {r['has_audio']}<br>"
|
|
103
|
-
f"File size: {r.get('file size', 'NA')}<br>"
|
|
104
|
-
f"Video codec: {r.get('video_codec', 'NA')}<br>"
|
|
105
|
-
f"Audio codec: {r.get('audio_codec', 'NA')}<br>"
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
ffmpeg_output += f"Total duration: {sum(self.dw_player[i].media_durations) / 1000} ({util.convertTime(self.timeFormat, sum(self.dw_player[i].media_durations) / 1000)})"
|
|
97
|
+
ffmpeg_output += media_analysis_str(self.ffmpeg_bin, media_full_path)
|
|
109
98
|
|
|
110
|
-
|
|
99
|
+
ffmpeg_output += f"<br>Total duration: {sum(self.dw_player[i].media_durations) / 1000} ({util.convertTime(self.timeFormat, sum(self.dw_player[i].media_durations) / 1000)})"
|
|
111
100
|
|
|
112
|
-
|
|
113
|
-
self.results.setWindowTitle(cfg.programName + " - Media file information")
|
|
114
|
-
self.results.ptText.appendHtml(tot_output)
|
|
115
|
-
self.results.show()
|
|
101
|
+
tot_output += mpv_output + ffmpeg_output + "<br><hr>"
|
|
116
102
|
|
|
117
103
|
else: # no open observation
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
self.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
self.results.ptText.appendHtml(f"<br><b>{r['analysis_program'] } analysis</b><br>")
|
|
131
|
-
self.results.ptText.appendHtml(
|
|
132
|
-
(
|
|
133
|
-
f"File path: <b>{file_path}</b><br><br>"
|
|
134
|
-
f"Duration: {r['duration']} seconds ({util.convertTime(self.timeFormat, r['duration'])})<br>"
|
|
135
|
-
f"Resolution: {r['resolution']}<br>"
|
|
136
|
-
f"Number of frames: {r['frames_number']}<br>"
|
|
137
|
-
f"Bitrate: {r['bitrate']} k<br>"
|
|
138
|
-
f"FPS: {r['fps']}<br>"
|
|
139
|
-
f"Has video: {r['has_video']}<br>"
|
|
140
|
-
f"Has audio: {r['has_audio']}<br>"
|
|
141
|
-
f"File size: {r.get('file size', 'NA')}<br>"
|
|
142
|
-
f"Video codec: {r.get('video_codec', 'NA')}<br>"
|
|
143
|
-
f"Audio codec: {r.get('audio_codec', 'NA')}<br>"
|
|
144
|
-
)
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
self.results.show()
|
|
104
|
+
file_paths, _ = QFileDialog().getOpenFileNames(self, "Select a media file", "", "Media files (*)")
|
|
105
|
+
if not file_paths:
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
tot_output: str = ""
|
|
109
|
+
for file_path in file_paths:
|
|
110
|
+
tot_output += media_analysis_str(self.ffmpeg_bin, file_path)
|
|
111
|
+
|
|
112
|
+
self.results = dialog.Results_dialog()
|
|
113
|
+
self.results.setWindowTitle(f"{cfg.programName} - Media file information")
|
|
114
|
+
self.results.ptText.appendHtml(tot_output)
|
|
115
|
+
self.results.show()
|
boris/menu_options.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,9 +19,9 @@ Copyright 2012-2023 Olivier Friard
|
|
|
19
19
|
MA 02110-1301, USA.
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
|
|
23
22
|
import logging
|
|
24
23
|
from . import config as cfg
|
|
24
|
+
from PySide6.QtCore import QSize
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
def update_windows_title(self):
|
|
@@ -57,6 +57,13 @@ def update_menu(self):
|
|
|
57
57
|
|
|
58
58
|
update_windows_title(self)
|
|
59
59
|
|
|
60
|
+
self.toolBar.setIconSize(
|
|
61
|
+
QSize(
|
|
62
|
+
self.config_param.get(cfg.TOOLBAR_ICON_SIZE, cfg.DEFAULT_TOOLBAR_ICON_SIZE_VALUE),
|
|
63
|
+
self.config_param.get(cfg.TOOLBAR_ICON_SIZE, cfg.DEFAULT_TOOLBAR_ICON_SIZE_VALUE),
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
|
|
60
67
|
# enabled if project loaded
|
|
61
68
|
for action in (
|
|
62
69
|
self.actionEdit_project,
|
|
@@ -65,9 +72,9 @@ def update_menu(self):
|
|
|
65
72
|
self.actionExport_project,
|
|
66
73
|
self.actionCheck_project,
|
|
67
74
|
self.actionClose_project,
|
|
68
|
-
self.actionSend_project,
|
|
69
75
|
self.actionNew_observation,
|
|
70
76
|
self.actionRemove_path_from_media_files,
|
|
77
|
+
self.action_create_observations,
|
|
71
78
|
self.action_obs_list,
|
|
72
79
|
self.actionExport_observations_list,
|
|
73
80
|
self.actionExplore_project,
|
|
@@ -136,7 +143,10 @@ def update_menu(self):
|
|
|
136
143
|
self.actionJumpForward,
|
|
137
144
|
self.actionJumpBackward,
|
|
138
145
|
self.actionJumpTo,
|
|
146
|
+
self.action_change_time_offset_of_players,
|
|
147
|
+
self.action_deinterlace,
|
|
139
148
|
self.actionZoom_level,
|
|
149
|
+
self.actionRotate_current_video,
|
|
140
150
|
self.actionDisplay_subtitles,
|
|
141
151
|
self.actionPlay,
|
|
142
152
|
self.actionReset,
|
|
@@ -156,12 +166,12 @@ def update_menu(self):
|
|
|
156
166
|
self.menuImage_overlay_on_video_2,
|
|
157
167
|
self.actionAdd_image_overlay_on_video,
|
|
158
168
|
self.actionRemove_image_overlay,
|
|
169
|
+
self.actionAdd_frame_indexes,
|
|
159
170
|
):
|
|
160
|
-
|
|
161
171
|
action.setEnabled(self.playerType == cfg.MEDIA)
|
|
162
172
|
|
|
163
173
|
# geometric measurements
|
|
164
|
-
self.action_geometric_measurements.setEnabled(observation_is_active and self.geometric_measurements_mode
|
|
174
|
+
self.action_geometric_measurements.setEnabled(observation_is_active and self.geometric_measurements_mode is False)
|
|
165
175
|
self.actionCoding_pad.setEnabled(observation_is_active)
|
|
166
176
|
self.actionSubjects_pad.setEnabled(observation_is_active)
|
|
167
177
|
self.actionBehaviors_coding_map.setEnabled(observation_is_active)
|
|
@@ -187,6 +197,7 @@ def update_menu(self):
|
|
|
187
197
|
self.action_behavior_binary_table,
|
|
188
198
|
self.action_advanced_event_filtering,
|
|
189
199
|
self.action_latency,
|
|
200
|
+
self.action_cooccurence,
|
|
190
201
|
self.menuPlot_events,
|
|
191
202
|
self.menuInter_rater_reliability,
|
|
192
203
|
self.menuSimilarities,
|
|
@@ -196,7 +207,7 @@ def update_menu(self):
|
|
|
196
207
|
w.setEnabled(project_contains_obs)
|
|
197
208
|
|
|
198
209
|
# statusbar labels
|
|
199
|
-
for w in
|
|
210
|
+
for w in (self.lbTimeOffset, self.lb_obs_time_interval):
|
|
200
211
|
w.setVisible(self.playerType == cfg.MEDIA)
|
|
201
212
|
|
|
202
213
|
logging.debug("function: menu_options finished")
|