boris-behav-obs 8.16.5__py3-none-any.whl → 9.7.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- boris/__init__.py +1 -1
- boris/__main__.py +1 -1
- boris/about.py +28 -40
- boris/add_modifier.py +88 -80
- boris/add_modifier_ui.py +266 -144
- boris/advanced_event_filtering.py +23 -29
- boris/analysis_plugins/__init__.py +0 -0
- boris/analysis_plugins/_export_to_feral.py +225 -0
- boris/analysis_plugins/_latency.py +59 -0
- boris/analysis_plugins/irr_cohen_kappa.py +109 -0
- boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
- boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
- boris/analysis_plugins/number_of_occurences.py +22 -0
- boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
- boris/analysis_plugins/time_budget.py +61 -0
- boris/behav_coding_map_creator.py +235 -236
- boris/behavior_binary_table.py +33 -50
- boris/behaviors_coding_map.py +17 -18
- boris/boris_cli.py +6 -25
- boris/cmd_arguments.py +12 -1
- boris/coding_pad.py +19 -36
- boris/config.py +109 -50
- boris/config_file.py +58 -67
- boris/connections.py +105 -58
- boris/converters.py +13 -37
- boris/converters_ui.py +187 -110
- boris/cooccurence.py +250 -0
- boris/core.py +2174 -1303
- boris/core_qrc.py +15892 -10829
- boris/core_ui.py +941 -806
- boris/db_functions.py +17 -42
- boris/dev.py +27 -7
- boris/dialog.py +461 -242
- boris/duration_widget.py +9 -14
- boris/edit_event.py +61 -31
- boris/edit_event_ui.py +208 -97
- boris/event_operations.py +405 -281
- boris/events_cursor.py +25 -17
- boris/events_snapshots.py +36 -82
- boris/exclusion_matrix.py +4 -9
- boris/export_events.py +180 -203
- boris/export_observation.py +60 -73
- boris/external_processes.py +123 -98
- boris/geometric_measurement.py +427 -218
- boris/gui_utilities.py +91 -14
- boris/image_overlay.py +4 -4
- boris/import_observations.py +190 -98
- boris/ipc_mpv.py +325 -0
- boris/irr.py +20 -57
- boris/latency.py +31 -24
- boris/measurement_widget.py +14 -18
- boris/media_file.py +17 -19
- boris/menu_options.py +16 -6
- boris/modifier_coding_map_creator.py +1013 -0
- boris/modifiers_coding_map.py +7 -9
- boris/mpv2.py +128 -35
- boris/observation.py +501 -211
- boris/observation_operations.py +1037 -393
- boris/observation_ui.py +573 -363
- boris/observations_list.py +51 -58
- boris/otx_parser.py +74 -68
- boris/param_panel.py +45 -59
- boris/param_panel_ui.py +254 -138
- boris/player_dock_widget.py +91 -56
- boris/plot_data_module.py +20 -53
- boris/plot_events.py +56 -153
- boris/plot_events_rt.py +16 -30
- boris/plot_spectrogram_rt.py +83 -56
- boris/plot_waveform_rt.py +27 -49
- boris/plugins.py +468 -0
- boris/portion/__init__.py +18 -8
- boris/portion/const.py +35 -18
- boris/portion/dict.py +5 -5
- boris/portion/func.py +2 -2
- boris/portion/interval.py +21 -41
- boris/portion/io.py +41 -32
- boris/preferences.py +307 -123
- boris/preferences_ui.py +686 -227
- boris/project.py +294 -271
- boris/project_functions.py +626 -537
- boris/project_import_export.py +204 -213
- boris/project_ui.py +673 -441
- boris/qrc_boris.py +6 -3
- boris/qrc_boris5.py +6 -3
- boris/select_modifiers.py +62 -90
- boris/select_observations.py +19 -197
- boris/select_subj_behav.py +67 -39
- boris/state_events.py +51 -33
- boris/subjects_pad.py +7 -9
- boris/synthetic_time_budget.py +42 -26
- boris/time_budget_functions.py +169 -169
- boris/time_budget_widget.py +77 -89
- boris/transitions.py +41 -41
- boris/utilities.py +594 -226
- boris/version.py +3 -3
- boris/video_equalizer.py +16 -14
- boris/video_equalizer_ui.py +199 -130
- boris/video_operations.py +86 -28
- boris/view_df.py +104 -0
- boris/view_df_ui.py +75 -0
- boris/write_event.py +240 -136
- boris_behav_obs-9.7.12.dist-info/METADATA +139 -0
- boris_behav_obs-9.7.12.dist-info/RECORD +110 -0
- {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.12.dist-info}/WHEEL +1 -1
- boris_behav_obs-9.7.12.dist-info/entry_points.txt +2 -0
- boris/README.TXT +0 -22
- boris/add_modifier.ui +0 -323
- boris/converters.ui +0 -289
- boris/core.qrc +0 -37
- boris/core.ui +0 -1571
- boris/edit_event.ui +0 -233
- boris/icons/logo_eye.ico +0 -0
- boris/map_creator.py +0 -982
- boris/observation.ui +0 -814
- boris/param_panel.ui +0 -379
- boris/preferences.ui +0 -537
- boris/project.ui +0 -1074
- boris/vlc_local.py +0 -90
- boris_behav_obs-8.16.5.dist-info/LICENSE.TXT +0 -674
- boris_behav_obs-8.16.5.dist-info/METADATA +0 -134
- boris_behav_obs-8.16.5.dist-info/RECORD +0 -107
- boris_behav_obs-8.16.5.dist-info/entry_points.txt +0 -2
- {boris → boris_behav_obs-9.7.12.dist-info/licenses}/LICENSE.TXT +0 -0
- {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.12.dist-info}/top_level.txt +0 -0
boris/project_functions.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
|
|
@@ -22,24 +22,25 @@ Copyright 2012-2023 Olivier Friard
|
|
|
22
22
|
import gzip
|
|
23
23
|
import json
|
|
24
24
|
import logging
|
|
25
|
-
import
|
|
26
|
-
import
|
|
25
|
+
import pandas as pd
|
|
26
|
+
import numpy as np
|
|
27
|
+
from pathlib import Path
|
|
27
28
|
import sys
|
|
28
29
|
from decimal import Decimal as dec
|
|
29
30
|
from shutil import copyfile
|
|
30
31
|
from typing import List, Tuple, Dict
|
|
31
32
|
|
|
32
33
|
import tablib
|
|
33
|
-
from
|
|
34
|
-
from
|
|
34
|
+
from PySide6.QtWidgets import QMessageBox, QTableWidgetItem, QAbstractItemView
|
|
35
|
+
from PySide6.QtCore import Qt
|
|
35
36
|
|
|
36
37
|
from . import config as cfg
|
|
37
38
|
from . import db_functions
|
|
38
39
|
from . import dialog
|
|
40
|
+
from . import observation_operations
|
|
39
41
|
from . import portion as I
|
|
40
42
|
from . import utilities as util
|
|
41
43
|
from . import version
|
|
42
|
-
from . import observation_operations
|
|
43
44
|
|
|
44
45
|
|
|
45
46
|
def check_observation_exhaustivity(
|
|
@@ -56,6 +57,9 @@ def check_observation_exhaustivity(
|
|
|
56
57
|
"""
|
|
57
58
|
|
|
58
59
|
def interval_len(interval: I) -> dec:
|
|
60
|
+
""" "
|
|
61
|
+
returns duration of an interval or a set of intervals
|
|
62
|
+
"""
|
|
59
63
|
if interval.empty:
|
|
60
64
|
return dec(0)
|
|
61
65
|
else:
|
|
@@ -69,61 +73,36 @@ def check_observation_exhaustivity(
|
|
|
69
73
|
events_interval[event[cfg.EVENT_SUBJECT_FIELD_IDX]] = {}
|
|
70
74
|
mem_events_interval[event[cfg.EVENT_SUBJECT_FIELD_IDX]] = {}
|
|
71
75
|
|
|
72
|
-
if
|
|
73
|
-
event[cfg.EVENT_BEHAVIOR_FIELD_IDX]
|
|
74
|
-
|
|
75
|
-
):
|
|
76
|
-
events_interval[event[cfg.EVENT_SUBJECT_FIELD_IDX]][
|
|
77
|
-
event[cfg.EVENT_BEHAVIOR_FIELD_IDX]
|
|
78
|
-
] = I.empty()
|
|
79
|
-
mem_events_interval[event[cfg.EVENT_SUBJECT_FIELD_IDX]][
|
|
80
|
-
event[cfg.EVENT_BEHAVIOR_FIELD_IDX]
|
|
81
|
-
] = []
|
|
76
|
+
if event[cfg.EVENT_BEHAVIOR_FIELD_IDX] not in events_interval[event[cfg.EVENT_SUBJECT_FIELD_IDX]]:
|
|
77
|
+
events_interval[event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]] = I.empty()
|
|
78
|
+
mem_events_interval[event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]] = []
|
|
82
79
|
|
|
83
80
|
# state event
|
|
84
81
|
if event[cfg.EVENT_BEHAVIOR_FIELD_IDX] in state_events_list:
|
|
85
|
-
mem_events_interval[event[cfg.EVENT_SUBJECT_FIELD_IDX]][
|
|
86
|
-
event[cfg.
|
|
87
|
-
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
mem_events_interval[event[cfg.EVENT_SUBJECT_FIELD_IDX]][
|
|
91
|
-
|
|
92
|
-
]
|
|
93
|
-
)
|
|
94
|
-
== 2
|
|
95
|
-
):
|
|
96
|
-
events_interval[event[cfg.EVENT_SUBJECT_FIELD_IDX]][
|
|
97
|
-
event[cfg.EVENT_BEHAVIOR_FIELD_IDX]
|
|
98
|
-
] |= I.closedopen(
|
|
99
|
-
mem_events_interval[event[cfg.EVENT_SUBJECT_FIELD_IDX]][
|
|
100
|
-
event[cfg.EVENT_BEHAVIOR_FIELD_IDX]
|
|
101
|
-
][0],
|
|
102
|
-
mem_events_interval[event[cfg.EVENT_SUBJECT_FIELD_IDX]][
|
|
103
|
-
event[cfg.EVENT_BEHAVIOR_FIELD_IDX]
|
|
104
|
-
][1],
|
|
82
|
+
mem_events_interval[event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]].append(
|
|
83
|
+
event[cfg.EVENT_TIME_FIELD_IDX]
|
|
84
|
+
)
|
|
85
|
+
if len(mem_events_interval[event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]]) == 2:
|
|
86
|
+
events_interval[event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]] |= I.closedopen(
|
|
87
|
+
mem_events_interval[event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]][0],
|
|
88
|
+
mem_events_interval[event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]][1],
|
|
105
89
|
)
|
|
106
|
-
mem_events_interval[event[cfg.EVENT_SUBJECT_FIELD_IDX]][
|
|
107
|
-
event[cfg.EVENT_BEHAVIOR_FIELD_IDX]
|
|
108
|
-
] = []
|
|
90
|
+
mem_events_interval[event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]] = []
|
|
109
91
|
# point event
|
|
110
92
|
else:
|
|
111
|
-
events_interval[event[cfg.EVENT_SUBJECT_FIELD_IDX]][
|
|
112
|
-
event[cfg.
|
|
113
|
-
|
|
93
|
+
events_interval[event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]] |= I.singleton(
|
|
94
|
+
event[cfg.EVENT_TIME_FIELD_IDX]
|
|
95
|
+
)
|
|
114
96
|
|
|
115
97
|
if events:
|
|
116
98
|
# coding duration
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
- min(events)[cfg.EVENT_TIME_FIELD_IDX]
|
|
120
|
-
)
|
|
99
|
+
event_timestamps = [event[cfg.EVENT_TIME_FIELD_IDX] for event in events]
|
|
100
|
+
obs_theo_dur = max(event_timestamps) - min(event_timestamps)
|
|
121
101
|
else:
|
|
122
102
|
obs_theo_dur = dec("0")
|
|
123
103
|
|
|
124
104
|
total_duration = 0
|
|
125
105
|
for subject in events_interval:
|
|
126
|
-
|
|
127
106
|
tot_behav_for_subject = I.empty()
|
|
128
107
|
for behav in events_interval[subject]:
|
|
129
108
|
tot_behav_for_subject |= events_interval[subject][behav]
|
|
@@ -136,9 +115,7 @@ def check_observation_exhaustivity(
|
|
|
136
115
|
total_duration += obs_real_dur
|
|
137
116
|
|
|
138
117
|
if len(events_interval) and obs_theo_dur:
|
|
139
|
-
exhausivity_percent = (
|
|
140
|
-
total_duration / (len(events_interval) * obs_theo_dur) * 100
|
|
141
|
-
)
|
|
118
|
+
exhausivity_percent = total_duration / (len(events_interval) * obs_theo_dur) * 100
|
|
142
119
|
else:
|
|
143
120
|
exhausivity_percent = 0
|
|
144
121
|
|
|
@@ -161,9 +138,7 @@ def check_observation_exhaustivity_pictures(obs) -> float:
|
|
|
161
138
|
return "No pictures found"
|
|
162
139
|
|
|
163
140
|
# list of paths of coded images
|
|
164
|
-
coded_images_number = len(
|
|
165
|
-
set([x[cfg.PJ_OBS_FIELDS[cfg.IMAGES][cfg.IMAGE_PATH]] for x in obs[cfg.EVENTS]])
|
|
166
|
-
)
|
|
141
|
+
coded_images_number = len(set([x[cfg.PJ_OBS_FIELDS[cfg.IMAGES][cfg.IMAGE_PATH]] for x in obs[cfg.EVENTS]]))
|
|
167
142
|
|
|
168
143
|
return round(coded_images_number / tot_images_number * 100, 1)
|
|
169
144
|
|
|
@@ -182,17 +157,13 @@ def behavior_category(ethogram: dict) -> Dict[str, str]:
|
|
|
182
157
|
behavioral_category = {}
|
|
183
158
|
for idx in ethogram:
|
|
184
159
|
if cfg.BEHAVIOR_CATEGORY in ethogram[idx]:
|
|
185
|
-
behavioral_category[ethogram[idx][cfg.BEHAVIOR_CODE]] = ethogram[idx][
|
|
186
|
-
cfg.BEHAVIOR_CATEGORY
|
|
187
|
-
]
|
|
160
|
+
behavioral_category[ethogram[idx][cfg.BEHAVIOR_CODE]] = ethogram[idx][cfg.BEHAVIOR_CATEGORY]
|
|
188
161
|
else:
|
|
189
162
|
behavioral_category[ethogram[idx][cfg.BEHAVIOR_CODE]] = ""
|
|
190
163
|
return behavioral_category
|
|
191
164
|
|
|
192
165
|
|
|
193
|
-
def check_if_media_available(
|
|
194
|
-
observation: dict, project_file_name: str
|
|
195
|
-
) -> Tuple[bool, str]:
|
|
166
|
+
def check_if_media_available(observation: dict, project_file_name: str) -> Tuple[bool, str]:
|
|
196
167
|
"""
|
|
197
168
|
check if media files available for media and images observations
|
|
198
169
|
|
|
@@ -227,9 +198,7 @@ def check_if_media_available(
|
|
|
227
198
|
return (False, "Observation type not found")
|
|
228
199
|
|
|
229
200
|
|
|
230
|
-
def check_directories_availability(
|
|
231
|
-
observation: dict, project_file_name: str
|
|
232
|
-
) -> Tuple[bool, str]:
|
|
201
|
+
def check_directories_availability(observation: dict, project_file_name: str) -> Tuple[bool, str]:
|
|
233
202
|
"""
|
|
234
203
|
check if directories are available
|
|
235
204
|
|
|
@@ -256,9 +225,7 @@ def check_coded_behaviors_in_obs_list(pj: dict, observations_list: list) -> bool
|
|
|
256
225
|
check if coded behaviors in a list of observations are defined in the ethogram
|
|
257
226
|
"""
|
|
258
227
|
out = ""
|
|
259
|
-
ethogram_behavior_codes = {
|
|
260
|
-
pj[cfg.ETHOGRAM][idx][cfg.BEHAVIOR_CODE] for idx in pj[cfg.ETHOGRAM]
|
|
261
|
-
}
|
|
228
|
+
ethogram_behavior_codes = {pj[cfg.ETHOGRAM][idx][cfg.BEHAVIOR_CODE] for idx in pj[cfg.ETHOGRAM]}
|
|
262
229
|
behaviors_not_defined = []
|
|
263
230
|
out = "" # will contain the output
|
|
264
231
|
for obs_id in observations_list:
|
|
@@ -278,6 +245,18 @@ def check_coded_behaviors_in_obs_list(pj: dict, observations_list: list) -> bool
|
|
|
278
245
|
return False
|
|
279
246
|
|
|
280
247
|
|
|
248
|
+
def get_modifiers_of_behavior(ethogram, behavior: str) -> list:
|
|
249
|
+
"""
|
|
250
|
+
get all modifiers for a behavior (if any)
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
return [
|
|
254
|
+
[ethogram[x][cfg.MODIFIERS][y]["values"] for y in ethogram[x][cfg.MODIFIERS]]
|
|
255
|
+
for x in ethogram
|
|
256
|
+
if ethogram[x][cfg.BEHAVIOR_CODE] == behavior
|
|
257
|
+
]
|
|
258
|
+
|
|
259
|
+
|
|
281
260
|
def check_coded_behaviors(pj: dict) -> set:
|
|
282
261
|
"""
|
|
283
262
|
check if behaviors coded in events are defined in ethogram for all observations
|
|
@@ -286,13 +265,11 @@ def check_coded_behaviors(pj: dict) -> set:
|
|
|
286
265
|
pj (dict): project dictionary
|
|
287
266
|
|
|
288
267
|
Returns:
|
|
289
|
-
set: behaviors present in observations that are not
|
|
268
|
+
set: behaviors present in observations that are not defined in ethogram
|
|
290
269
|
"""
|
|
291
270
|
|
|
292
271
|
# set of behaviors defined in ethogram
|
|
293
|
-
ethogram_behavior_codes = {
|
|
294
|
-
pj[cfg.ETHOGRAM][idx][cfg.BEHAVIOR_CODE] for idx in pj[cfg.ETHOGRAM]
|
|
295
|
-
}
|
|
272
|
+
ethogram_behavior_codes = {pj[cfg.ETHOGRAM][idx][cfg.BEHAVIOR_CODE] for idx in pj[cfg.ETHOGRAM]}
|
|
296
273
|
behaviors_not_defined = []
|
|
297
274
|
|
|
298
275
|
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
@@ -302,9 +279,7 @@ def check_coded_behaviors(pj: dict) -> set:
|
|
|
302
279
|
return set(sorted(behaviors_not_defined))
|
|
303
280
|
|
|
304
281
|
|
|
305
|
-
def check_state_events_obs(
|
|
306
|
-
obsId: str, ethogram: dict, observation: dict, time_format: str = cfg.HHMMSS
|
|
307
|
-
) -> Tuple[bool, str]:
|
|
282
|
+
def check_state_events_obs(obsId: str, ethogram: dict, observation: dict, time_format: str = cfg.HHMMSS) -> Tuple[bool, str]:
|
|
308
283
|
"""
|
|
309
284
|
check state events for the observation obsId
|
|
310
285
|
check if behaviors in observation are defined in ethogram
|
|
@@ -320,7 +295,7 @@ def check_state_events_obs(
|
|
|
320
295
|
tuple (bool, str): if OK True else False , message
|
|
321
296
|
"""
|
|
322
297
|
|
|
323
|
-
out = ""
|
|
298
|
+
out: str = ""
|
|
324
299
|
|
|
325
300
|
# check if behaviors are defined as "state event"
|
|
326
301
|
event_types = {ethogram[idx]["type"] for idx in ethogram}
|
|
@@ -330,13 +305,11 @@ def check_state_events_obs(
|
|
|
330
305
|
|
|
331
306
|
subjects = [event[cfg.EVENT_SUBJECT_FIELD_IDX] for event in observation[cfg.EVENTS]]
|
|
332
307
|
ethogram_behaviors = {ethogram[idx][cfg.BEHAVIOR_CODE] for idx in ethogram}
|
|
308
|
+
state_behaviors = set(util.state_behavior_codes(ethogram))
|
|
333
309
|
|
|
334
310
|
for subject in sorted(set(subjects)):
|
|
335
|
-
|
|
336
311
|
behaviors = [
|
|
337
|
-
event[cfg.EVENT_BEHAVIOR_FIELD_IDX]
|
|
338
|
-
for event in observation[cfg.EVENTS]
|
|
339
|
-
if event[cfg.EVENT_SUBJECT_FIELD_IDX] == subject
|
|
312
|
+
event[cfg.EVENT_BEHAVIOR_FIELD_IDX] for event in observation[cfg.EVENTS] if event[cfg.EVENT_SUBJECT_FIELD_IDX] == subject
|
|
340
313
|
]
|
|
341
314
|
|
|
342
315
|
for behavior in sorted(set(behaviors)):
|
|
@@ -344,35 +317,35 @@ def check_state_events_obs(
|
|
|
344
317
|
# return (False, "The behaviour <b>{}</b> is not defined in the ethogram.<br>".format(behavior))
|
|
345
318
|
continue
|
|
346
319
|
else:
|
|
347
|
-
if
|
|
348
|
-
|
|
349
|
-
memTime: dict = {}
|
|
350
|
-
for event in [
|
|
351
|
-
event
|
|
352
|
-
for event in observation[cfg.EVENTS]
|
|
353
|
-
if event[cfg.EVENT_BEHAVIOR_FIELD_IDX] == behavior
|
|
354
|
-
and event[cfg.EVENT_SUBJECT_FIELD_IDX] == subject
|
|
355
|
-
]:
|
|
356
|
-
|
|
357
|
-
behav_modif = [
|
|
358
|
-
event[cfg.EVENT_BEHAVIOR_FIELD_IDX],
|
|
359
|
-
event[cfg.EVENT_MODIFIER_FIELD_IDX],
|
|
360
|
-
]
|
|
320
|
+
if behavior not in state_behaviors:
|
|
321
|
+
continue
|
|
361
322
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
)
|
|
323
|
+
lst: list = []
|
|
324
|
+
memTime: dict = {}
|
|
325
|
+
for event in [
|
|
326
|
+
event
|
|
327
|
+
for event in observation[cfg.EVENTS]
|
|
328
|
+
if event[cfg.EVENT_BEHAVIOR_FIELD_IDX] == behavior and event[cfg.EVENT_SUBJECT_FIELD_IDX] == subject
|
|
329
|
+
]:
|
|
330
|
+
behav_modif = [
|
|
331
|
+
event[cfg.EVENT_BEHAVIOR_FIELD_IDX],
|
|
332
|
+
event[cfg.EVENT_MODIFIER_FIELD_IDX],
|
|
333
|
+
]
|
|
334
|
+
|
|
335
|
+
if behav_modif in lst:
|
|
336
|
+
lst.remove(behav_modif)
|
|
337
|
+
del memTime[str(behav_modif)]
|
|
338
|
+
else:
|
|
339
|
+
lst.append(behav_modif)
|
|
340
|
+
memTime[str(behav_modif)] = event[cfg.EVENT_TIME_FIELD_IDX]
|
|
341
|
+
|
|
342
|
+
for event in lst:
|
|
343
|
+
out += (
|
|
344
|
+
f"The behavior <b>{behavior}</b> "
|
|
345
|
+
f"{('(modifier ' + event[1] + ') ') if event[1] else ''} is not PAIRED "
|
|
346
|
+
f'for subject "<b>{subject if subject else cfg.NO_FOCAL_SUBJECT}</b>" at '
|
|
347
|
+
f"<b>{memTime[str(event)] if time_format == cfg.S else util.seconds2time(memTime[str(event)])}</b><br>"
|
|
348
|
+
)
|
|
376
349
|
|
|
377
350
|
return (False, out) if out else (True, "No problem detected")
|
|
378
351
|
|
|
@@ -383,12 +356,12 @@ def check_state_events(pj: dict, observations_list: list) -> Tuple[bool, tuple]:
|
|
|
383
356
|
use check_state_events_obs function
|
|
384
357
|
"""
|
|
385
358
|
|
|
359
|
+
logging.info("Check state events function")
|
|
360
|
+
|
|
386
361
|
out = ""
|
|
387
362
|
not_paired_obs_list = []
|
|
388
363
|
for obs_id in observations_list:
|
|
389
|
-
r, msg = check_state_events_obs(
|
|
390
|
-
obs_id, pj[cfg.ETHOGRAM], pj[cfg.OBSERVATIONS][obs_id]
|
|
391
|
-
)
|
|
364
|
+
r, msg = check_state_events_obs(obs_id, pj[cfg.ETHOGRAM], pj[cfg.OBSERVATIONS][obs_id])
|
|
392
365
|
|
|
393
366
|
if not r:
|
|
394
367
|
out += f"Observation: <strong>{obs_id}</strong><br>{msg}<br>"
|
|
@@ -406,12 +379,12 @@ def check_state_events(pj: dict, observations_list: list) -> Tuple[bool, tuple]:
|
|
|
406
379
|
return True, []
|
|
407
380
|
|
|
408
381
|
# remove observations with unpaired state events
|
|
409
|
-
new_observations_list = [
|
|
410
|
-
x for x in observations_list if x not in not_paired_obs_list
|
|
411
|
-
]
|
|
382
|
+
new_observations_list = [x for x in observations_list if x not in not_paired_obs_list]
|
|
412
383
|
if not new_observations_list:
|
|
413
384
|
QMessageBox.warning(None, cfg.programName, "The observation list is empty")
|
|
414
385
|
|
|
386
|
+
logging.info("Check state events done")
|
|
387
|
+
|
|
415
388
|
return False, new_observations_list # no state events are unpaired
|
|
416
389
|
|
|
417
390
|
|
|
@@ -422,14 +395,16 @@ def check_project_integrity(
|
|
|
422
395
|
media_file_available: bool = True,
|
|
423
396
|
) -> str:
|
|
424
397
|
"""
|
|
425
|
-
check project integrity
|
|
426
|
-
check if behaviors
|
|
427
|
-
check unpaired state events
|
|
428
|
-
check if
|
|
429
|
-
check
|
|
430
|
-
check
|
|
431
|
-
check if media
|
|
432
|
-
check
|
|
398
|
+
check project integrity:
|
|
399
|
+
* check if coded behaviors are defined in ethogram
|
|
400
|
+
* check unpaired state events
|
|
401
|
+
* check if timestamp between -2147483647 and 2147483647 (2**31 - 1)
|
|
402
|
+
* check if behavior belong to behavioral category that do not more exist
|
|
403
|
+
* check for leading and trailing spaces and special chars in modifiers
|
|
404
|
+
* check if media file are available (optional)
|
|
405
|
+
* check if media length available
|
|
406
|
+
* check independent variables
|
|
407
|
+
* check if coded subjects are defined
|
|
433
408
|
|
|
434
409
|
Args:
|
|
435
410
|
pj (dict): BORIS project
|
|
@@ -439,19 +414,24 @@ def check_project_integrity(
|
|
|
439
414
|
|
|
440
415
|
Returns:
|
|
441
416
|
str: message
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
TODO: implement check on order of events (for live and media)
|
|
420
|
+
|
|
442
421
|
"""
|
|
443
|
-
out = ""
|
|
422
|
+
out: str = ""
|
|
444
423
|
|
|
445
424
|
# check if coded behaviors are defined in ethogram
|
|
446
425
|
r = check_coded_behaviors(pj)
|
|
447
426
|
if r:
|
|
448
427
|
out += f"The following behaviors are not defined in the ethogram: <b>{', '.join(r)}</b><br>"
|
|
428
|
+
flag_all_behaviors_defined = False
|
|
429
|
+
else:
|
|
430
|
+
flag_all_behaviors_defined = True
|
|
449
431
|
|
|
450
432
|
# check for unpaired state events
|
|
451
433
|
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
452
|
-
ok, msg = check_state_events_obs(
|
|
453
|
-
obs_id, pj[cfg.ETHOGRAM], pj[cfg.OBSERVATIONS][obs_id], time_format
|
|
454
|
-
)
|
|
434
|
+
ok, msg = check_state_events_obs(obs_id, pj[cfg.ETHOGRAM], pj[cfg.OBSERVATIONS][obs_id], time_format)
|
|
455
435
|
if not ok:
|
|
456
436
|
out += "<br><br>" if out else ""
|
|
457
437
|
out += f"Observation: <b>{obs_id}</b><br>{msg}"
|
|
@@ -460,10 +440,7 @@ def check_project_integrity(
|
|
|
460
440
|
for idx in pj[cfg.ETHOGRAM]:
|
|
461
441
|
if cfg.BEHAVIOR_CATEGORY in pj[cfg.ETHOGRAM][idx]:
|
|
462
442
|
if pj[cfg.ETHOGRAM][idx][cfg.BEHAVIOR_CATEGORY]:
|
|
463
|
-
if
|
|
464
|
-
pj[cfg.ETHOGRAM][idx][cfg.BEHAVIOR_CATEGORY]
|
|
465
|
-
not in pj[cfg.BEHAVIORAL_CATEGORIES]
|
|
466
|
-
):
|
|
443
|
+
if pj[cfg.ETHOGRAM][idx][cfg.BEHAVIOR_CATEGORY] not in pj[cfg.BEHAVIORAL_CATEGORIES]:
|
|
467
444
|
out += "<br><br>" if out else ""
|
|
468
445
|
out += (
|
|
469
446
|
f"The behavior <b>{pj[cfg.ETHOGRAM][idx][cfg.BEHAVIOR_CODE]}</b> belongs "
|
|
@@ -481,37 +458,40 @@ def check_project_integrity(
|
|
|
481
458
|
out += (
|
|
482
459
|
"The following <b>modifier</b> defined in ethogram "
|
|
483
460
|
"has leading/trailing spaces or special chars: "
|
|
484
|
-
f"<b>{util.replace_leading_trailing_chars(modifier_code
|
|
461
|
+
f"<b>{util.replace_leading_trailing_chars(modifier_code, old_char=' ', new_char='█')}</b>"
|
|
485
462
|
)
|
|
486
463
|
|
|
487
464
|
# check if all media are available
|
|
488
465
|
if media_file_available:
|
|
489
466
|
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
490
|
-
ok, msg = check_if_media_available(
|
|
491
|
-
pj[cfg.OBSERVATIONS][obs_id], project_file_name
|
|
492
|
-
)
|
|
467
|
+
ok, msg = check_if_media_available(pj[cfg.OBSERVATIONS][obs_id], project_file_name)
|
|
493
468
|
if not ok:
|
|
494
469
|
out += "<br><br>" if out else ""
|
|
495
470
|
out += f"Observation: <b>{obs_id}</b><br>{msg}"
|
|
496
471
|
|
|
497
|
-
|
|
472
|
+
out_events: str = ""
|
|
498
473
|
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
474
|
+
# check if timestamp between -2147483647 and 2147483647
|
|
475
|
+
for event in pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]:
|
|
476
|
+
timestamp = event[cfg.PJ_OBS_FIELDS[pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE]][cfg.TIME]]
|
|
477
|
+
if not timestamp.is_nan() and not (-2147483647 <= timestamp <= 2147483647):
|
|
478
|
+
out_events += f"Observation: <b>{obs_id}</b><br>The timestamp {timestamp} is not between -2147483647 and 2147483647.<br>"
|
|
499
479
|
|
|
500
|
-
|
|
501
|
-
if
|
|
502
|
-
continue
|
|
503
|
-
|
|
480
|
+
"""
|
|
481
|
+
# check if media length available
|
|
504
482
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.MEDIA:
|
|
505
483
|
for nplayer in cfg.ALL_PLAYERS:
|
|
506
484
|
if nplayer in pj[cfg.OBSERVATIONS][obs_id][cfg.FILE]:
|
|
507
485
|
for media_file in pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][nplayer]:
|
|
508
486
|
try:
|
|
509
|
-
pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO][cfg.LENGTH][
|
|
510
|
-
media_file
|
|
511
|
-
]
|
|
487
|
+
pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO][cfg.LENGTH][media_file]
|
|
512
488
|
except KeyError:
|
|
513
489
|
out += "<br><br>" if out else ""
|
|
514
490
|
out += f"Observation: <b>{obs_id}</b><br>Length not available for media file <b>{media_file}</b>"
|
|
491
|
+
"""
|
|
492
|
+
|
|
493
|
+
out += "<br><br>" if out else ""
|
|
494
|
+
out += out_events
|
|
515
495
|
|
|
516
496
|
# check for leading/trailing spaces/special chars in observation id
|
|
517
497
|
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
@@ -524,10 +504,7 @@ def check_project_integrity(
|
|
|
524
504
|
)
|
|
525
505
|
|
|
526
506
|
# check independent variables present in observations are defined
|
|
527
|
-
defined_var_label = [
|
|
528
|
-
pj[cfg.INDEPENDENT_VARIABLES][idx]["label"]
|
|
529
|
-
for idx in pj.get(cfg.INDEPENDENT_VARIABLES, {})
|
|
530
|
-
]
|
|
507
|
+
defined_var_label = [pj[cfg.INDEPENDENT_VARIABLES][idx]["label"] for idx in pj.get(cfg.INDEPENDENT_VARIABLES, {})]
|
|
531
508
|
not_defined: dict = {}
|
|
532
509
|
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
533
510
|
if cfg.INDEPENDENT_VARIABLES not in pj[cfg.OBSERVATIONS][obs_id]:
|
|
@@ -558,27 +535,87 @@ def check_project_integrity(
|
|
|
558
535
|
]
|
|
559
536
|
)
|
|
560
537
|
|
|
561
|
-
|
|
538
|
+
tmp_out: str = ""
|
|
562
539
|
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
563
540
|
if cfg.INDEPENDENT_VARIABLES not in pj[cfg.OBSERVATIONS][obs_id]:
|
|
564
541
|
continue
|
|
565
542
|
for var_label in pj[cfg.OBSERVATIONS][obs_id][cfg.INDEPENDENT_VARIABLES]:
|
|
566
543
|
if var_label in defined_set_var_label:
|
|
567
|
-
if pj[cfg.OBSERVATIONS][obs_id][cfg.INDEPENDENT_VARIABLES][
|
|
568
|
-
|
|
569
|
-
] not in defined_set_var_label[var_label].split(","):
|
|
570
|
-
|
|
571
|
-
out += (
|
|
544
|
+
if pj[cfg.OBSERVATIONS][obs_id][cfg.INDEPENDENT_VARIABLES][var_label] not in defined_set_var_label[var_label].split(","):
|
|
545
|
+
tmp_out += (
|
|
572
546
|
f"{obs_id}: the <b>{pj[cfg.OBSERVATIONS][obs_id][cfg.INDEPENDENT_VARIABLES][var_label]}</b> value "
|
|
573
547
|
f" is not allowed for {var_label} (choose between {defined_set_var_label[var_label]})<br>"
|
|
574
548
|
)
|
|
549
|
+
if tmp_out:
|
|
550
|
+
out += "<br><br>" if out else ""
|
|
551
|
+
out += tmp_out
|
|
552
|
+
|
|
553
|
+
# check if coded subjects are defined in the subjects list
|
|
554
|
+
tmp_out: str = ""
|
|
555
|
+
subjects_list: list = [pj[cfg.SUBJECTS][x]["name"] for x in pj[cfg.SUBJECTS]]
|
|
556
|
+
coded_subjects = set(util.flatten_list([[y[1] for y in pj[cfg.OBSERVATIONS][x].get(cfg.EVENTS, [])] for x in pj[cfg.OBSERVATIONS]]))
|
|
557
|
+
|
|
558
|
+
for subject in coded_subjects:
|
|
559
|
+
if subject and subject not in subjects_list:
|
|
560
|
+
tmp_out += f"The coded subject <b>{subject}</b> is not defined in the subjects list.<br>You can use the <b>Explore project</b> to fix it.<br><br>"
|
|
561
|
+
if tmp_out:
|
|
562
|
+
out += "<br><br>" if out else ""
|
|
563
|
+
out += tmp_out
|
|
564
|
+
|
|
565
|
+
# check if media file have info in media_info section of project
|
|
566
|
+
tmp_out: str = ""
|
|
567
|
+
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
568
|
+
for player in pj[cfg.OBSERVATIONS][obs_id][cfg.FILE]:
|
|
569
|
+
for media_file in pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][player]:
|
|
570
|
+
for info in (cfg.LENGTH, cfg.FPS, cfg.HAS_AUDIO, cfg.HAS_VIDEO):
|
|
571
|
+
if media_file not in pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO].get(info, {}):
|
|
572
|
+
tmp_out += f"Observation <b>{obs_id}</b>:<br>"
|
|
573
|
+
tmp_out += f"The media file {media_file} has no <b>{info}</b> info.<br>"
|
|
574
|
+
if tmp_out:
|
|
575
|
+
tmp_out += "<br>You should repick the media file to fix this issue."
|
|
576
|
+
out += "<br><br>" if out else ""
|
|
577
|
+
out += tmp_out
|
|
578
|
+
|
|
579
|
+
# check if the number of coded modifiers correspond to the number of sets of modifier
|
|
580
|
+
obs_results: dict = {}
|
|
581
|
+
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
582
|
+
for event_idx, event in enumerate(pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]):
|
|
583
|
+
for idx in pj[cfg.ETHOGRAM]:
|
|
584
|
+
if pj[cfg.ETHOGRAM][idx][cfg.BEHAVIOR_CODE] == event[cfg.EVENT_BEHAVIOR_FIELD_IDX]:
|
|
585
|
+
break
|
|
586
|
+
else:
|
|
587
|
+
# behavior not defined in ethogram
|
|
588
|
+
continue
|
|
589
|
+
|
|
590
|
+
if (not event[cfg.EVENT_MODIFIER_FIELD_IDX]) and not pj[cfg.ETHOGRAM][idx][cfg.MODIFIERS]: # no modifiers
|
|
591
|
+
continue
|
|
592
|
+
|
|
593
|
+
if len(event[cfg.EVENT_MODIFIER_FIELD_IDX].split("|")) != len(pj[cfg.ETHOGRAM][idx][cfg.MODIFIERS]):
|
|
594
|
+
# print("behavior", event[cfg.EVENT_BEHAVIOR_FIELD_IDX])
|
|
595
|
+
# print(f"modifier(s) #{event[cfg.EVENT_MODIFIER_FIELD_IDX]}#", len(event[cfg.EVENT_MODIFIER_FIELD_IDX].split("|")))
|
|
596
|
+
# print(pj[cfg.ETHOGRAM][idx]["code"], pj[cfg.ETHOGRAM][idx][cfg.MODIFIERS])
|
|
597
|
+
# print()
|
|
598
|
+
if obs_id not in obs_results:
|
|
599
|
+
obs_results[obs_id] = []
|
|
600
|
+
|
|
601
|
+
obs_results[obs_id].append(
|
|
602
|
+
(
|
|
603
|
+
f"Event #{event_idx}: the coded modifiers for {event[cfg.EVENT_BEHAVIOR_FIELD_IDX]} are {len(event[cfg.EVENT_MODIFIER_FIELD_IDX].split('|'))} "
|
|
604
|
+
f"but {len(pj[cfg.ETHOGRAM][idx][cfg.MODIFIERS])} sets were defined in ethogram."
|
|
605
|
+
)
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
if obs_results:
|
|
609
|
+
out += "<br><br>" if out else ""
|
|
610
|
+
for o in obs_results:
|
|
611
|
+
out += f"<br>Observation <b>{o}</b>:<br>"
|
|
612
|
+
out += "<br>".join(obs_results[o])
|
|
613
|
+
out += "<br><br>"
|
|
575
614
|
|
|
576
615
|
return out
|
|
577
616
|
|
|
578
617
|
|
|
579
|
-
def create_subtitles(
|
|
580
|
-
pj: dict, selected_observations: list, parameters: dict, export_dir: str
|
|
581
|
-
) -> Tuple[bool, str]:
|
|
618
|
+
def create_subtitles(pj: dict, selected_observations: list, parameters: dict, export_dir: str) -> Tuple[bool, str]:
|
|
582
619
|
"""
|
|
583
620
|
create subtitles for selected observations, subjects and behaviors
|
|
584
621
|
|
|
@@ -608,9 +645,9 @@ def create_subtitles(
|
|
|
608
645
|
return "", ""
|
|
609
646
|
else:
|
|
610
647
|
return (
|
|
611
|
-
f"""<font color="{
|
|
612
|
-
|
|
613
|
-
|
|
648
|
+
f"""<font color="{
|
|
649
|
+
cfg.subtitlesColors[parameters[cfg.SELECTED_SUBJECTS].index(row["subject"]) % len(cfg.subtitlesColors)]
|
|
650
|
+
}">""",
|
|
614
651
|
"</font>",
|
|
615
652
|
)
|
|
616
653
|
|
|
@@ -677,20 +714,12 @@ def create_subtitles(
|
|
|
677
714
|
modifiers_str = f"\n{row['modifiers'].replace('|', ', ')}"
|
|
678
715
|
else:
|
|
679
716
|
modifiers_str = ""
|
|
680
|
-
out += (
|
|
681
|
-
"{idx}\n"
|
|
682
|
-
"{start} --> {stop}\n"
|
|
683
|
-
"{col1}{subject}: {behavior}"
|
|
684
|
-
"{modifiers}"
|
|
685
|
-
"{col2}\n\n"
|
|
686
|
-
).format(
|
|
717
|
+
out += ("{idx}\n{start} --> {stop}\n{col1}{subject}: {behavior}{modifiers}{col2}\n\n").format(
|
|
687
718
|
idx=idx + 1,
|
|
688
719
|
start=util.seconds2time(row["start"]).replace(".", ","),
|
|
689
|
-
stop=util.seconds2time(
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
else row["stop"] + cfg.POINT_EVENT_ST_DURATION
|
|
693
|
-
).replace(".", ","),
|
|
720
|
+
stop=util.seconds2time(row["stop"] if row["type"] == cfg.STATE else row["stop"] + cfg.POINT_EVENT_ST_DURATION).replace(
|
|
721
|
+
".", ","
|
|
722
|
+
),
|
|
694
723
|
col1=col1,
|
|
695
724
|
col2=col2,
|
|
696
725
|
subject=row["subject"],
|
|
@@ -698,14 +727,9 @@ def create_subtitles(
|
|
|
698
727
|
modifiers=modifiers_str,
|
|
699
728
|
)
|
|
700
729
|
|
|
701
|
-
file_name =
|
|
702
|
-
util.safeFileName(obs_id)
|
|
703
|
-
).with_suffix(".srt")
|
|
730
|
+
file_name = Path(export_dir) / Path(util.safeFileName(obs_id)).with_suffix(".srt")
|
|
704
731
|
|
|
705
|
-
if (
|
|
706
|
-
mem_command not in (cfg.OVERWRITE_ALL, cfg.SKIP_ALL)
|
|
707
|
-
and file_name.is_file()
|
|
708
|
-
):
|
|
732
|
+
if mem_command not in (cfg.OVERWRITE_ALL, cfg.SKIP_ALL) and file_name.is_file():
|
|
709
733
|
mem_command = dialog.MessageDialog(
|
|
710
734
|
cfg.programName,
|
|
711
735
|
f"The file {file_name} already exists.",
|
|
@@ -730,19 +754,13 @@ def create_subtitles(
|
|
|
730
754
|
msg += f"observation: {obs_id}\ngave the following error:\n{str(sys.exc_info()[1])}\n"
|
|
731
755
|
|
|
732
756
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.MEDIA:
|
|
733
|
-
|
|
734
757
|
for nplayer in cfg.ALL_PLAYERS:
|
|
735
758
|
if not pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][nplayer]:
|
|
736
759
|
continue
|
|
737
760
|
init = 0
|
|
738
761
|
for media_file in pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][nplayer]:
|
|
739
762
|
try:
|
|
740
|
-
end =
|
|
741
|
-
init
|
|
742
|
-
+ pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO][cfg.LENGTH][
|
|
743
|
-
media_file
|
|
744
|
-
]
|
|
745
|
-
)
|
|
763
|
+
end = init + pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO][cfg.LENGTH][media_file]
|
|
746
764
|
except KeyError:
|
|
747
765
|
return (
|
|
748
766
|
False,
|
|
@@ -760,12 +778,8 @@ def create_subtitles(
|
|
|
760
778
|
"AND behavior in ({}) "
|
|
761
779
|
"ORDER BY start"
|
|
762
780
|
).format(
|
|
763
|
-
",".join(
|
|
764
|
-
|
|
765
|
-
),
|
|
766
|
-
",".join(
|
|
767
|
-
["?"] * len(parameters[cfg.SELECTED_BEHAVIORS])
|
|
768
|
-
),
|
|
781
|
+
",".join(["?"] * len(parameters[cfg.SELECTED_SUBJECTS])),
|
|
782
|
+
",".join(["?"] * len(parameters[cfg.SELECTED_BEHAVIORS])),
|
|
769
783
|
),
|
|
770
784
|
[
|
|
771
785
|
obs_id,
|
|
@@ -777,7 +791,6 @@ def create_subtitles(
|
|
|
777
791
|
)
|
|
778
792
|
|
|
779
793
|
else: # arbitrary 'time interval'
|
|
780
|
-
|
|
781
794
|
cursor.execute(
|
|
782
795
|
(
|
|
783
796
|
"SELECT subject, behavior, type, start, stop, modifiers FROM aggregated_events "
|
|
@@ -788,12 +801,8 @@ def create_subtitles(
|
|
|
788
801
|
"AND behavior in ({}) "
|
|
789
802
|
"ORDER BY start"
|
|
790
803
|
).format(
|
|
791
|
-
",".join(
|
|
792
|
-
|
|
793
|
-
),
|
|
794
|
-
",".join(
|
|
795
|
-
["?"] * len(parameters[cfg.SELECTED_BEHAVIORS])
|
|
796
|
-
),
|
|
804
|
+
",".join(["?"] * len(parameters[cfg.SELECTED_SUBJECTS])),
|
|
805
|
+
",".join(["?"] * len(parameters[cfg.SELECTED_BEHAVIORS])),
|
|
797
806
|
),
|
|
798
807
|
[
|
|
799
808
|
obs_id,
|
|
@@ -813,24 +822,11 @@ def create_subtitles(
|
|
|
813
822
|
else:
|
|
814
823
|
modifiers_str = ""
|
|
815
824
|
|
|
816
|
-
out += (
|
|
817
|
-
"{idx}\n"
|
|
818
|
-
"{start} --> {stop}\n"
|
|
819
|
-
"{col1}{subject}: {behavior}"
|
|
820
|
-
"{modifiers}"
|
|
821
|
-
"{col2}\n\n"
|
|
822
|
-
).format(
|
|
825
|
+
out += ("{idx}\n{start} --> {stop}\n{col1}{subject}: {behavior}{modifiers}{col2}\n\n").format(
|
|
823
826
|
idx=idx + 1,
|
|
824
|
-
start=util.seconds2time(row["start"] - init).replace(
|
|
825
|
-
".", ","
|
|
826
|
-
),
|
|
827
|
+
start=util.seconds2time(row["start"] - init).replace(".", ","),
|
|
827
828
|
stop=util.seconds2time(
|
|
828
|
-
(
|
|
829
|
-
row["stop"]
|
|
830
|
-
if row["type"] == cfg.STATE
|
|
831
|
-
else row["stop"] + cfg.POINT_EVENT_ST_DURATION
|
|
832
|
-
)
|
|
833
|
-
- init
|
|
829
|
+
(row["stop"] if row["type"] == cfg.STATE else row["stop"] + cfg.POINT_EVENT_ST_DURATION) - init
|
|
834
830
|
).replace(".", ","),
|
|
835
831
|
col1=col1,
|
|
836
832
|
col2=col2,
|
|
@@ -838,14 +834,9 @@ def create_subtitles(
|
|
|
838
834
|
behavior=row["behavior"],
|
|
839
835
|
modifiers=modifiers_str,
|
|
840
836
|
)
|
|
841
|
-
file_name =
|
|
842
|
-
pl.Path(media_file).stem
|
|
843
|
-
).with_suffix(".srt")
|
|
837
|
+
file_name = Path(export_dir) / Path(Path(media_file).stem).with_suffix(".srt")
|
|
844
838
|
|
|
845
|
-
if (
|
|
846
|
-
mem_command not in (cfg.OVERWRITE_ALL, cfg.SKIP_ALL)
|
|
847
|
-
and file_name.is_file()
|
|
848
|
-
):
|
|
839
|
+
if mem_command not in (cfg.OVERWRITE_ALL, cfg.SKIP_ALL) and file_name.is_file():
|
|
849
840
|
mem_command = dialog.MessageDialog(
|
|
850
841
|
cfg.programName,
|
|
851
842
|
f"The file {file_name} already exists.",
|
|
@@ -873,9 +864,7 @@ def create_subtitles(
|
|
|
873
864
|
return flag_ok, msg
|
|
874
865
|
|
|
875
866
|
|
|
876
|
-
def export_observations_list(
|
|
877
|
-
pj: dict, selected_observations: list, file_name: str, output_format: str
|
|
878
|
-
) -> bool:
|
|
867
|
+
def export_observations_list(pj: dict, selected_observations: list, file_name: str, output_format: str) -> bool:
|
|
879
868
|
"""
|
|
880
869
|
create file with a list of selected observations
|
|
881
870
|
|
|
@@ -905,17 +894,7 @@ def export_observations_list(
|
|
|
905
894
|
data.headers.extend(indep_var_header)
|
|
906
895
|
|
|
907
896
|
for obs_id in selected_observations:
|
|
908
|
-
|
|
909
|
-
subjects_list = sorted(
|
|
910
|
-
list(
|
|
911
|
-
set(
|
|
912
|
-
[
|
|
913
|
-
x[cfg.EVENT_SUBJECT_FIELD_IDX]
|
|
914
|
-
for x in pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]
|
|
915
|
-
]
|
|
916
|
-
)
|
|
917
|
-
)
|
|
918
|
-
)
|
|
897
|
+
subjects_list = sorted(list(set([x[cfg.EVENT_SUBJECT_FIELD_IDX] for x in pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]])))
|
|
919
898
|
if "" in subjects_list:
|
|
920
899
|
subjects_list = [cfg.NO_FOCAL_SUBJECT] + subjects_list
|
|
921
900
|
subjects_list.remove("")
|
|
@@ -935,11 +914,7 @@ def export_observations_list(
|
|
|
935
914
|
if cfg.INDEPENDENT_VARIABLES in pj[cfg.OBSERVATIONS][obs_id]:
|
|
936
915
|
for var_label in indep_var_header:
|
|
937
916
|
if var_label in pj[cfg.OBSERVATIONS][obs_id][cfg.INDEPENDENT_VARIABLES]:
|
|
938
|
-
indep_var.append(
|
|
939
|
-
pj[cfg.OBSERVATIONS][obs_id][cfg.INDEPENDENT_VARIABLES][
|
|
940
|
-
var_label
|
|
941
|
-
]
|
|
942
|
-
)
|
|
917
|
+
indep_var.append(pj[cfg.OBSERVATIONS][obs_id][cfg.INDEPENDENT_VARIABLES][var_label])
|
|
943
918
|
else:
|
|
944
919
|
indep_var.append("")
|
|
945
920
|
|
|
@@ -954,13 +929,13 @@ def export_observations_list(
|
|
|
954
929
|
+ indep_var
|
|
955
930
|
)
|
|
956
931
|
|
|
957
|
-
if output_format in
|
|
932
|
+
if output_format in (cfg.TSV_EXT, cfg.CSV_EXT, cfg.HTML_EXT):
|
|
958
933
|
try:
|
|
959
934
|
with open(file_name, "wb") as f:
|
|
960
935
|
f.write(str.encode(data.export(output_format)))
|
|
961
936
|
except Exception:
|
|
962
937
|
return False
|
|
963
|
-
if output_format in [
|
|
938
|
+
if output_format in [cfg.ODS_EXT, cfg.XLS_EXT, cfg.XLSX_EXT]:
|
|
964
939
|
try:
|
|
965
940
|
with open(file_name, "wb") as f:
|
|
966
941
|
f.write(data.export(output_format))
|
|
@@ -987,15 +962,9 @@ def set_media_paths_relative_to_project_dir(pj: dict, project_file_name: str) ->
|
|
|
987
962
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.IMAGES:
|
|
988
963
|
for img_dir in pj[cfg.OBSERVATIONS][obs_id][cfg.DIRECTORIES_LIST]:
|
|
989
964
|
try:
|
|
990
|
-
|
|
965
|
+
Path(img_dir).relative_to(Path(project_file_name).parent)
|
|
991
966
|
except ValueError:
|
|
992
|
-
if (
|
|
993
|
-
pl.Path(img_dir).is_absolute()
|
|
994
|
-
or not (
|
|
995
|
-
pl.Path(project_file_name).parent / pl.Path(img_dir)
|
|
996
|
-
).is_dir()
|
|
997
|
-
):
|
|
998
|
-
|
|
967
|
+
if Path(img_dir).is_absolute() or not (Path(project_file_name).parent / Path(img_dir)).is_dir():
|
|
999
968
|
QMessageBox.critical(
|
|
1000
969
|
None,
|
|
1001
970
|
cfg.programName,
|
|
@@ -1006,23 +975,11 @@ def set_media_paths_relative_to_project_dir(pj: dict, project_file_name: str) ->
|
|
|
1006
975
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.MEDIA:
|
|
1007
976
|
for n_player in cfg.ALL_PLAYERS:
|
|
1008
977
|
if n_player in pj[cfg.OBSERVATIONS][obs_id][cfg.FILE]:
|
|
1009
|
-
for idx, media_file in enumerate(
|
|
1010
|
-
pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][n_player]
|
|
1011
|
-
):
|
|
978
|
+
for idx, media_file in enumerate(pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][n_player]):
|
|
1012
979
|
try:
|
|
1013
|
-
|
|
1014
|
-
pl.Path(project_file_name).parent
|
|
1015
|
-
)
|
|
980
|
+
Path(media_file).relative_to(Path(project_file_name).parent)
|
|
1016
981
|
except ValueError:
|
|
1017
|
-
|
|
1018
|
-
if (
|
|
1019
|
-
pl.Path(media_file).is_absolute()
|
|
1020
|
-
or not (
|
|
1021
|
-
pl.Path(project_file_name).parent
|
|
1022
|
-
/ pl.Path(media_file)
|
|
1023
|
-
).is_file()
|
|
1024
|
-
):
|
|
1025
|
-
|
|
982
|
+
if Path(media_file).is_absolute() or not (Path(project_file_name).parent / Path(media_file)).is_file():
|
|
1026
983
|
QMessageBox.critical(
|
|
1027
984
|
None,
|
|
1028
985
|
cfg.programName,
|
|
@@ -1036,25 +993,13 @@ def set_media_paths_relative_to_project_dir(pj: dict, project_file_name: str) ->
|
|
|
1036
993
|
# set media path and image dir relative to project dir
|
|
1037
994
|
flag_changed = False
|
|
1038
995
|
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
1039
|
-
|
|
1040
996
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.IMAGES:
|
|
1041
997
|
new_dir_list = []
|
|
1042
998
|
for img_dir in pj[cfg.OBSERVATIONS][obs_id][cfg.DIRECTORIES_LIST]:
|
|
1043
999
|
try:
|
|
1044
|
-
new_dir_list.append(
|
|
1045
|
-
str(
|
|
1046
|
-
pl.Path(img_dir).relative_to(
|
|
1047
|
-
pl.Path(project_file_name).parent
|
|
1048
|
-
)
|
|
1049
|
-
)
|
|
1050
|
-
)
|
|
1000
|
+
new_dir_list.append(str(Path(img_dir).relative_to(Path(project_file_name).parent)))
|
|
1051
1001
|
except ValueError:
|
|
1052
|
-
if (
|
|
1053
|
-
not pl.Path(img_dir).is_absolute()
|
|
1054
|
-
and (
|
|
1055
|
-
pl.Path(project_file_name).parent / pl.Path(img_dir)
|
|
1056
|
-
).is_dir()
|
|
1057
|
-
):
|
|
1002
|
+
if not Path(img_dir).is_absolute() and (Path(project_file_name).parent / Path(img_dir)).is_dir():
|
|
1058
1003
|
new_dir_list.append(img_dir)
|
|
1059
1004
|
|
|
1060
1005
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.DIRECTORIES_LIST] != new_dir_list:
|
|
@@ -1064,23 +1009,11 @@ def set_media_paths_relative_to_project_dir(pj: dict, project_file_name: str) ->
|
|
|
1064
1009
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.MEDIA:
|
|
1065
1010
|
for n_player in cfg.ALL_PLAYERS:
|
|
1066
1011
|
if n_player in pj[cfg.OBSERVATIONS][obs_id][cfg.FILE]:
|
|
1067
|
-
for idx, media_file in enumerate(
|
|
1068
|
-
pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][n_player]
|
|
1069
|
-
):
|
|
1012
|
+
for idx, media_file in enumerate(pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][n_player]):
|
|
1070
1013
|
try:
|
|
1071
|
-
p = str(
|
|
1072
|
-
pl.Path(media_file).relative_to(
|
|
1073
|
-
pl.Path(project_file_name).parent
|
|
1074
|
-
)
|
|
1075
|
-
)
|
|
1014
|
+
p = str(Path(media_file).relative_to(Path(project_file_name).parent))
|
|
1076
1015
|
except ValueError:
|
|
1077
|
-
if (
|
|
1078
|
-
not pl.Path(media_file).is_absolute()
|
|
1079
|
-
and (
|
|
1080
|
-
pl.Path(project_file_name).parent
|
|
1081
|
-
/ pl.Path(media_file)
|
|
1082
|
-
).is_file()
|
|
1083
|
-
):
|
|
1016
|
+
if not Path(media_file).is_absolute() and (Path(project_file_name).parent / Path(media_file)).is_file():
|
|
1084
1017
|
p = media_file
|
|
1085
1018
|
if p != media_file:
|
|
1086
1019
|
flag_changed = True
|
|
@@ -1093,27 +1026,15 @@ def set_media_paths_relative_to_project_dir(pj: dict, project_file_name: str) ->
|
|
|
1093
1026
|
cfg.FPS,
|
|
1094
1027
|
]:
|
|
1095
1028
|
if (
|
|
1096
|
-
info
|
|
1097
|
-
in pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO]
|
|
1098
|
-
and media_file
|
|
1099
|
-
in pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO][
|
|
1100
|
-
info
|
|
1101
|
-
]
|
|
1029
|
+
info in pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO]
|
|
1030
|
+
and media_file in pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO][info]
|
|
1102
1031
|
):
|
|
1103
1032
|
# add new file path
|
|
1104
|
-
pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO][
|
|
1105
|
-
info
|
|
1106
|
-
][p] = pj[cfg.OBSERVATIONS][obs_id][
|
|
1107
|
-
cfg.MEDIA_INFO
|
|
1108
|
-
][
|
|
1109
|
-
info
|
|
1110
|
-
][
|
|
1111
|
-
media_file
|
|
1112
|
-
]
|
|
1113
|
-
# remove old path
|
|
1114
|
-
del pj[cfg.OBSERVATIONS][obs_id][
|
|
1033
|
+
pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO][info][p] = pj[cfg.OBSERVATIONS][obs_id][
|
|
1115
1034
|
cfg.MEDIA_INFO
|
|
1116
1035
|
][info][media_file]
|
|
1036
|
+
# remove old path
|
|
1037
|
+
del pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO][info][media_file]
|
|
1117
1038
|
return flag_changed
|
|
1118
1039
|
|
|
1119
1040
|
|
|
@@ -1133,18 +1054,10 @@ def set_data_paths_relative_to_project_dir(pj: dict, project_file_name: str) ->
|
|
|
1133
1054
|
for _, v in pj[cfg.OBSERVATIONS][obs_id].get(cfg.PLOT_DATA, {}).items():
|
|
1134
1055
|
if cfg.FILE_PATH in v:
|
|
1135
1056
|
try:
|
|
1136
|
-
|
|
1137
|
-
pl.Path(project_file_name).parent
|
|
1138
|
-
)
|
|
1057
|
+
Path(v[cfg.FILE_PATH]).relative_to(Path(project_file_name).parent)
|
|
1139
1058
|
except ValueError:
|
|
1140
1059
|
# check if file is in project dir
|
|
1141
|
-
if (
|
|
1142
|
-
pl.Path(v[cfg.FILE_PATH]).is_absolute()
|
|
1143
|
-
or not (
|
|
1144
|
-
pl.Path(project_file_name).parent
|
|
1145
|
-
/ pl.Path(v[cfg.FILE_PATH])
|
|
1146
|
-
).is_file()
|
|
1147
|
-
):
|
|
1060
|
+
if Path(v[cfg.FILE_PATH]).is_absolute() or not (Path(project_file_name).parent / Path(v[cfg.FILE_PATH])).is_file():
|
|
1148
1061
|
QMessageBox.critical(
|
|
1149
1062
|
None,
|
|
1150
1063
|
cfg.programName,
|
|
@@ -1158,26 +1071,15 @@ def set_data_paths_relative_to_project_dir(pj: dict, project_file_name: str) ->
|
|
|
1158
1071
|
|
|
1159
1072
|
flag_changed = False
|
|
1160
1073
|
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
1161
|
-
|
|
1162
1074
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] != cfg.MEDIA:
|
|
1163
1075
|
continue
|
|
1164
1076
|
for idx, v in pj[cfg.OBSERVATIONS][obs_id].get(cfg.PLOT_DATA, {}).items():
|
|
1165
1077
|
if cfg.FILE_PATH in v:
|
|
1166
1078
|
try:
|
|
1167
|
-
p = str(
|
|
1168
|
-
pl.Path(v[cfg.FILE_PATH]).relative_to(
|
|
1169
|
-
pl.Path(project_file_name).parent
|
|
1170
|
-
)
|
|
1171
|
-
)
|
|
1079
|
+
p = str(Path(v[cfg.FILE_PATH]).relative_to(Path(project_file_name).parent))
|
|
1172
1080
|
except ValueError:
|
|
1173
1081
|
# check if file is in project dir
|
|
1174
|
-
if (
|
|
1175
|
-
not pl.Path(v[cfg.FILE_PATH]).is_absolute()
|
|
1176
|
-
and (
|
|
1177
|
-
pl.Path(project_file_name).parent
|
|
1178
|
-
/ pl.Path(v[cfg.FILE_PATH])
|
|
1179
|
-
).is_file()
|
|
1180
|
-
):
|
|
1082
|
+
if not Path(v[cfg.FILE_PATH]).is_absolute() and (Path(project_file_name).parent / Path(v[cfg.FILE_PATH])).is_file():
|
|
1181
1083
|
p = v[cfg.FILE_PATH]
|
|
1182
1084
|
|
|
1183
1085
|
if p != v[cfg.FILE_PATH]:
|
|
@@ -1199,26 +1101,14 @@ def remove_data_files_path(pj: dict) -> None:
|
|
|
1199
1101
|
"""
|
|
1200
1102
|
|
|
1201
1103
|
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
1202
|
-
|
|
1203
1104
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] != cfg.MEDIA:
|
|
1204
1105
|
continue
|
|
1205
1106
|
if cfg.PLOT_DATA in pj[cfg.OBSERVATIONS][obs_id]:
|
|
1206
1107
|
for idx in pj[cfg.OBSERVATIONS][obs_id][cfg.PLOT_DATA]:
|
|
1207
1108
|
if "file_path" in pj[cfg.OBSERVATIONS][obs_id][cfg.PLOT_DATA][idx]:
|
|
1208
|
-
p = str(
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
"file_path"
|
|
1212
|
-
]
|
|
1213
|
-
).name
|
|
1214
|
-
)
|
|
1215
|
-
if (
|
|
1216
|
-
p
|
|
1217
|
-
!= pj[cfg.OBSERVATIONS][obs_id][cfg.PLOT_DATA][idx]["file_path"]
|
|
1218
|
-
):
|
|
1219
|
-
pj[cfg.OBSERVATIONS][obs_id][cfg.PLOT_DATA][idx][
|
|
1220
|
-
"file_path"
|
|
1221
|
-
] = p
|
|
1109
|
+
p = str(Path(pj[cfg.OBSERVATIONS][obs_id][cfg.PLOT_DATA][idx]["file_path"]).name)
|
|
1110
|
+
if p != pj[cfg.OBSERVATIONS][obs_id][cfg.PLOT_DATA][idx]["file_path"]:
|
|
1111
|
+
pj[cfg.OBSERVATIONS][obs_id][cfg.PLOT_DATA][idx]["file_path"] = p
|
|
1222
1112
|
|
|
1223
1113
|
|
|
1224
1114
|
def remove_media_files_path(pj: dict, project_file_name: str) -> bool:
|
|
@@ -1236,19 +1126,16 @@ def remove_media_files_path(pj: dict, project_file_name: str) -> bool:
|
|
|
1236
1126
|
file_not_found = []
|
|
1237
1127
|
# check if media and images dir
|
|
1238
1128
|
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
1239
|
-
|
|
1240
1129
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.IMAGES:
|
|
1241
1130
|
for img_dir in pj[cfg.OBSERVATIONS][obs_id][cfg.DIRECTORIES_LIST]:
|
|
1242
|
-
if full_path(
|
|
1131
|
+
if full_path(Path(img_dir).name, project_file_name) == "":
|
|
1243
1132
|
file_not_found.append(img_dir)
|
|
1244
1133
|
|
|
1245
1134
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.MEDIA:
|
|
1246
1135
|
for n_player in cfg.ALL_PLAYERS:
|
|
1247
1136
|
if n_player in pj[cfg.OBSERVATIONS][obs_id][cfg.FILE]:
|
|
1248
|
-
for idx, media_file in enumerate(
|
|
1249
|
-
|
|
1250
|
-
):
|
|
1251
|
-
if full_path(pl.Path(media_file).name, project_file_name) == "":
|
|
1137
|
+
for idx, media_file in enumerate(pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][n_player]):
|
|
1138
|
+
if full_path(Path(media_file).name, project_file_name) == "":
|
|
1252
1139
|
file_not_found.append(media_file)
|
|
1253
1140
|
|
|
1254
1141
|
file_not_found = set(file_not_found)
|
|
@@ -1269,22 +1156,19 @@ def remove_media_files_path(pj: dict, project_file_name: str) -> bool:
|
|
|
1269
1156
|
|
|
1270
1157
|
flag_changed = False
|
|
1271
1158
|
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
1272
|
-
|
|
1273
1159
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.IMAGES:
|
|
1274
1160
|
new_img_dir_list = []
|
|
1275
1161
|
for img_dir in pj[cfg.OBSERVATIONS][obs_id][cfg.DIRECTORIES_LIST]:
|
|
1276
|
-
if img_dir !=
|
|
1162
|
+
if img_dir != Path(img_dir).name:
|
|
1277
1163
|
flag_changed = True
|
|
1278
|
-
new_img_dir_list.append(str(
|
|
1164
|
+
new_img_dir_list.append(str(Path(img_dir).name))
|
|
1279
1165
|
pj[cfg.OBSERVATIONS][obs_id][cfg.DIRECTORIES_LIST] = new_img_dir_list
|
|
1280
1166
|
|
|
1281
1167
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.MEDIA:
|
|
1282
1168
|
for n_player in cfg.ALL_PLAYERS:
|
|
1283
1169
|
if n_player in pj[cfg.OBSERVATIONS][obs_id][cfg.FILE]:
|
|
1284
|
-
for idx, media_file in enumerate(
|
|
1285
|
-
|
|
1286
|
-
):
|
|
1287
|
-
p = pl.Path(media_file).name
|
|
1170
|
+
for idx, media_file in enumerate(pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][n_player]):
|
|
1171
|
+
p = Path(media_file).name
|
|
1288
1172
|
if p != media_file:
|
|
1289
1173
|
flag_changed = True
|
|
1290
1174
|
pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][n_player][idx] = p
|
|
@@ -1296,27 +1180,15 @@ def remove_media_files_path(pj: dict, project_file_name: str) -> bool:
|
|
|
1296
1180
|
cfg.FPS,
|
|
1297
1181
|
]:
|
|
1298
1182
|
if (
|
|
1299
|
-
info
|
|
1300
|
-
in pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO]
|
|
1301
|
-
and media_file
|
|
1302
|
-
in pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO][
|
|
1303
|
-
info
|
|
1304
|
-
]
|
|
1183
|
+
info in pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO]
|
|
1184
|
+
and media_file in pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO][info]
|
|
1305
1185
|
):
|
|
1306
1186
|
# add new file path
|
|
1307
|
-
pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO][
|
|
1308
|
-
info
|
|
1309
|
-
][p] = pj[cfg.OBSERVATIONS][obs_id][
|
|
1310
|
-
cfg.MEDIA_INFO
|
|
1311
|
-
][
|
|
1312
|
-
info
|
|
1313
|
-
][
|
|
1314
|
-
media_file
|
|
1315
|
-
]
|
|
1316
|
-
# remove old path
|
|
1317
|
-
del pj[cfg.OBSERVATIONS][obs_id][
|
|
1187
|
+
pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO][info][p] = pj[cfg.OBSERVATIONS][obs_id][
|
|
1318
1188
|
cfg.MEDIA_INFO
|
|
1319
1189
|
][info][media_file]
|
|
1190
|
+
# remove old path
|
|
1191
|
+
del pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO][info][media_file]
|
|
1320
1192
|
|
|
1321
1193
|
return flag_changed
|
|
1322
1194
|
|
|
@@ -1324,7 +1196,7 @@ def remove_media_files_path(pj: dict, project_file_name: str) -> bool:
|
|
|
1324
1196
|
def full_path(path: str, project_file_name: str) -> str:
|
|
1325
1197
|
"""
|
|
1326
1198
|
returns the media/data full path or the images directory full path
|
|
1327
|
-
add path of BORIS project if media/data with relative path
|
|
1199
|
+
add path of BORIS project if media/data/pictures dir with relative path
|
|
1328
1200
|
|
|
1329
1201
|
Args:
|
|
1330
1202
|
path (str): file path or images directory path
|
|
@@ -1334,12 +1206,12 @@ def full_path(path: str, project_file_name: str) -> str:
|
|
|
1334
1206
|
str: full path
|
|
1335
1207
|
"""
|
|
1336
1208
|
|
|
1337
|
-
source_path =
|
|
1209
|
+
source_path = Path(path)
|
|
1338
1210
|
if source_path.exists():
|
|
1339
1211
|
return str(source_path)
|
|
1340
1212
|
else:
|
|
1341
1213
|
# check relative path (to project path)
|
|
1342
|
-
project_path =
|
|
1214
|
+
project_path = Path(project_file_name)
|
|
1343
1215
|
if (project_path.parent / source_path).exists():
|
|
1344
1216
|
return str(project_path.parent / source_path)
|
|
1345
1217
|
else:
|
|
@@ -1358,22 +1230,22 @@ def observed_interval(observation: dict) -> Tuple[dec, dec]:
|
|
|
1358
1230
|
"""
|
|
1359
1231
|
if not observation[cfg.EVENTS]:
|
|
1360
1232
|
return (dec("0.0"), dec("0.0"))
|
|
1233
|
+
|
|
1361
1234
|
if observation[cfg.TYPE] in (cfg.MEDIA, cfg.LIVE):
|
|
1235
|
+
event_timestamp = [event[cfg.PJ_OBS_FIELDS[observation[cfg.TYPE]][cfg.TIME]] for event in observation[cfg.EVENTS]]
|
|
1236
|
+
|
|
1362
1237
|
return (
|
|
1363
|
-
min(
|
|
1364
|
-
|
|
1365
|
-
],
|
|
1366
|
-
max(observation[cfg.EVENTS])[
|
|
1367
|
-
cfg.PJ_OBS_FIELDS[observation[cfg.TYPE]][cfg.TIME]
|
|
1368
|
-
],
|
|
1238
|
+
min(event_timestamp),
|
|
1239
|
+
max(event_timestamp),
|
|
1369
1240
|
)
|
|
1370
1241
|
if observation[cfg.TYPE] == cfg.IMAGES:
|
|
1371
|
-
events = [
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1242
|
+
events = [x[cfg.PJ_OBS_FIELDS[observation[cfg.TYPE]][cfg.IMAGE_INDEX]] for x in observation[cfg.EVENTS]]
|
|
1243
|
+
# test if indexes contain NA
|
|
1244
|
+
try:
|
|
1245
|
+
dec(min(events))
|
|
1246
|
+
return (dec(min(events)), dec(max(events)))
|
|
1247
|
+
except Exception:
|
|
1248
|
+
return (dec("NaN"), dec("NaN"))
|
|
1377
1249
|
|
|
1378
1250
|
|
|
1379
1251
|
def events_start_stop(ethogram: dict, events: list, obs_type: str) -> List[tuple]:
|
|
@@ -1391,7 +1263,6 @@ def events_start_stop(ethogram: dict, events: list, obs_type: str) -> List[tuple
|
|
|
1391
1263
|
|
|
1392
1264
|
events_flagged: list = []
|
|
1393
1265
|
for idx, event in enumerate(events):
|
|
1394
|
-
|
|
1395
1266
|
_, subject, code, modifier = event[: cfg.EVENT_MODIFIER_FIELD_IDX + 1]
|
|
1396
1267
|
|
|
1397
1268
|
# check if code is state
|
|
@@ -1441,11 +1312,7 @@ def extract_observed_subjects(pj: dict, selected_observations: list) -> list:
|
|
|
1441
1312
|
observed_subjects = []
|
|
1442
1313
|
|
|
1443
1314
|
# extract events from selected observations
|
|
1444
|
-
for events in [
|
|
1445
|
-
pj[cfg.OBSERVATIONS][x][cfg.EVENTS]
|
|
1446
|
-
for x in pj[cfg.OBSERVATIONS]
|
|
1447
|
-
if x in selected_observations
|
|
1448
|
-
]:
|
|
1315
|
+
for events in [pj[cfg.OBSERVATIONS][x][cfg.EVENTS] for x in pj[cfg.OBSERVATIONS] if x in selected_observations]:
|
|
1449
1316
|
for event in events:
|
|
1450
1317
|
observed_subjects.append(event[cfg.EVENT_SUBJECT_FIELD_IDX])
|
|
1451
1318
|
|
|
@@ -1453,7 +1320,7 @@ def extract_observed_subjects(pj: dict, selected_observations: list) -> list:
|
|
|
1453
1320
|
return list(set(observed_subjects))
|
|
1454
1321
|
|
|
1455
1322
|
|
|
1456
|
-
def open_project_json(
|
|
1323
|
+
def open_project_json(project_file_name: str) -> tuple:
|
|
1457
1324
|
"""
|
|
1458
1325
|
open BORIS project file in json format or GZ compressed json format
|
|
1459
1326
|
|
|
@@ -1467,37 +1334,37 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1467
1334
|
str: message
|
|
1468
1335
|
"""
|
|
1469
1336
|
|
|
1470
|
-
logging.debug(f"
|
|
1337
|
+
logging.debug(f"open_project_json function: {project_file_name}")
|
|
1471
1338
|
|
|
1472
|
-
projectChanged = False
|
|
1473
|
-
msg = ""
|
|
1339
|
+
projectChanged: bool = False
|
|
1340
|
+
msg: str = ""
|
|
1474
1341
|
|
|
1475
|
-
if not
|
|
1342
|
+
if not Path(project_file_name).is_file():
|
|
1476
1343
|
return (
|
|
1477
|
-
|
|
1344
|
+
project_file_name,
|
|
1478
1345
|
projectChanged,
|
|
1479
|
-
{"error": f"File {
|
|
1346
|
+
{"error": f"File {project_file_name} not found"},
|
|
1480
1347
|
msg,
|
|
1481
1348
|
)
|
|
1482
1349
|
|
|
1483
1350
|
try:
|
|
1484
|
-
if
|
|
1485
|
-
file_in = gzip.open(
|
|
1351
|
+
if project_file_name.endswith(".boris.gz"):
|
|
1352
|
+
file_in = gzip.open(project_file_name, mode="rt", encoding="utf-8")
|
|
1486
1353
|
else:
|
|
1487
|
-
file_in = open(
|
|
1354
|
+
file_in = open(project_file_name, "r")
|
|
1488
1355
|
file_content = file_in.read()
|
|
1489
1356
|
except PermissionError:
|
|
1490
1357
|
return (
|
|
1491
|
-
|
|
1358
|
+
project_file_name,
|
|
1492
1359
|
projectChanged,
|
|
1493
|
-
{"error": f"File {
|
|
1360
|
+
{"error": f"File {project_file_name}: Permission denied"},
|
|
1494
1361
|
msg,
|
|
1495
1362
|
)
|
|
1496
1363
|
except Exception:
|
|
1497
1364
|
return (
|
|
1498
|
-
|
|
1365
|
+
project_file_name,
|
|
1499
1366
|
projectChanged,
|
|
1500
|
-
{"error": f"Error on file {
|
|
1367
|
+
{"error": f"Error on file {project_file_name}: {sys.exc_info()[1]}"},
|
|
1501
1368
|
msg,
|
|
1502
1369
|
)
|
|
1503
1370
|
|
|
@@ -1505,16 +1372,16 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1505
1372
|
pj = json.loads(file_content)
|
|
1506
1373
|
except json.decoder.JSONDecodeError:
|
|
1507
1374
|
return (
|
|
1508
|
-
|
|
1375
|
+
project_file_name,
|
|
1509
1376
|
projectChanged,
|
|
1510
1377
|
{"error": "This project file seems corrupted"},
|
|
1511
1378
|
msg,
|
|
1512
1379
|
)
|
|
1513
1380
|
except Exception:
|
|
1514
1381
|
return (
|
|
1515
|
-
|
|
1382
|
+
project_file_name,
|
|
1516
1383
|
projectChanged,
|
|
1517
|
-
{"error": f"Error on file {
|
|
1384
|
+
{"error": f"Error on file {project_file_name}: {sys.exc_info()[1]}"},
|
|
1518
1385
|
msg,
|
|
1519
1386
|
)
|
|
1520
1387
|
|
|
@@ -1534,11 +1401,9 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1534
1401
|
projectChanged = True
|
|
1535
1402
|
|
|
1536
1403
|
# check if project file version is newer than current BORIS project file version
|
|
1537
|
-
if cfg.PROJECT_VERSION in pj and util.versiontuple(
|
|
1538
|
-
pj[cfg.PROJECT_VERSION]
|
|
1539
|
-
) > util.versiontuple(version.__version__):
|
|
1404
|
+
if cfg.PROJECT_VERSION in pj and util.versiontuple(pj[cfg.PROJECT_VERSION]) > util.versiontuple(version.__version__):
|
|
1540
1405
|
return (
|
|
1541
|
-
|
|
1406
|
+
project_file_name,
|
|
1542
1407
|
projectChanged,
|
|
1543
1408
|
{
|
|
1544
1409
|
"error": (
|
|
@@ -1551,13 +1416,11 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1551
1416
|
|
|
1552
1417
|
# check if old version v. 0 *.obs
|
|
1553
1418
|
if cfg.PROJECT_VERSION not in pj:
|
|
1554
|
-
|
|
1555
1419
|
# convert VIDEO, AUDIO -> MEDIA
|
|
1556
1420
|
pj[cfg.PROJECT_VERSION] = cfg.project_format_version
|
|
1557
1421
|
projectChanged = True
|
|
1558
1422
|
|
|
1559
1423
|
for obs in [x for x in pj[cfg.OBSERVATIONS]]:
|
|
1560
|
-
|
|
1561
1424
|
# remove 'replace audio' key
|
|
1562
1425
|
if "replace audio" in pj[cfg.OBSERVATIONS][obs]:
|
|
1563
1426
|
del pj[cfg.OBSERVATIONS][obs]["replace audio"]
|
|
@@ -1566,6 +1429,7 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1566
1429
|
pj[cfg.OBSERVATIONS][obs][cfg.TYPE] = cfg.MEDIA
|
|
1567
1430
|
|
|
1568
1431
|
# convert old media list in new one
|
|
1432
|
+
d1: dict = {}
|
|
1569
1433
|
if len(pj[cfg.OBSERVATIONS][obs][cfg.FILE]):
|
|
1570
1434
|
d1 = {cfg.PLAYER1: [pj[cfg.OBSERVATIONS][obs][cfg.FILE][0]]}
|
|
1571
1435
|
|
|
@@ -1583,15 +1447,13 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1583
1447
|
f"The project file was converted to the new format (v. {cfg.project_format_version}) in use with your version of BORIS.<br>"
|
|
1584
1448
|
"Choose a new file name for saving it."
|
|
1585
1449
|
)
|
|
1586
|
-
|
|
1450
|
+
project_file_name = ""
|
|
1587
1451
|
|
|
1588
1452
|
# update modifiers to JSON format
|
|
1589
1453
|
|
|
1590
1454
|
# check if project format version < 4 (modifiers were str)
|
|
1591
1455
|
project_lowerthan4 = False
|
|
1592
|
-
if cfg.PROJECT_VERSION in pj and util.versiontuple(
|
|
1593
|
-
pj[cfg.PROJECT_VERSION]
|
|
1594
|
-
) < util.versiontuple("4.0"):
|
|
1456
|
+
if cfg.PROJECT_VERSION in pj and util.versiontuple(pj[cfg.PROJECT_VERSION]) < util.versiontuple("4.0"):
|
|
1595
1457
|
for idx in pj[cfg.ETHOGRAM]:
|
|
1596
1458
|
if pj[cfg.ETHOGRAM][idx]["modifiers"]:
|
|
1597
1459
|
if isinstance(pj[cfg.ETHOGRAM][idx]["modifiers"], str):
|
|
@@ -1609,40 +1471,46 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1609
1471
|
pj[cfg.ETHOGRAM][idx]["modifiers"] = {}
|
|
1610
1472
|
|
|
1611
1473
|
if not project_lowerthan4:
|
|
1612
|
-
msg = "The project version was updated from {} to {}".format(
|
|
1613
|
-
pj[cfg.PROJECT_VERSION], cfg.project_format_version
|
|
1614
|
-
)
|
|
1474
|
+
msg = "The project version was updated from {} to {}".format(pj[cfg.PROJECT_VERSION], cfg.project_format_version)
|
|
1615
1475
|
pj[cfg.PROJECT_VERSION] = cfg.project_format_version
|
|
1616
1476
|
projectChanged = True
|
|
1617
1477
|
|
|
1478
|
+
# check if behavioral categories are stored as a list
|
|
1479
|
+
if cfg.BEHAVIORAL_CATEGORIES_CONF in pj:
|
|
1480
|
+
if isinstance(pj[cfg.BEHAVIORAL_CATEGORIES_CONF], list):
|
|
1481
|
+
# convert to dict
|
|
1482
|
+
pj[cfg.BEHAVIORAL_CATEGORIES_CONF] = {str(idx): {"name": bc} for idx, bc in enumerate(pj[cfg.BEHAVIORAL_CATEGORIES_CONF])}
|
|
1483
|
+
logging.info("Behavioral categories was converted from a list to a dictionary")
|
|
1484
|
+
projectChanged = True
|
|
1485
|
+
else:
|
|
1486
|
+
pj[cfg.BEHAVIORAL_CATEGORIES_CONF] = dict()
|
|
1487
|
+
projectChanged = True
|
|
1488
|
+
|
|
1618
1489
|
# add category key if not found
|
|
1619
1490
|
for idx in pj[cfg.ETHOGRAM]:
|
|
1620
|
-
if
|
|
1621
|
-
pj[cfg.ETHOGRAM][idx][
|
|
1491
|
+
if cfg.BEHAVIOR_CATEGORY not in pj[cfg.ETHOGRAM][idx]:
|
|
1492
|
+
pj[cfg.ETHOGRAM][idx][cfg.BEHAVIOR_CATEGORY] = ""
|
|
1622
1493
|
|
|
1623
1494
|
# if one file is present in player #1 -> set "media_info" key with value of media_file_info
|
|
1624
1495
|
for obs in pj[cfg.OBSERVATIONS]:
|
|
1625
|
-
if
|
|
1626
|
-
pj[cfg.OBSERVATIONS][obs][cfg.TYPE] in [cfg.MEDIA]
|
|
1627
|
-
and cfg.MEDIA_INFO not in pj[cfg.OBSERVATIONS][obs]
|
|
1628
|
-
):
|
|
1496
|
+
if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] in [cfg.MEDIA] and cfg.MEDIA_INFO not in pj[cfg.OBSERVATIONS][obs]:
|
|
1629
1497
|
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO] = {
|
|
1630
1498
|
cfg.LENGTH: {},
|
|
1631
1499
|
cfg.FPS: {},
|
|
1632
1500
|
cfg.HAS_VIDEO: {},
|
|
1633
1501
|
cfg.HAS_AUDIO: {},
|
|
1634
1502
|
}
|
|
1635
|
-
for player in
|
|
1503
|
+
for player in (cfg.PLAYER1, cfg.PLAYER2):
|
|
1636
1504
|
# fix bug Anne Maijer 2017-07-17
|
|
1637
1505
|
if pj[cfg.OBSERVATIONS][obs][cfg.FILE] == []:
|
|
1638
1506
|
pj[cfg.OBSERVATIONS][obs][cfg.FILE] = {"1": [], "2": []}
|
|
1639
1507
|
|
|
1640
1508
|
for media_file_path in pj[cfg.OBSERVATIONS][obs]["file"][player]:
|
|
1641
1509
|
# FIX: ffmpeg path
|
|
1642
|
-
ret,
|
|
1510
|
+
ret, ffmpeg_bin = util.check_ffmpeg_path()
|
|
1643
1511
|
if not ret:
|
|
1644
1512
|
return (
|
|
1645
|
-
|
|
1513
|
+
project_file_name,
|
|
1646
1514
|
projectChanged,
|
|
1647
1515
|
{"error": "FFmpeg path not found"},
|
|
1648
1516
|
"",
|
|
@@ -1653,64 +1521,35 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1653
1521
|
r = util.accurate_media_analysis(ffmpeg_bin, media_file_path)
|
|
1654
1522
|
|
|
1655
1523
|
if "duration" in r and r["duration"]:
|
|
1656
|
-
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO][cfg.LENGTH][
|
|
1657
|
-
|
|
1658
|
-
] =
|
|
1659
|
-
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO][cfg.
|
|
1660
|
-
media_file_path
|
|
1661
|
-
] = float(r["fps"])
|
|
1662
|
-
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO][cfg.HAS_VIDEO][
|
|
1663
|
-
media_file_path
|
|
1664
|
-
] = r["has_video"]
|
|
1665
|
-
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO][cfg.HAS_AUDIO][
|
|
1666
|
-
media_file_path
|
|
1667
|
-
] = r["has_audio"]
|
|
1524
|
+
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO][cfg.LENGTH][media_file_path] = float(r["duration"])
|
|
1525
|
+
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO][cfg.FPS][media_file_path] = float(r["fps"])
|
|
1526
|
+
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO][cfg.HAS_VIDEO][media_file_path] = r["has_video"]
|
|
1527
|
+
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO][cfg.HAS_AUDIO][media_file_path] = r["has_audio"]
|
|
1668
1528
|
projectChanged = True
|
|
1669
1529
|
else: # file path not found
|
|
1670
1530
|
if (
|
|
1671
1531
|
cfg.MEDIA_FILE_INFO in pj[cfg.OBSERVATIONS][obs]
|
|
1672
1532
|
and len(pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_FILE_INFO]) == 1
|
|
1673
|
-
and len(pj[cfg.OBSERVATIONS][obs][cfg.FILE][cfg.PLAYER1])
|
|
1674
|
-
==
|
|
1675
|
-
and len(pj[cfg.OBSERVATIONS][obs][cfg.FILE][cfg.PLAYER2])
|
|
1676
|
-
== 0
|
|
1533
|
+
and len(pj[cfg.OBSERVATIONS][obs][cfg.FILE][cfg.PLAYER1]) == 1
|
|
1534
|
+
and len(pj[cfg.OBSERVATIONS][obs][cfg.FILE][cfg.PLAYER2]) == 0
|
|
1677
1535
|
):
|
|
1678
|
-
media_md5_key = list(
|
|
1679
|
-
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_FILE_INFO].keys()
|
|
1680
|
-
)[0]
|
|
1536
|
+
media_md5_key = list(pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_FILE_INFO].keys())[0]
|
|
1681
1537
|
# duration
|
|
1682
1538
|
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO] = {
|
|
1683
1539
|
cfg.LENGTH: {
|
|
1684
|
-
media_file_path: pj[cfg.OBSERVATIONS][obs][
|
|
1685
|
-
cfg.MEDIA_FILE_INFO
|
|
1686
|
-
][media_md5_key]["video_length"]
|
|
1687
|
-
/ 1000
|
|
1540
|
+
media_file_path: pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_FILE_INFO][media_md5_key]["video_length"] / 1000
|
|
1688
1541
|
}
|
|
1689
1542
|
}
|
|
1690
1543
|
projectChanged = True
|
|
1691
1544
|
|
|
1692
1545
|
# FPS
|
|
1693
|
-
if
|
|
1694
|
-
"nframe"
|
|
1695
|
-
in pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_FILE_INFO][
|
|
1696
|
-
media_md5_key
|
|
1697
|
-
]
|
|
1698
|
-
):
|
|
1546
|
+
if "nframe" in pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_FILE_INFO][media_md5_key]:
|
|
1699
1547
|
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO][cfg.FPS] = {
|
|
1700
|
-
media_file_path: pj[cfg.OBSERVATIONS][obs][
|
|
1701
|
-
|
|
1702
|
-
][media_md5_key]["nframe"]
|
|
1703
|
-
/ (
|
|
1704
|
-
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_FILE_INFO][
|
|
1705
|
-
media_md5_key
|
|
1706
|
-
]["video_length"]
|
|
1707
|
-
/ 1000
|
|
1708
|
-
)
|
|
1548
|
+
media_file_path: pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_FILE_INFO][media_md5_key]["nframe"]
|
|
1549
|
+
/ (pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_FILE_INFO][media_md5_key]["video_length"] / 1000)
|
|
1709
1550
|
}
|
|
1710
1551
|
else:
|
|
1711
|
-
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO][cfg.FPS] = {
|
|
1712
|
-
media_file_path: 0
|
|
1713
|
-
}
|
|
1552
|
+
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO][cfg.FPS] = {media_file_path: 0}
|
|
1714
1553
|
|
|
1715
1554
|
# update project to v.7 for time offset second player
|
|
1716
1555
|
project_lowerthan7 = False
|
|
@@ -1723,9 +1562,7 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1723
1562
|
for player in pj[cfg.OBSERVATIONS][obs][cfg.FILE]:
|
|
1724
1563
|
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO][cfg.OFFSET][player] = 0.0
|
|
1725
1564
|
if pj[cfg.OBSERVATIONS][obs]["time offset second player"]:
|
|
1726
|
-
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO][cfg.OFFSET]["2"] = float(
|
|
1727
|
-
pj[cfg.OBSERVATIONS][obs]["time offset second player"]
|
|
1728
|
-
)
|
|
1565
|
+
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO][cfg.OFFSET]["2"] = float(pj[cfg.OBSERVATIONS][obs]["time offset second player"])
|
|
1729
1566
|
|
|
1730
1567
|
del pj[cfg.OBSERVATIONS][obs]["time offset second player"]
|
|
1731
1568
|
project_lowerthan7 = True
|
|
@@ -1739,49 +1576,49 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1739
1576
|
projectChanged = True
|
|
1740
1577
|
|
|
1741
1578
|
if project_lowerthan7:
|
|
1742
|
-
|
|
1743
1579
|
msg = f"The project was updated to the current project version ({cfg.project_format_version})."
|
|
1744
1580
|
|
|
1745
1581
|
try:
|
|
1746
|
-
old_project_file_name =
|
|
1747
|
-
|
|
1748
|
-
)
|
|
1749
|
-
copyfile(projectFileName, old_project_file_name)
|
|
1582
|
+
old_project_file_name = project_file_name.replace(".boris", f".v{pj['project_format_version']}.boris")
|
|
1583
|
+
copyfile(project_file_name, old_project_file_name)
|
|
1750
1584
|
msg += f"\n\nThe old file project was saved as {old_project_file_name}"
|
|
1751
1585
|
except Exception:
|
|
1752
|
-
|
|
1586
|
+
QMessageBox.critical(cfg.programName, f"Error saving old project to {old_project_file_name}")
|
|
1753
1587
|
|
|
1754
1588
|
pj[cfg.PROJECT_VERSION] = cfg.project_format_version
|
|
1755
1589
|
|
|
1756
|
-
|
|
1590
|
+
# sort events by time asc
|
|
1591
|
+
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
1592
|
+
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] in (cfg.LIVE, cfg.MEDIA):
|
|
1593
|
+
# sort events list using the first 3 items (time, subject, behavior)
|
|
1594
|
+
pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS].sort(key=lambda x: x[:3])
|
|
1595
|
+
|
|
1596
|
+
return project_file_name, projectChanged, pj, msg
|
|
1757
1597
|
|
|
1758
1598
|
|
|
1759
|
-
def event_type(code: str, ethogram: dict) -> str:
|
|
1599
|
+
def event_type(code: str, ethogram: dict) -> str | None:
|
|
1760
1600
|
"""
|
|
1761
|
-
returns type of event for code
|
|
1601
|
+
returns type of event for behavior code
|
|
1762
1602
|
|
|
1763
1603
|
Args:
|
|
1764
1604
|
ethogram (dict); ethogram of project
|
|
1765
1605
|
code (str): behavior code
|
|
1766
1606
|
|
|
1767
1607
|
Returns:
|
|
1768
|
-
str:
|
|
1608
|
+
str: behavior type
|
|
1769
1609
|
"""
|
|
1770
1610
|
|
|
1771
1611
|
for idx in ethogram:
|
|
1772
1612
|
if ethogram[idx][cfg.BEHAVIOR_CODE] == code:
|
|
1773
|
-
return ethogram[idx][cfg.TYPE]
|
|
1613
|
+
return ethogram[idx][cfg.TYPE]
|
|
1774
1614
|
return None
|
|
1775
1615
|
|
|
1776
1616
|
|
|
1777
|
-
def fix_unpaired_state_events(
|
|
1778
|
-
ethogram: dict, observation: dict, fix_at_time: dec
|
|
1779
|
-
) -> list:
|
|
1617
|
+
def fix_unpaired_state_events(ethogram: dict, observation: dict, fix_at_time: dec) -> list:
|
|
1780
1618
|
"""
|
|
1781
1619
|
fix unpaired state events in observation
|
|
1782
1620
|
|
|
1783
1621
|
Args:
|
|
1784
|
-
obsId (str): observation id
|
|
1785
1622
|
ethogram (dict): ethogram dictionary
|
|
1786
1623
|
observation (dict): observation dictionary
|
|
1787
1624
|
fix_at_time (Decimal): time to fix the unpaired events
|
|
@@ -1790,31 +1627,23 @@ def fix_unpaired_state_events(
|
|
|
1790
1627
|
list: list of events with state events fixed
|
|
1791
1628
|
"""
|
|
1792
1629
|
|
|
1793
|
-
closing_events_to_add = []
|
|
1794
|
-
subjects = [event[cfg.EVENT_SUBJECT_FIELD_IDX] for event in observation[cfg.EVENTS]]
|
|
1795
|
-
ethogram_behaviors = {ethogram[idx][cfg.BEHAVIOR_CODE] for idx in ethogram}
|
|
1630
|
+
closing_events_to_add: list = []
|
|
1631
|
+
subjects: list = [event[cfg.EVENT_SUBJECT_FIELD_IDX] for event in observation[cfg.EVENTS]]
|
|
1632
|
+
ethogram_behaviors: dict = {ethogram[idx][cfg.BEHAVIOR_CODE] for idx in ethogram}
|
|
1796
1633
|
|
|
1797
1634
|
for subject in sorted(set(subjects)):
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
event[cfg.EVENT_BEHAVIOR_FIELD_IDX]
|
|
1801
|
-
for event in observation[cfg.EVENTS]
|
|
1802
|
-
if event[cfg.EVENT_SUBJECT_FIELD_IDX] == subject
|
|
1635
|
+
behaviors: list = [
|
|
1636
|
+
event[cfg.EVENT_BEHAVIOR_FIELD_IDX] for event in observation[cfg.EVENTS] if event[cfg.EVENT_SUBJECT_FIELD_IDX] == subject
|
|
1803
1637
|
]
|
|
1804
1638
|
|
|
1805
1639
|
for behavior in sorted(set(behaviors)):
|
|
1806
|
-
if (behavior in ethogram_behaviors) and (
|
|
1807
|
-
cfg.STATE in event_type(behavior, ethogram).upper()
|
|
1808
|
-
):
|
|
1809
|
-
|
|
1640
|
+
if (behavior in ethogram_behaviors) and (event_type(behavior, ethogram) in cfg.STATE_EVENT_TYPES):
|
|
1810
1641
|
lst, memTime = [], {}
|
|
1811
1642
|
for event in [
|
|
1812
1643
|
event
|
|
1813
1644
|
for event in observation[cfg.EVENTS]
|
|
1814
|
-
if event[cfg.EVENT_BEHAVIOR_FIELD_IDX] == behavior
|
|
1815
|
-
and event[cfg.EVENT_SUBJECT_FIELD_IDX] == subject
|
|
1645
|
+
if event[cfg.EVENT_BEHAVIOR_FIELD_IDX] == behavior and event[cfg.EVENT_SUBJECT_FIELD_IDX] == subject
|
|
1816
1646
|
]:
|
|
1817
|
-
|
|
1818
1647
|
behav_modif = [
|
|
1819
1648
|
event[cfg.EVENT_BEHAVIOR_FIELD_IDX],
|
|
1820
1649
|
event[cfg.EVENT_MODIFIER_FIELD_IDX],
|
|
@@ -1828,19 +1657,78 @@ def fix_unpaired_state_events(
|
|
|
1828
1657
|
memTime[str(behav_modif)] = event[cfg.EVENT_TIME_FIELD_IDX]
|
|
1829
1658
|
|
|
1830
1659
|
for event in lst:
|
|
1660
|
+
last_event_time = max([fix_at_time] + [x[0] for x in closing_events_to_add])
|
|
1831
1661
|
|
|
1832
|
-
|
|
1833
|
-
[
|
|
1662
|
+
closing_events_to_add.append(
|
|
1663
|
+
[
|
|
1664
|
+
last_event_time + dec("0.001"),
|
|
1665
|
+
subject,
|
|
1666
|
+
behavior,
|
|
1667
|
+
event[1], # modifiers
|
|
1668
|
+
"Event automatically added by the fix unpaired state events function",
|
|
1669
|
+
cfg.NA, # frame index
|
|
1670
|
+
]
|
|
1834
1671
|
)
|
|
1835
1672
|
|
|
1673
|
+
return closing_events_to_add
|
|
1674
|
+
|
|
1675
|
+
|
|
1676
|
+
def fix_unpaired_state_events2(ethogram: dict, events: list, fix_at_time: dec) -> list:
|
|
1677
|
+
"""
|
|
1678
|
+
fix unpaired state events in events list
|
|
1679
|
+
|
|
1680
|
+
Args:
|
|
1681
|
+
ethogram (dict): ethogram dictionary
|
|
1682
|
+
events (list): list of events
|
|
1683
|
+
fix_at_time (Decimal): time to fix the unpaired events
|
|
1684
|
+
|
|
1685
|
+
Returns:
|
|
1686
|
+
list: list of events with state events fixed
|
|
1687
|
+
"""
|
|
1688
|
+
|
|
1689
|
+
logging.debug("fix_unpaired_state_events2 function")
|
|
1690
|
+
|
|
1691
|
+
closing_events_to_add: list = []
|
|
1692
|
+
subjects: list = [event[cfg.EVENT_SUBJECT_FIELD_IDX] for event in events]
|
|
1693
|
+
ethogram_behaviors: dict = {ethogram[idx][cfg.BEHAVIOR_CODE] for idx in ethogram}
|
|
1694
|
+
|
|
1695
|
+
for subject in sorted(set(subjects)):
|
|
1696
|
+
behaviors: list = [event[cfg.EVENT_BEHAVIOR_FIELD_IDX] for event in events if event[cfg.EVENT_SUBJECT_FIELD_IDX] == subject]
|
|
1697
|
+
|
|
1698
|
+
for behavior in sorted(set(behaviors)):
|
|
1699
|
+
if (behavior in ethogram_behaviors) and (event_type(behavior, ethogram) in cfg.STATE_EVENT_TYPES):
|
|
1700
|
+
lst: list = []
|
|
1701
|
+
memTime: dict = {}
|
|
1702
|
+
for event in [
|
|
1703
|
+
event
|
|
1704
|
+
for event in events
|
|
1705
|
+
if event[cfg.EVENT_BEHAVIOR_FIELD_IDX] == behavior and event[cfg.EVENT_SUBJECT_FIELD_IDX] == subject
|
|
1706
|
+
]:
|
|
1707
|
+
behav_modif = [
|
|
1708
|
+
event[cfg.EVENT_BEHAVIOR_FIELD_IDX],
|
|
1709
|
+
event[cfg.EVENT_MODIFIER_FIELD_IDX],
|
|
1710
|
+
]
|
|
1711
|
+
|
|
1712
|
+
if behav_modif in lst:
|
|
1713
|
+
lst.remove(behav_modif)
|
|
1714
|
+
del memTime[str(behav_modif)]
|
|
1715
|
+
else:
|
|
1716
|
+
lst.append(behav_modif)
|
|
1717
|
+
memTime[str(behav_modif)] = event[cfg.EVENT_TIME_FIELD_IDX]
|
|
1718
|
+
|
|
1719
|
+
for event in lst:
|
|
1720
|
+
last_event_time = max([fix_at_time] + [x[0] for x in closing_events_to_add])
|
|
1721
|
+
|
|
1836
1722
|
closing_events_to_add.append(
|
|
1837
1723
|
[
|
|
1838
|
-
last_event_time + dec("0.001"),
|
|
1724
|
+
# last_event_time + dec("0.001"),
|
|
1725
|
+
last_event_time,
|
|
1839
1726
|
subject,
|
|
1840
1727
|
behavior,
|
|
1841
|
-
event[1],
|
|
1842
|
-
"",
|
|
1843
|
-
|
|
1728
|
+
event[1], # modifiers
|
|
1729
|
+
"Event automatically added by the fix unpaired state events function",
|
|
1730
|
+
cfg.NA, # frame index
|
|
1731
|
+
]
|
|
1844
1732
|
)
|
|
1845
1733
|
|
|
1846
1734
|
return closing_events_to_add
|
|
@@ -1867,8 +1755,11 @@ def explore_project(self) -> None:
|
|
|
1867
1755
|
manage double-click on tablewidget of explore project results
|
|
1868
1756
|
"""
|
|
1869
1757
|
observation_operations.load_observation(self, obs_id, cfg.VIEW)
|
|
1870
|
-
|
|
1871
|
-
self.
|
|
1758
|
+
|
|
1759
|
+
self.tv_events.selectRow(event_idx - 1)
|
|
1760
|
+
index = self.tv_events.model().index(event_idx - 1, 0)
|
|
1761
|
+
self.tv_events.scrollTo(index, QAbstractItemView.EnsureVisible)
|
|
1762
|
+
# self.twEvents.scrollToItem(self.twEvents.item(event_idx - 1, 0))
|
|
1872
1763
|
|
|
1873
1764
|
elements_list = ("Subject", "Behavior", "Modifier", "Comment")
|
|
1874
1765
|
elements = []
|
|
@@ -1891,9 +1782,7 @@ def explore_project(self) -> None:
|
|
|
1891
1782
|
nb_fields += explore_dlg.elements[element].text() != ""
|
|
1892
1783
|
|
|
1893
1784
|
for obs_id in sorted(self.pj[cfg.OBSERVATIONS]):
|
|
1894
|
-
for event_idx, event in enumerate(
|
|
1895
|
-
self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]
|
|
1896
|
-
):
|
|
1785
|
+
for event_idx, event in enumerate(self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]):
|
|
1897
1786
|
nb_results = 0
|
|
1898
1787
|
for text, idx in (
|
|
1899
1788
|
(explore_dlg.elements["Subject"].text(), cfg.EVENT_SUBJECT_FIELD_IDX),
|
|
@@ -1904,14 +1793,8 @@ def explore_project(self) -> None:
|
|
|
1904
1793
|
if text:
|
|
1905
1794
|
if any(
|
|
1906
1795
|
(
|
|
1907
|
-
(
|
|
1908
|
-
|
|
1909
|
-
and text in event[idx]
|
|
1910
|
-
),
|
|
1911
|
-
(
|
|
1912
|
-
not explore_dlg.elements["Case sensitive"].isChecked()
|
|
1913
|
-
and text.upper() in event[idx].upper()
|
|
1914
|
-
),
|
|
1796
|
+
(explore_dlg.elements["Case sensitive"].isChecked() and text in event[idx]),
|
|
1797
|
+
(not explore_dlg.elements["Case sensitive"].isChecked() and text.upper() in event[idx].upper()),
|
|
1915
1798
|
)
|
|
1916
1799
|
):
|
|
1917
1800
|
nb_results += 1
|
|
@@ -1928,26 +1811,232 @@ def explore_project(self) -> None:
|
|
|
1928
1811
|
txt2 = ""
|
|
1929
1812
|
for element in elements_list:
|
|
1930
1813
|
if explore_dlg.elements[element].text():
|
|
1931
|
-
txt2 += (
|
|
1932
|
-
f"<b>{explore_dlg.elements[element].text()}</b> in {element}<br>"
|
|
1933
|
-
)
|
|
1814
|
+
txt2 += f"<b>{explore_dlg.elements[element].text()}</b> in {element}<br>"
|
|
1934
1815
|
if txt2:
|
|
1935
1816
|
txt += " for<br>"
|
|
1936
1817
|
self.results_dialog.lb.setText(txt + txt2)
|
|
1937
1818
|
self.results_dialog.tw.setColumnCount(2)
|
|
1938
1819
|
self.results_dialog.tw.setRowCount(len(results))
|
|
1939
|
-
self.results_dialog.tw.setHorizontalHeaderLabels(
|
|
1940
|
-
["Observation id", "row index"]
|
|
1941
|
-
)
|
|
1820
|
+
self.results_dialog.tw.setHorizontalHeaderLabels(["Observation id", "row index"])
|
|
1942
1821
|
|
|
1943
1822
|
for row, result in enumerate(results):
|
|
1944
1823
|
for i in range(0, 2):
|
|
1945
1824
|
self.results_dialog.tw.setItem(row, i, QTableWidgetItem(str(result[i])))
|
|
1946
|
-
self.results_dialog.tw.item(row, i).setFlags(
|
|
1947
|
-
Qt.ItemIsSelectable | Qt.ItemIsEnabled
|
|
1948
|
-
)
|
|
1825
|
+
self.results_dialog.tw.item(row, i).setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
|
1949
1826
|
|
|
1950
1827
|
self.results_dialog.show()
|
|
1951
1828
|
|
|
1952
1829
|
else:
|
|
1953
1830
|
QMessageBox.information(self, cfg.programName, "No events found")
|
|
1831
|
+
|
|
1832
|
+
|
|
1833
|
+
def project2dataframe(pj: dict, observations_list: list = []) -> Tuple[str, pd.DataFrame]:
|
|
1834
|
+
"""
|
|
1835
|
+
returns a pandas dataframe containing observations data
|
|
1836
|
+
"""
|
|
1837
|
+
# print(pj.keys())
|
|
1838
|
+
|
|
1839
|
+
# print(pj["independent_variables"])
|
|
1840
|
+
|
|
1841
|
+
# indep_var = [pj["independent_variables"][idx]["label"] for idx in pj["independent_variables"]]
|
|
1842
|
+
|
|
1843
|
+
indep_variables = dict(
|
|
1844
|
+
[(pj[cfg.INDEPENDENT_VARIABLES][idx]["label"], pj[cfg.INDEPENDENT_VARIABLES][idx]["type"]) for idx in pj[cfg.INDEPENDENT_VARIABLES]]
|
|
1845
|
+
)
|
|
1846
|
+
|
|
1847
|
+
# print()
|
|
1848
|
+
# print(f"{indep_variables=}")
|
|
1849
|
+
|
|
1850
|
+
# n_max_set_modifiers = max([len(pj["behaviors_conf"][behavior_id]["modifiers"]) for behavior_id in pj["behaviors_conf"]])
|
|
1851
|
+
|
|
1852
|
+
# behavioral_categories
|
|
1853
|
+
behavioral_category = dict(
|
|
1854
|
+
[(pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE], pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CATEGORY]) for x in pj[cfg.ETHOGRAM]]
|
|
1855
|
+
)
|
|
1856
|
+
|
|
1857
|
+
# print(f"{pj["behaviors_conf"]=}")
|
|
1858
|
+
|
|
1859
|
+
# check all modifiers
|
|
1860
|
+
all_modifier_sets: list = []
|
|
1861
|
+
for behavior_id in pj[cfg.ETHOGRAM]:
|
|
1862
|
+
modifier_names: list = []
|
|
1863
|
+
set_count = 0
|
|
1864
|
+
if pj[cfg.ETHOGRAM][behavior_id][cfg.MODIFIERS] == "":
|
|
1865
|
+
continue
|
|
1866
|
+
for modifier in pj[cfg.ETHOGRAM][behavior_id][cfg.MODIFIERS].values():
|
|
1867
|
+
if modifier["name"]:
|
|
1868
|
+
modifier_names.append((pj[cfg.ETHOGRAM][behavior_id][cfg.BEHAVIOR_CODE], modifier["name"]))
|
|
1869
|
+
else:
|
|
1870
|
+
set_count += 1
|
|
1871
|
+
modifier_names.append((pj[cfg.ETHOGRAM][behavior_id][cfg.BEHAVIOR_CODE], f"set #{set_count}"))
|
|
1872
|
+
|
|
1873
|
+
# print(modifier_names)
|
|
1874
|
+
if modifier_names:
|
|
1875
|
+
all_modifier_sets.extend(modifier_names)
|
|
1876
|
+
|
|
1877
|
+
# print()
|
|
1878
|
+
# print(f"{all_modifier_sets=}")
|
|
1879
|
+
|
|
1880
|
+
# create df
|
|
1881
|
+
|
|
1882
|
+
data = {
|
|
1883
|
+
"Observation id": [],
|
|
1884
|
+
"Observation date": [],
|
|
1885
|
+
"Description": [],
|
|
1886
|
+
"Observation type": [],
|
|
1887
|
+
"Observation interval start": [],
|
|
1888
|
+
"Observation interval stop": [],
|
|
1889
|
+
# "Source": [],
|
|
1890
|
+
# "Time offset (s)": [],
|
|
1891
|
+
# "Coding duration": [],
|
|
1892
|
+
# "Media duration (s)": [],
|
|
1893
|
+
# "FPS (frame/s)": [],
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
for indep_var in indep_variables:
|
|
1897
|
+
data[f"independent variable '{indep_var}'"] = []
|
|
1898
|
+
|
|
1899
|
+
data = data | {
|
|
1900
|
+
"Subject": [],
|
|
1901
|
+
"Observation duration by subject by observation": [],
|
|
1902
|
+
"Behavior": [],
|
|
1903
|
+
"Behavioral category": [],
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
for modifier_set in all_modifier_sets:
|
|
1907
|
+
data[modifier_set] = []
|
|
1908
|
+
|
|
1909
|
+
data = data | {
|
|
1910
|
+
"Behavior type": [],
|
|
1911
|
+
"Start (s)": [],
|
|
1912
|
+
"Stop (s)": [],
|
|
1913
|
+
"Duration (s)": [],
|
|
1914
|
+
# "Media file name": [],
|
|
1915
|
+
# "Image index start": [],
|
|
1916
|
+
# "Image index stop": [],
|
|
1917
|
+
# "Image file path start": [],
|
|
1918
|
+
# "Image file path stop": [],
|
|
1919
|
+
"Comment start": [],
|
|
1920
|
+
"Comment stop": [],
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
#
|
|
1924
|
+
|
|
1925
|
+
type_ = {
|
|
1926
|
+
"Observation id": "string",
|
|
1927
|
+
"Observation date": "string",
|
|
1928
|
+
"Description": "string",
|
|
1929
|
+
"Observation type": "string",
|
|
1930
|
+
"Observation interval start": "float64",
|
|
1931
|
+
"Observation interval stop": "float64",
|
|
1932
|
+
# "Source": "string",
|
|
1933
|
+
# "Time offset (s)": "string",
|
|
1934
|
+
# "Coding duration": "float64",
|
|
1935
|
+
# "Media duration (s)": "string",
|
|
1936
|
+
# "FPS (frame/s)": "float64",
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
# TODO: set correct type in base of the var type
|
|
1940
|
+
for indep_var in indep_variables:
|
|
1941
|
+
type_[f"independent variable '{indep_var}'"] = "float64" if indep_variables[indep_var] == cfg.NUMERIC else "string"
|
|
1942
|
+
|
|
1943
|
+
type_ = type_ | {
|
|
1944
|
+
"Subject": "string",
|
|
1945
|
+
"Observation duration by subject by observation": "float64",
|
|
1946
|
+
"Behavior": "string",
|
|
1947
|
+
"Behavioral category": "string",
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
for modifer_set in all_modifier_sets:
|
|
1951
|
+
type_[modifer_set] = "string"
|
|
1952
|
+
|
|
1953
|
+
type_ = type_ | {
|
|
1954
|
+
"Behavior type": "string",
|
|
1955
|
+
"Start (s)": "float64",
|
|
1956
|
+
"Stop (s)": "float64",
|
|
1957
|
+
"Duration (s)": "float64",
|
|
1958
|
+
# "Media file name": "string",
|
|
1959
|
+
# "Image index start": "float64",
|
|
1960
|
+
# "Image index stop": "float64",
|
|
1961
|
+
# "Image file path start": "string",
|
|
1962
|
+
# "Image file path stop": "string",
|
|
1963
|
+
"Comment start": "string",
|
|
1964
|
+
"Comment stop": "string",
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
state_behaviors = util.state_behavior_codes(pj[cfg.ETHOGRAM])
|
|
1968
|
+
|
|
1969
|
+
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
1970
|
+
if observations_list and obs_id not in observations_list:
|
|
1971
|
+
continue
|
|
1972
|
+
# print(obs_id)
|
|
1973
|
+
stop_event_idx = set()
|
|
1974
|
+
for idx_event, event in enumerate(pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]):
|
|
1975
|
+
if idx_event in stop_event_idx:
|
|
1976
|
+
continue
|
|
1977
|
+
data["Observation id"].append(obs_id)
|
|
1978
|
+
data["Observation date"].append(pj[cfg.OBSERVATIONS][obs_id]["date"])
|
|
1979
|
+
data["Description"].append(" ".join(pj[cfg.OBSERVATIONS][obs_id]["description"].splitlines()))
|
|
1980
|
+
data["Observation type"].append(pj[cfg.OBSERVATIONS][obs_id]["type"])
|
|
1981
|
+
|
|
1982
|
+
data["Observation interval start"].append(pj[cfg.OBSERVATIONS][obs_id].get(cfg.OBSERVATION_TIME_INTERVAL, [None, None])[0])
|
|
1983
|
+
data["Observation interval stop"].append(pj[cfg.OBSERVATIONS][obs_id].get(cfg.OBSERVATION_TIME_INTERVAL, [None, None])[1])
|
|
1984
|
+
|
|
1985
|
+
# data["Source"].append("")
|
|
1986
|
+
# data["Time offset (s)"].append(pj["observations"][obs_id]["time offset"])
|
|
1987
|
+
# data["Coding duration"].append("")
|
|
1988
|
+
# data["Media duration (s)"].append("")
|
|
1989
|
+
# data["FPS (frame/s)"].append("")
|
|
1990
|
+
|
|
1991
|
+
for indep_var in indep_variables:
|
|
1992
|
+
data[f"independent variable '{indep_var}'"].append(
|
|
1993
|
+
pj[cfg.OBSERVATIONS][obs_id][cfg.INDEPENDENT_VARIABLES].get(indep_var, None)
|
|
1994
|
+
)
|
|
1995
|
+
|
|
1996
|
+
data["Subject"].append(event[cfg.EVENT_SUBJECT_FIELD_IDX] if event[cfg.EVENT_SUBJECT_FIELD_IDX] != "" else cfg.NO_FOCAL_SUBJECT)
|
|
1997
|
+
data["Observation duration by subject by observation"].append(-1)
|
|
1998
|
+
data["Behavior"].append(event[2])
|
|
1999
|
+
data["Behavioral category"].append(behavioral_category[event[2]])
|
|
2000
|
+
|
|
2001
|
+
count_set = 0
|
|
2002
|
+
for modifier_set in all_modifier_sets:
|
|
2003
|
+
if event[2] == modifier_set[0]:
|
|
2004
|
+
try:
|
|
2005
|
+
data[modifier_set].append(event[3].split("|")[count_set])
|
|
2006
|
+
except Exception:
|
|
2007
|
+
return f"Modifier error for {event[2]} in observation {obs_id}", pd.DataFrame()
|
|
2008
|
+
count_set += 1
|
|
2009
|
+
else:
|
|
2010
|
+
data[modifier_set].append(np.nan)
|
|
2011
|
+
|
|
2012
|
+
data["Behavior type"].append(cfg.STATE_EVENT if event[2] in state_behaviors else cfg.POINT_EVENT)
|
|
2013
|
+
data["Start (s)"].append(float(event[0]))
|
|
2014
|
+
if event[2] in state_behaviors:
|
|
2015
|
+
# search stop
|
|
2016
|
+
# print(f"==> {idx_event=} {event[1:4]=}")
|
|
2017
|
+
for idx_event2, event2 in enumerate(pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS][idx_event + 1 :], start=idx_event + 1):
|
|
2018
|
+
# print(f"{idx_event2=} {event2[1:4]=}")
|
|
2019
|
+
if event2[1:4] == event[1:4]:
|
|
2020
|
+
# print("found")
|
|
2021
|
+
stop_event_idx.add(idx_event2)
|
|
2022
|
+
data["Stop (s)"].append(float(event2[0]))
|
|
2023
|
+
data["Duration (s)"].append(float(event2[0] - event[0]))
|
|
2024
|
+
data["Comment start"].append(event[4])
|
|
2025
|
+
data["Comment stop"].append(event2[4])
|
|
2026
|
+
break
|
|
2027
|
+
else:
|
|
2028
|
+
return f"Some events are not paired in {obs_id}", pd.DataFrame()
|
|
2029
|
+
|
|
2030
|
+
else: # point
|
|
2031
|
+
data["Stop (s)"].append(float(event[0]))
|
|
2032
|
+
data["Duration (s)"].append(np.nan)
|
|
2033
|
+
data["Comment start"].append(event[4])
|
|
2034
|
+
data["Comment stop"].append(event[4])
|
|
2035
|
+
|
|
2036
|
+
# Set the display option to show all rows and columns
|
|
2037
|
+
pd.set_option("display.max_rows", None)
|
|
2038
|
+
pd.set_option("display.max_columns", None)
|
|
2039
|
+
|
|
2040
|
+
pd.DataFrame(data).info()
|
|
2041
|
+
|
|
2042
|
+
return "", pd.DataFrame(data)
|