boris-behav-obs 8.16.5__py3-none-any.whl → 9.7.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- boris/__init__.py +1 -1
- boris/__main__.py +1 -1
- boris/about.py +24 -36
- boris/add_modifier.py +88 -80
- boris/add_modifier_ui.py +235 -131
- boris/advanced_event_filtering.py +23 -29
- boris/analysis_plugins/__init__.py +0 -0
- boris/analysis_plugins/_latency.py +59 -0
- boris/analysis_plugins/irr_cohen_kappa.py +109 -0
- boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
- boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
- boris/analysis_plugins/number_of_occurences.py +22 -0
- boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
- boris/analysis_plugins/time_budget.py +61 -0
- boris/behav_coding_map_creator.py +228 -229
- boris/behavior_binary_table.py +33 -50
- boris/behaviors_coding_map.py +17 -18
- boris/boris_cli.py +6 -25
- boris/cmd_arguments.py +12 -1
- boris/coding_pad.py +16 -34
- boris/config.py +102 -50
- boris/config_file.py +55 -64
- boris/connections.py +105 -58
- boris/converters.py +13 -37
- boris/converters_ui.py +187 -110
- boris/cooccurence.py +250 -0
- boris/core.py +2108 -1275
- boris/core_qrc.py +15892 -10829
- boris/core_ui.py +941 -806
- boris/db_functions.py +17 -42
- boris/dev.py +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 +304 -0
- boris/irr.py +20 -57
- boris/latency.py +31 -24
- boris/measurement_widget.py +14 -18
- boris/media_file.py +17 -19
- boris/menu_options.py +16 -6
- boris/modifier_coding_map_creator.py +1013 -0
- boris/modifiers_coding_map.py +7 -9
- boris/mpv2.py +128 -35
- boris/observation.py +493 -210
- boris/observation_operations.py +1010 -391
- boris/observation_ui.py +573 -363
- boris/observations_list.py +51 -58
- boris/otx_parser.py +74 -68
- boris/param_panel.py +45 -59
- boris/param_panel_ui.py +254 -138
- boris/player_dock_widget.py +91 -56
- boris/plot_data_module.py +18 -53
- boris/plot_events.py +56 -153
- boris/plot_events_rt.py +16 -30
- boris/plot_spectrogram_rt.py +80 -56
- boris/plot_waveform_rt.py +23 -48
- boris/plugins.py +431 -0
- boris/portion/__init__.py +18 -8
- boris/portion/const.py +35 -18
- boris/portion/dict.py +5 -5
- boris/portion/func.py +2 -2
- boris/portion/interval.py +21 -41
- boris/portion/io.py +41 -32
- boris/preferences.py +298 -123
- boris/preferences_ui.py +664 -225
- boris/project.py +293 -270
- boris/project_functions.py +610 -537
- boris/project_import_export.py +204 -213
- boris/project_ui.py +673 -441
- boris/qrc_boris.py +6 -3
- boris/qrc_boris5.py +6 -3
- boris/select_modifiers.py +62 -90
- boris/select_observations.py +19 -197
- boris/select_subj_behav.py +67 -39
- boris/state_events.py +51 -33
- boris/subjects_pad.py +6 -8
- boris/synthetic_time_budget.py +42 -26
- boris/time_budget_functions.py +169 -169
- boris/time_budget_widget.py +77 -89
- boris/transitions.py +41 -41
- boris/utilities.py +562 -222
- boris/version.py +3 -3
- boris/video_equalizer.py +16 -14
- boris/video_equalizer_ui.py +199 -130
- boris/video_operations.py +78 -28
- boris/view_df.py +104 -0
- boris/view_df_ui.py +75 -0
- boris/write_event.py +240 -136
- boris_behav_obs-9.7.1.dist-info/METADATA +140 -0
- boris_behav_obs-9.7.1.dist-info/RECORD +109 -0
- {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.1.dist-info}/WHEEL +1 -1
- boris_behav_obs-9.7.1.dist-info/entry_points.txt +2 -0
- boris/README.TXT +0 -22
- boris/add_modifier.ui +0 -323
- boris/converters.ui +0 -289
- boris/core.qrc +0 -37
- boris/core.ui +0 -1571
- boris/edit_event.ui +0 -233
- boris/icons/logo_eye.ico +0 -0
- boris/map_creator.py +0 -982
- boris/observation.ui +0 -814
- boris/param_panel.ui +0 -379
- boris/preferences.ui +0 -537
- boris/project.ui +0 -1074
- boris/vlc_local.py +0 -90
- boris_behav_obs-8.16.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.1.dist-info/licenses}/LICENSE.TXT +0 -0
- {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.1.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,8 +414,12 @@ 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)
|
|
@@ -449,9 +428,7 @@ def check_project_integrity(
|
|
|
449
428
|
|
|
450
429
|
# check for unpaired state events
|
|
451
430
|
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
|
-
)
|
|
431
|
+
ok, msg = check_state_events_obs(obs_id, pj[cfg.ETHOGRAM], pj[cfg.OBSERVATIONS][obs_id], time_format)
|
|
455
432
|
if not ok:
|
|
456
433
|
out += "<br><br>" if out else ""
|
|
457
434
|
out += f"Observation: <b>{obs_id}</b><br>{msg}"
|
|
@@ -460,10 +437,7 @@ def check_project_integrity(
|
|
|
460
437
|
for idx in pj[cfg.ETHOGRAM]:
|
|
461
438
|
if cfg.BEHAVIOR_CATEGORY in pj[cfg.ETHOGRAM][idx]:
|
|
462
439
|
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
|
-
):
|
|
440
|
+
if pj[cfg.ETHOGRAM][idx][cfg.BEHAVIOR_CATEGORY] not in pj[cfg.BEHAVIORAL_CATEGORIES]:
|
|
467
441
|
out += "<br><br>" if out else ""
|
|
468
442
|
out += (
|
|
469
443
|
f"The behavior <b>{pj[cfg.ETHOGRAM][idx][cfg.BEHAVIOR_CODE]}</b> belongs "
|
|
@@ -481,37 +455,40 @@ def check_project_integrity(
|
|
|
481
455
|
out += (
|
|
482
456
|
"The following <b>modifier</b> defined in ethogram "
|
|
483
457
|
"has leading/trailing spaces or special chars: "
|
|
484
|
-
f"<b>{util.replace_leading_trailing_chars(modifier_code
|
|
458
|
+
f"<b>{util.replace_leading_trailing_chars(modifier_code, old_char=' ', new_char='█')}</b>"
|
|
485
459
|
)
|
|
486
460
|
|
|
487
461
|
# check if all media are available
|
|
488
462
|
if media_file_available:
|
|
489
463
|
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
490
|
-
ok, msg = check_if_media_available(
|
|
491
|
-
pj[cfg.OBSERVATIONS][obs_id], project_file_name
|
|
492
|
-
)
|
|
464
|
+
ok, msg = check_if_media_available(pj[cfg.OBSERVATIONS][obs_id], project_file_name)
|
|
493
465
|
if not ok:
|
|
494
466
|
out += "<br><br>" if out else ""
|
|
495
467
|
out += f"Observation: <b>{obs_id}</b><br>{msg}"
|
|
496
468
|
|
|
497
|
-
|
|
469
|
+
out_events: str = ""
|
|
498
470
|
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
471
|
+
# check if timestamp between -2147483647 and 2147483647
|
|
472
|
+
for event in pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]:
|
|
473
|
+
timestamp = event[cfg.PJ_OBS_FIELDS[pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE]][cfg.TIME]]
|
|
474
|
+
if not timestamp.is_nan() and not (-2147483647 <= timestamp <= 2147483647):
|
|
475
|
+
out_events += f"Observation: <b>{obs_id}</b><br>The timestamp {timestamp} is not between -2147483647 and 2147483647.<br>"
|
|
499
476
|
|
|
500
|
-
|
|
501
|
-
if
|
|
502
|
-
continue
|
|
503
|
-
|
|
477
|
+
"""
|
|
478
|
+
# check if media length available
|
|
504
479
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.MEDIA:
|
|
505
480
|
for nplayer in cfg.ALL_PLAYERS:
|
|
506
481
|
if nplayer in pj[cfg.OBSERVATIONS][obs_id][cfg.FILE]:
|
|
507
482
|
for media_file in pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][nplayer]:
|
|
508
483
|
try:
|
|
509
|
-
pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO][cfg.LENGTH][
|
|
510
|
-
media_file
|
|
511
|
-
]
|
|
484
|
+
pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO][cfg.LENGTH][media_file]
|
|
512
485
|
except KeyError:
|
|
513
486
|
out += "<br><br>" if out else ""
|
|
514
487
|
out += f"Observation: <b>{obs_id}</b><br>Length not available for media file <b>{media_file}</b>"
|
|
488
|
+
"""
|
|
489
|
+
|
|
490
|
+
out += "<br><br>" if out else ""
|
|
491
|
+
out += out_events
|
|
515
492
|
|
|
516
493
|
# check for leading/trailing spaces/special chars in observation id
|
|
517
494
|
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
@@ -524,10 +501,7 @@ def check_project_integrity(
|
|
|
524
501
|
)
|
|
525
502
|
|
|
526
503
|
# 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
|
-
]
|
|
504
|
+
defined_var_label = [pj[cfg.INDEPENDENT_VARIABLES][idx]["label"] for idx in pj.get(cfg.INDEPENDENT_VARIABLES, {})]
|
|
531
505
|
not_defined: dict = {}
|
|
532
506
|
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
533
507
|
if cfg.INDEPENDENT_VARIABLES not in pj[cfg.OBSERVATIONS][obs_id]:
|
|
@@ -558,27 +532,86 @@ def check_project_integrity(
|
|
|
558
532
|
]
|
|
559
533
|
)
|
|
560
534
|
|
|
561
|
-
|
|
535
|
+
tmp_out: str = ""
|
|
562
536
|
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
563
537
|
if cfg.INDEPENDENT_VARIABLES not in pj[cfg.OBSERVATIONS][obs_id]:
|
|
564
538
|
continue
|
|
565
539
|
for var_label in pj[cfg.OBSERVATIONS][obs_id][cfg.INDEPENDENT_VARIABLES]:
|
|
566
540
|
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 += (
|
|
541
|
+
if pj[cfg.OBSERVATIONS][obs_id][cfg.INDEPENDENT_VARIABLES][var_label] not in defined_set_var_label[var_label].split(","):
|
|
542
|
+
tmp_out += (
|
|
572
543
|
f"{obs_id}: the <b>{pj[cfg.OBSERVATIONS][obs_id][cfg.INDEPENDENT_VARIABLES][var_label]}</b> value "
|
|
573
544
|
f" is not allowed for {var_label} (choose between {defined_set_var_label[var_label]})<br>"
|
|
574
545
|
)
|
|
546
|
+
if tmp_out:
|
|
547
|
+
out += "<br><br>" if out else ""
|
|
548
|
+
out += tmp_out
|
|
549
|
+
|
|
550
|
+
# check if coded subjects are defined in the subjects list
|
|
551
|
+
tmp_out: str = ""
|
|
552
|
+
subjects_list: list = [pj[cfg.SUBJECTS][x]["name"] for x in pj[cfg.SUBJECTS]]
|
|
553
|
+
coded_subjects = set(util.flatten_list([[y[1] for y in pj[cfg.OBSERVATIONS][x].get(cfg.EVENTS, [])] for x in pj[cfg.OBSERVATIONS]]))
|
|
554
|
+
|
|
555
|
+
for subject in coded_subjects:
|
|
556
|
+
if subject and subject not in subjects_list:
|
|
557
|
+
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>"
|
|
558
|
+
if tmp_out:
|
|
559
|
+
out += "<br><br>" if out else ""
|
|
560
|
+
out += tmp_out
|
|
561
|
+
|
|
562
|
+
# check if media file have info in media_info section of project
|
|
563
|
+
tmp_out: str = ""
|
|
564
|
+
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
565
|
+
for player in pj[cfg.OBSERVATIONS][obs_id][cfg.FILE]:
|
|
566
|
+
for media_file in pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][player]:
|
|
567
|
+
for info in (cfg.LENGTH, cfg.FPS, cfg.HAS_AUDIO, cfg.HAS_VIDEO):
|
|
568
|
+
if media_file not in pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO].get(info, {}):
|
|
569
|
+
tmp_out += f"Observation <b>{obs_id}</b>:<br>"
|
|
570
|
+
tmp_out += f"The media file {media_file} has no <b>{info}</b> info.<br>"
|
|
571
|
+
if tmp_out:
|
|
572
|
+
tmp_out += "<br>You should repick the media file to fix this issue."
|
|
573
|
+
out += "<br><br>" if out else ""
|
|
574
|
+
out += tmp_out
|
|
575
|
+
|
|
576
|
+
# check if the number of coded modifiers correspond to the number of sets of modifier
|
|
577
|
+
obs_results: dict = {}
|
|
578
|
+
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
579
|
+
for event_idx, event in enumerate(pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]):
|
|
580
|
+
# event[2]
|
|
581
|
+
for idx in pj[cfg.ETHOGRAM]:
|
|
582
|
+
if pj[cfg.ETHOGRAM][idx]["code"] == event[2]:
|
|
583
|
+
break
|
|
584
|
+
else:
|
|
585
|
+
raise
|
|
586
|
+
if (not event[3]) and not pj[cfg.ETHOGRAM][idx][cfg.MODIFIERS]:
|
|
587
|
+
continue
|
|
588
|
+
|
|
589
|
+
if len(event[3].split("|")) != len(pj[cfg.ETHOGRAM][idx][cfg.MODIFIERS]):
|
|
590
|
+
print("behavior", event[2])
|
|
591
|
+
print(f"modifier(s) #{event[3]}#", len(event[3].split("|")))
|
|
592
|
+
print(pj[cfg.ETHOGRAM][idx]["code"], pj[cfg.ETHOGRAM][idx][cfg.MODIFIERS])
|
|
593
|
+
print()
|
|
594
|
+
if obs_id not in obs_results:
|
|
595
|
+
obs_results[obs_id] = []
|
|
596
|
+
|
|
597
|
+
obs_results[obs_id].append(
|
|
598
|
+
(
|
|
599
|
+
f"Event #{event_idx}: the coded modifiers for {event[2]} are {len(event[3].split('|'))} "
|
|
600
|
+
f"but {len(pj[cfg.ETHOGRAM][idx][cfg.MODIFIERS])} sets were defined in ethogram."
|
|
601
|
+
)
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
if obs_results:
|
|
605
|
+
out += "<br><br>" if out else ""
|
|
606
|
+
for o in obs_results:
|
|
607
|
+
out += f"<br>Observation <b>{o}</b>:<br>"
|
|
608
|
+
out += "<br>".join(obs_results[o])
|
|
609
|
+
out += "<br><br>"
|
|
575
610
|
|
|
576
611
|
return out
|
|
577
612
|
|
|
578
613
|
|
|
579
|
-
def create_subtitles(
|
|
580
|
-
pj: dict, selected_observations: list, parameters: dict, export_dir: str
|
|
581
|
-
) -> Tuple[bool, str]:
|
|
614
|
+
def create_subtitles(pj: dict, selected_observations: list, parameters: dict, export_dir: str) -> Tuple[bool, str]:
|
|
582
615
|
"""
|
|
583
616
|
create subtitles for selected observations, subjects and behaviors
|
|
584
617
|
|
|
@@ -608,9 +641,9 @@ def create_subtitles(
|
|
|
608
641
|
return "", ""
|
|
609
642
|
else:
|
|
610
643
|
return (
|
|
611
|
-
f"""<font color="{
|
|
612
|
-
|
|
613
|
-
|
|
644
|
+
f"""<font color="{
|
|
645
|
+
cfg.subtitlesColors[parameters[cfg.SELECTED_SUBJECTS].index(row["subject"]) % len(cfg.subtitlesColors)]
|
|
646
|
+
}">""",
|
|
614
647
|
"</font>",
|
|
615
648
|
)
|
|
616
649
|
|
|
@@ -677,20 +710,12 @@ def create_subtitles(
|
|
|
677
710
|
modifiers_str = f"\n{row['modifiers'].replace('|', ', ')}"
|
|
678
711
|
else:
|
|
679
712
|
modifiers_str = ""
|
|
680
|
-
out += (
|
|
681
|
-
"{idx}\n"
|
|
682
|
-
"{start} --> {stop}\n"
|
|
683
|
-
"{col1}{subject}: {behavior}"
|
|
684
|
-
"{modifiers}"
|
|
685
|
-
"{col2}\n\n"
|
|
686
|
-
).format(
|
|
713
|
+
out += ("{idx}\n{start} --> {stop}\n{col1}{subject}: {behavior}{modifiers}{col2}\n\n").format(
|
|
687
714
|
idx=idx + 1,
|
|
688
715
|
start=util.seconds2time(row["start"]).replace(".", ","),
|
|
689
|
-
stop=util.seconds2time(
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
else row["stop"] + cfg.POINT_EVENT_ST_DURATION
|
|
693
|
-
).replace(".", ","),
|
|
716
|
+
stop=util.seconds2time(row["stop"] if row["type"] == cfg.STATE else row["stop"] + cfg.POINT_EVENT_ST_DURATION).replace(
|
|
717
|
+
".", ","
|
|
718
|
+
),
|
|
694
719
|
col1=col1,
|
|
695
720
|
col2=col2,
|
|
696
721
|
subject=row["subject"],
|
|
@@ -698,14 +723,9 @@ def create_subtitles(
|
|
|
698
723
|
modifiers=modifiers_str,
|
|
699
724
|
)
|
|
700
725
|
|
|
701
|
-
file_name =
|
|
702
|
-
util.safeFileName(obs_id)
|
|
703
|
-
).with_suffix(".srt")
|
|
726
|
+
file_name = Path(export_dir) / Path(util.safeFileName(obs_id)).with_suffix(".srt")
|
|
704
727
|
|
|
705
|
-
if (
|
|
706
|
-
mem_command not in (cfg.OVERWRITE_ALL, cfg.SKIP_ALL)
|
|
707
|
-
and file_name.is_file()
|
|
708
|
-
):
|
|
728
|
+
if mem_command not in (cfg.OVERWRITE_ALL, cfg.SKIP_ALL) and file_name.is_file():
|
|
709
729
|
mem_command = dialog.MessageDialog(
|
|
710
730
|
cfg.programName,
|
|
711
731
|
f"The file {file_name} already exists.",
|
|
@@ -730,19 +750,13 @@ def create_subtitles(
|
|
|
730
750
|
msg += f"observation: {obs_id}\ngave the following error:\n{str(sys.exc_info()[1])}\n"
|
|
731
751
|
|
|
732
752
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.MEDIA:
|
|
733
|
-
|
|
734
753
|
for nplayer in cfg.ALL_PLAYERS:
|
|
735
754
|
if not pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][nplayer]:
|
|
736
755
|
continue
|
|
737
756
|
init = 0
|
|
738
757
|
for media_file in pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][nplayer]:
|
|
739
758
|
try:
|
|
740
|
-
end =
|
|
741
|
-
init
|
|
742
|
-
+ pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO][cfg.LENGTH][
|
|
743
|
-
media_file
|
|
744
|
-
]
|
|
745
|
-
)
|
|
759
|
+
end = init + pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO][cfg.LENGTH][media_file]
|
|
746
760
|
except KeyError:
|
|
747
761
|
return (
|
|
748
762
|
False,
|
|
@@ -760,12 +774,8 @@ def create_subtitles(
|
|
|
760
774
|
"AND behavior in ({}) "
|
|
761
775
|
"ORDER BY start"
|
|
762
776
|
).format(
|
|
763
|
-
",".join(
|
|
764
|
-
|
|
765
|
-
),
|
|
766
|
-
",".join(
|
|
767
|
-
["?"] * len(parameters[cfg.SELECTED_BEHAVIORS])
|
|
768
|
-
),
|
|
777
|
+
",".join(["?"] * len(parameters[cfg.SELECTED_SUBJECTS])),
|
|
778
|
+
",".join(["?"] * len(parameters[cfg.SELECTED_BEHAVIORS])),
|
|
769
779
|
),
|
|
770
780
|
[
|
|
771
781
|
obs_id,
|
|
@@ -777,7 +787,6 @@ def create_subtitles(
|
|
|
777
787
|
)
|
|
778
788
|
|
|
779
789
|
else: # arbitrary 'time interval'
|
|
780
|
-
|
|
781
790
|
cursor.execute(
|
|
782
791
|
(
|
|
783
792
|
"SELECT subject, behavior, type, start, stop, modifiers FROM aggregated_events "
|
|
@@ -788,12 +797,8 @@ def create_subtitles(
|
|
|
788
797
|
"AND behavior in ({}) "
|
|
789
798
|
"ORDER BY start"
|
|
790
799
|
).format(
|
|
791
|
-
",".join(
|
|
792
|
-
|
|
793
|
-
),
|
|
794
|
-
",".join(
|
|
795
|
-
["?"] * len(parameters[cfg.SELECTED_BEHAVIORS])
|
|
796
|
-
),
|
|
800
|
+
",".join(["?"] * len(parameters[cfg.SELECTED_SUBJECTS])),
|
|
801
|
+
",".join(["?"] * len(parameters[cfg.SELECTED_BEHAVIORS])),
|
|
797
802
|
),
|
|
798
803
|
[
|
|
799
804
|
obs_id,
|
|
@@ -813,24 +818,11 @@ def create_subtitles(
|
|
|
813
818
|
else:
|
|
814
819
|
modifiers_str = ""
|
|
815
820
|
|
|
816
|
-
out += (
|
|
817
|
-
"{idx}\n"
|
|
818
|
-
"{start} --> {stop}\n"
|
|
819
|
-
"{col1}{subject}: {behavior}"
|
|
820
|
-
"{modifiers}"
|
|
821
|
-
"{col2}\n\n"
|
|
822
|
-
).format(
|
|
821
|
+
out += ("{idx}\n{start} --> {stop}\n{col1}{subject}: {behavior}{modifiers}{col2}\n\n").format(
|
|
823
822
|
idx=idx + 1,
|
|
824
|
-
start=util.seconds2time(row["start"] - init).replace(
|
|
825
|
-
".", ","
|
|
826
|
-
),
|
|
823
|
+
start=util.seconds2time(row["start"] - init).replace(".", ","),
|
|
827
824
|
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
|
|
825
|
+
(row["stop"] if row["type"] == cfg.STATE else row["stop"] + cfg.POINT_EVENT_ST_DURATION) - init
|
|
834
826
|
).replace(".", ","),
|
|
835
827
|
col1=col1,
|
|
836
828
|
col2=col2,
|
|
@@ -838,14 +830,9 @@ def create_subtitles(
|
|
|
838
830
|
behavior=row["behavior"],
|
|
839
831
|
modifiers=modifiers_str,
|
|
840
832
|
)
|
|
841
|
-
file_name =
|
|
842
|
-
pl.Path(media_file).stem
|
|
843
|
-
).with_suffix(".srt")
|
|
833
|
+
file_name = Path(export_dir) / Path(Path(media_file).stem).with_suffix(".srt")
|
|
844
834
|
|
|
845
|
-
if (
|
|
846
|
-
mem_command not in (cfg.OVERWRITE_ALL, cfg.SKIP_ALL)
|
|
847
|
-
and file_name.is_file()
|
|
848
|
-
):
|
|
835
|
+
if mem_command not in (cfg.OVERWRITE_ALL, cfg.SKIP_ALL) and file_name.is_file():
|
|
849
836
|
mem_command = dialog.MessageDialog(
|
|
850
837
|
cfg.programName,
|
|
851
838
|
f"The file {file_name} already exists.",
|
|
@@ -873,9 +860,7 @@ def create_subtitles(
|
|
|
873
860
|
return flag_ok, msg
|
|
874
861
|
|
|
875
862
|
|
|
876
|
-
def export_observations_list(
|
|
877
|
-
pj: dict, selected_observations: list, file_name: str, output_format: str
|
|
878
|
-
) -> bool:
|
|
863
|
+
def export_observations_list(pj: dict, selected_observations: list, file_name: str, output_format: str) -> bool:
|
|
879
864
|
"""
|
|
880
865
|
create file with a list of selected observations
|
|
881
866
|
|
|
@@ -905,17 +890,7 @@ def export_observations_list(
|
|
|
905
890
|
data.headers.extend(indep_var_header)
|
|
906
891
|
|
|
907
892
|
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
|
-
)
|
|
893
|
+
subjects_list = sorted(list(set([x[cfg.EVENT_SUBJECT_FIELD_IDX] for x in pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]])))
|
|
919
894
|
if "" in subjects_list:
|
|
920
895
|
subjects_list = [cfg.NO_FOCAL_SUBJECT] + subjects_list
|
|
921
896
|
subjects_list.remove("")
|
|
@@ -935,11 +910,7 @@ def export_observations_list(
|
|
|
935
910
|
if cfg.INDEPENDENT_VARIABLES in pj[cfg.OBSERVATIONS][obs_id]:
|
|
936
911
|
for var_label in indep_var_header:
|
|
937
912
|
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
|
-
)
|
|
913
|
+
indep_var.append(pj[cfg.OBSERVATIONS][obs_id][cfg.INDEPENDENT_VARIABLES][var_label])
|
|
943
914
|
else:
|
|
944
915
|
indep_var.append("")
|
|
945
916
|
|
|
@@ -954,13 +925,13 @@ def export_observations_list(
|
|
|
954
925
|
+ indep_var
|
|
955
926
|
)
|
|
956
927
|
|
|
957
|
-
if output_format in
|
|
928
|
+
if output_format in (cfg.TSV_EXT, cfg.CSV_EXT, cfg.HTML_EXT):
|
|
958
929
|
try:
|
|
959
930
|
with open(file_name, "wb") as f:
|
|
960
931
|
f.write(str.encode(data.export(output_format)))
|
|
961
932
|
except Exception:
|
|
962
933
|
return False
|
|
963
|
-
if output_format in [
|
|
934
|
+
if output_format in [cfg.ODS_EXT, cfg.XLS_EXT, cfg.XLSX_EXT]:
|
|
964
935
|
try:
|
|
965
936
|
with open(file_name, "wb") as f:
|
|
966
937
|
f.write(data.export(output_format))
|
|
@@ -987,15 +958,9 @@ def set_media_paths_relative_to_project_dir(pj: dict, project_file_name: str) ->
|
|
|
987
958
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.IMAGES:
|
|
988
959
|
for img_dir in pj[cfg.OBSERVATIONS][obs_id][cfg.DIRECTORIES_LIST]:
|
|
989
960
|
try:
|
|
990
|
-
|
|
961
|
+
Path(img_dir).relative_to(Path(project_file_name).parent)
|
|
991
962
|
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
|
-
|
|
963
|
+
if Path(img_dir).is_absolute() or not (Path(project_file_name).parent / Path(img_dir)).is_dir():
|
|
999
964
|
QMessageBox.critical(
|
|
1000
965
|
None,
|
|
1001
966
|
cfg.programName,
|
|
@@ -1006,23 +971,11 @@ def set_media_paths_relative_to_project_dir(pj: dict, project_file_name: str) ->
|
|
|
1006
971
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.MEDIA:
|
|
1007
972
|
for n_player in cfg.ALL_PLAYERS:
|
|
1008
973
|
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
|
-
):
|
|
974
|
+
for idx, media_file in enumerate(pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][n_player]):
|
|
1012
975
|
try:
|
|
1013
|
-
|
|
1014
|
-
pl.Path(project_file_name).parent
|
|
1015
|
-
)
|
|
976
|
+
Path(media_file).relative_to(Path(project_file_name).parent)
|
|
1016
977
|
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
|
-
|
|
978
|
+
if Path(media_file).is_absolute() or not (Path(project_file_name).parent / Path(media_file)).is_file():
|
|
1026
979
|
QMessageBox.critical(
|
|
1027
980
|
None,
|
|
1028
981
|
cfg.programName,
|
|
@@ -1036,25 +989,13 @@ def set_media_paths_relative_to_project_dir(pj: dict, project_file_name: str) ->
|
|
|
1036
989
|
# set media path and image dir relative to project dir
|
|
1037
990
|
flag_changed = False
|
|
1038
991
|
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
1039
|
-
|
|
1040
992
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.IMAGES:
|
|
1041
993
|
new_dir_list = []
|
|
1042
994
|
for img_dir in pj[cfg.OBSERVATIONS][obs_id][cfg.DIRECTORIES_LIST]:
|
|
1043
995
|
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
|
-
)
|
|
996
|
+
new_dir_list.append(str(Path(img_dir).relative_to(Path(project_file_name).parent)))
|
|
1051
997
|
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
|
-
):
|
|
998
|
+
if not Path(img_dir).is_absolute() and (Path(project_file_name).parent / Path(img_dir)).is_dir():
|
|
1058
999
|
new_dir_list.append(img_dir)
|
|
1059
1000
|
|
|
1060
1001
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.DIRECTORIES_LIST] != new_dir_list:
|
|
@@ -1064,23 +1005,11 @@ def set_media_paths_relative_to_project_dir(pj: dict, project_file_name: str) ->
|
|
|
1064
1005
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.MEDIA:
|
|
1065
1006
|
for n_player in cfg.ALL_PLAYERS:
|
|
1066
1007
|
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
|
-
):
|
|
1008
|
+
for idx, media_file in enumerate(pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][n_player]):
|
|
1070
1009
|
try:
|
|
1071
|
-
p = str(
|
|
1072
|
-
pl.Path(media_file).relative_to(
|
|
1073
|
-
pl.Path(project_file_name).parent
|
|
1074
|
-
)
|
|
1075
|
-
)
|
|
1010
|
+
p = str(Path(media_file).relative_to(Path(project_file_name).parent))
|
|
1076
1011
|
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
|
-
):
|
|
1012
|
+
if not Path(media_file).is_absolute() and (Path(project_file_name).parent / Path(media_file)).is_file():
|
|
1084
1013
|
p = media_file
|
|
1085
1014
|
if p != media_file:
|
|
1086
1015
|
flag_changed = True
|
|
@@ -1093,27 +1022,15 @@ def set_media_paths_relative_to_project_dir(pj: dict, project_file_name: str) ->
|
|
|
1093
1022
|
cfg.FPS,
|
|
1094
1023
|
]:
|
|
1095
1024
|
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
|
-
]
|
|
1025
|
+
info in pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO]
|
|
1026
|
+
and media_file in pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO][info]
|
|
1102
1027
|
):
|
|
1103
1028
|
# 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][
|
|
1029
|
+
pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO][info][p] = pj[cfg.OBSERVATIONS][obs_id][
|
|
1115
1030
|
cfg.MEDIA_INFO
|
|
1116
1031
|
][info][media_file]
|
|
1032
|
+
# remove old path
|
|
1033
|
+
del pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO][info][media_file]
|
|
1117
1034
|
return flag_changed
|
|
1118
1035
|
|
|
1119
1036
|
|
|
@@ -1133,18 +1050,10 @@ def set_data_paths_relative_to_project_dir(pj: dict, project_file_name: str) ->
|
|
|
1133
1050
|
for _, v in pj[cfg.OBSERVATIONS][obs_id].get(cfg.PLOT_DATA, {}).items():
|
|
1134
1051
|
if cfg.FILE_PATH in v:
|
|
1135
1052
|
try:
|
|
1136
|
-
|
|
1137
|
-
pl.Path(project_file_name).parent
|
|
1138
|
-
)
|
|
1053
|
+
Path(v[cfg.FILE_PATH]).relative_to(Path(project_file_name).parent)
|
|
1139
1054
|
except ValueError:
|
|
1140
1055
|
# 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
|
-
):
|
|
1056
|
+
if Path(v[cfg.FILE_PATH]).is_absolute() or not (Path(project_file_name).parent / Path(v[cfg.FILE_PATH])).is_file():
|
|
1148
1057
|
QMessageBox.critical(
|
|
1149
1058
|
None,
|
|
1150
1059
|
cfg.programName,
|
|
@@ -1158,26 +1067,15 @@ def set_data_paths_relative_to_project_dir(pj: dict, project_file_name: str) ->
|
|
|
1158
1067
|
|
|
1159
1068
|
flag_changed = False
|
|
1160
1069
|
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
1161
|
-
|
|
1162
1070
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] != cfg.MEDIA:
|
|
1163
1071
|
continue
|
|
1164
1072
|
for idx, v in pj[cfg.OBSERVATIONS][obs_id].get(cfg.PLOT_DATA, {}).items():
|
|
1165
1073
|
if cfg.FILE_PATH in v:
|
|
1166
1074
|
try:
|
|
1167
|
-
p = str(
|
|
1168
|
-
pl.Path(v[cfg.FILE_PATH]).relative_to(
|
|
1169
|
-
pl.Path(project_file_name).parent
|
|
1170
|
-
)
|
|
1171
|
-
)
|
|
1075
|
+
p = str(Path(v[cfg.FILE_PATH]).relative_to(Path(project_file_name).parent))
|
|
1172
1076
|
except ValueError:
|
|
1173
1077
|
# 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
|
-
):
|
|
1078
|
+
if not Path(v[cfg.FILE_PATH]).is_absolute() and (Path(project_file_name).parent / Path(v[cfg.FILE_PATH])).is_file():
|
|
1181
1079
|
p = v[cfg.FILE_PATH]
|
|
1182
1080
|
|
|
1183
1081
|
if p != v[cfg.FILE_PATH]:
|
|
@@ -1199,26 +1097,14 @@ def remove_data_files_path(pj: dict) -> None:
|
|
|
1199
1097
|
"""
|
|
1200
1098
|
|
|
1201
1099
|
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
1202
|
-
|
|
1203
1100
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] != cfg.MEDIA:
|
|
1204
1101
|
continue
|
|
1205
1102
|
if cfg.PLOT_DATA in pj[cfg.OBSERVATIONS][obs_id]:
|
|
1206
1103
|
for idx in pj[cfg.OBSERVATIONS][obs_id][cfg.PLOT_DATA]:
|
|
1207
1104
|
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
|
|
1105
|
+
p = str(Path(pj[cfg.OBSERVATIONS][obs_id][cfg.PLOT_DATA][idx]["file_path"]).name)
|
|
1106
|
+
if p != pj[cfg.OBSERVATIONS][obs_id][cfg.PLOT_DATA][idx]["file_path"]:
|
|
1107
|
+
pj[cfg.OBSERVATIONS][obs_id][cfg.PLOT_DATA][idx]["file_path"] = p
|
|
1222
1108
|
|
|
1223
1109
|
|
|
1224
1110
|
def remove_media_files_path(pj: dict, project_file_name: str) -> bool:
|
|
@@ -1236,19 +1122,16 @@ def remove_media_files_path(pj: dict, project_file_name: str) -> bool:
|
|
|
1236
1122
|
file_not_found = []
|
|
1237
1123
|
# check if media and images dir
|
|
1238
1124
|
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
1239
|
-
|
|
1240
1125
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.IMAGES:
|
|
1241
1126
|
for img_dir in pj[cfg.OBSERVATIONS][obs_id][cfg.DIRECTORIES_LIST]:
|
|
1242
|
-
if full_path(
|
|
1127
|
+
if full_path(Path(img_dir).name, project_file_name) == "":
|
|
1243
1128
|
file_not_found.append(img_dir)
|
|
1244
1129
|
|
|
1245
1130
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.MEDIA:
|
|
1246
1131
|
for n_player in cfg.ALL_PLAYERS:
|
|
1247
1132
|
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) == "":
|
|
1133
|
+
for idx, media_file in enumerate(pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][n_player]):
|
|
1134
|
+
if full_path(Path(media_file).name, project_file_name) == "":
|
|
1252
1135
|
file_not_found.append(media_file)
|
|
1253
1136
|
|
|
1254
1137
|
file_not_found = set(file_not_found)
|
|
@@ -1269,22 +1152,19 @@ def remove_media_files_path(pj: dict, project_file_name: str) -> bool:
|
|
|
1269
1152
|
|
|
1270
1153
|
flag_changed = False
|
|
1271
1154
|
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
1272
|
-
|
|
1273
1155
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.IMAGES:
|
|
1274
1156
|
new_img_dir_list = []
|
|
1275
1157
|
for img_dir in pj[cfg.OBSERVATIONS][obs_id][cfg.DIRECTORIES_LIST]:
|
|
1276
|
-
if img_dir !=
|
|
1158
|
+
if img_dir != Path(img_dir).name:
|
|
1277
1159
|
flag_changed = True
|
|
1278
|
-
new_img_dir_list.append(str(
|
|
1160
|
+
new_img_dir_list.append(str(Path(img_dir).name))
|
|
1279
1161
|
pj[cfg.OBSERVATIONS][obs_id][cfg.DIRECTORIES_LIST] = new_img_dir_list
|
|
1280
1162
|
|
|
1281
1163
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.MEDIA:
|
|
1282
1164
|
for n_player in cfg.ALL_PLAYERS:
|
|
1283
1165
|
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
|
|
1166
|
+
for idx, media_file in enumerate(pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][n_player]):
|
|
1167
|
+
p = Path(media_file).name
|
|
1288
1168
|
if p != media_file:
|
|
1289
1169
|
flag_changed = True
|
|
1290
1170
|
pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][n_player][idx] = p
|
|
@@ -1296,27 +1176,15 @@ def remove_media_files_path(pj: dict, project_file_name: str) -> bool:
|
|
|
1296
1176
|
cfg.FPS,
|
|
1297
1177
|
]:
|
|
1298
1178
|
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
|
-
]
|
|
1179
|
+
info in pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO]
|
|
1180
|
+
and media_file in pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO][info]
|
|
1305
1181
|
):
|
|
1306
1182
|
# 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][
|
|
1183
|
+
pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO][info][p] = pj[cfg.OBSERVATIONS][obs_id][
|
|
1318
1184
|
cfg.MEDIA_INFO
|
|
1319
1185
|
][info][media_file]
|
|
1186
|
+
# remove old path
|
|
1187
|
+
del pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO][info][media_file]
|
|
1320
1188
|
|
|
1321
1189
|
return flag_changed
|
|
1322
1190
|
|
|
@@ -1324,7 +1192,7 @@ def remove_media_files_path(pj: dict, project_file_name: str) -> bool:
|
|
|
1324
1192
|
def full_path(path: str, project_file_name: str) -> str:
|
|
1325
1193
|
"""
|
|
1326
1194
|
returns the media/data full path or the images directory full path
|
|
1327
|
-
add path of BORIS project if media/data with relative path
|
|
1195
|
+
add path of BORIS project if media/data/pictures dir with relative path
|
|
1328
1196
|
|
|
1329
1197
|
Args:
|
|
1330
1198
|
path (str): file path or images directory path
|
|
@@ -1334,12 +1202,12 @@ def full_path(path: str, project_file_name: str) -> str:
|
|
|
1334
1202
|
str: full path
|
|
1335
1203
|
"""
|
|
1336
1204
|
|
|
1337
|
-
source_path =
|
|
1205
|
+
source_path = Path(path)
|
|
1338
1206
|
if source_path.exists():
|
|
1339
1207
|
return str(source_path)
|
|
1340
1208
|
else:
|
|
1341
1209
|
# check relative path (to project path)
|
|
1342
|
-
project_path =
|
|
1210
|
+
project_path = Path(project_file_name)
|
|
1343
1211
|
if (project_path.parent / source_path).exists():
|
|
1344
1212
|
return str(project_path.parent / source_path)
|
|
1345
1213
|
else:
|
|
@@ -1358,22 +1226,22 @@ def observed_interval(observation: dict) -> Tuple[dec, dec]:
|
|
|
1358
1226
|
"""
|
|
1359
1227
|
if not observation[cfg.EVENTS]:
|
|
1360
1228
|
return (dec("0.0"), dec("0.0"))
|
|
1229
|
+
|
|
1361
1230
|
if observation[cfg.TYPE] in (cfg.MEDIA, cfg.LIVE):
|
|
1231
|
+
event_timestamp = [event[cfg.PJ_OBS_FIELDS[observation[cfg.TYPE]][cfg.TIME]] for event in observation[cfg.EVENTS]]
|
|
1232
|
+
|
|
1362
1233
|
return (
|
|
1363
|
-
min(
|
|
1364
|
-
|
|
1365
|
-
],
|
|
1366
|
-
max(observation[cfg.EVENTS])[
|
|
1367
|
-
cfg.PJ_OBS_FIELDS[observation[cfg.TYPE]][cfg.TIME]
|
|
1368
|
-
],
|
|
1234
|
+
min(event_timestamp),
|
|
1235
|
+
max(event_timestamp),
|
|
1369
1236
|
)
|
|
1370
1237
|
if observation[cfg.TYPE] == cfg.IMAGES:
|
|
1371
|
-
events = [
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1238
|
+
events = [x[cfg.PJ_OBS_FIELDS[observation[cfg.TYPE]][cfg.IMAGE_INDEX]] for x in observation[cfg.EVENTS]]
|
|
1239
|
+
# test if indexes contain NA
|
|
1240
|
+
try:
|
|
1241
|
+
dec(min(events))
|
|
1242
|
+
return (dec(min(events)), dec(max(events)))
|
|
1243
|
+
except Exception:
|
|
1244
|
+
return (dec("NaN"), dec("NaN"))
|
|
1377
1245
|
|
|
1378
1246
|
|
|
1379
1247
|
def events_start_stop(ethogram: dict, events: list, obs_type: str) -> List[tuple]:
|
|
@@ -1391,7 +1259,6 @@ def events_start_stop(ethogram: dict, events: list, obs_type: str) -> List[tuple
|
|
|
1391
1259
|
|
|
1392
1260
|
events_flagged: list = []
|
|
1393
1261
|
for idx, event in enumerate(events):
|
|
1394
|
-
|
|
1395
1262
|
_, subject, code, modifier = event[: cfg.EVENT_MODIFIER_FIELD_IDX + 1]
|
|
1396
1263
|
|
|
1397
1264
|
# check if code is state
|
|
@@ -1441,11 +1308,7 @@ def extract_observed_subjects(pj: dict, selected_observations: list) -> list:
|
|
|
1441
1308
|
observed_subjects = []
|
|
1442
1309
|
|
|
1443
1310
|
# 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
|
-
]:
|
|
1311
|
+
for events in [pj[cfg.OBSERVATIONS][x][cfg.EVENTS] for x in pj[cfg.OBSERVATIONS] if x in selected_observations]:
|
|
1449
1312
|
for event in events:
|
|
1450
1313
|
observed_subjects.append(event[cfg.EVENT_SUBJECT_FIELD_IDX])
|
|
1451
1314
|
|
|
@@ -1453,7 +1316,7 @@ def extract_observed_subjects(pj: dict, selected_observations: list) -> list:
|
|
|
1453
1316
|
return list(set(observed_subjects))
|
|
1454
1317
|
|
|
1455
1318
|
|
|
1456
|
-
def open_project_json(
|
|
1319
|
+
def open_project_json(project_file_name: str) -> tuple:
|
|
1457
1320
|
"""
|
|
1458
1321
|
open BORIS project file in json format or GZ compressed json format
|
|
1459
1322
|
|
|
@@ -1467,37 +1330,37 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1467
1330
|
str: message
|
|
1468
1331
|
"""
|
|
1469
1332
|
|
|
1470
|
-
logging.debug(f"
|
|
1333
|
+
logging.debug(f"open_project_json function: {project_file_name}")
|
|
1471
1334
|
|
|
1472
|
-
projectChanged = False
|
|
1473
|
-
msg = ""
|
|
1335
|
+
projectChanged: bool = False
|
|
1336
|
+
msg: str = ""
|
|
1474
1337
|
|
|
1475
|
-
if not
|
|
1338
|
+
if not Path(project_file_name).is_file():
|
|
1476
1339
|
return (
|
|
1477
|
-
|
|
1340
|
+
project_file_name,
|
|
1478
1341
|
projectChanged,
|
|
1479
|
-
{"error": f"File {
|
|
1342
|
+
{"error": f"File {project_file_name} not found"},
|
|
1480
1343
|
msg,
|
|
1481
1344
|
)
|
|
1482
1345
|
|
|
1483
1346
|
try:
|
|
1484
|
-
if
|
|
1485
|
-
file_in = gzip.open(
|
|
1347
|
+
if project_file_name.endswith(".boris.gz"):
|
|
1348
|
+
file_in = gzip.open(project_file_name, mode="rt", encoding="utf-8")
|
|
1486
1349
|
else:
|
|
1487
|
-
file_in = open(
|
|
1350
|
+
file_in = open(project_file_name, "r")
|
|
1488
1351
|
file_content = file_in.read()
|
|
1489
1352
|
except PermissionError:
|
|
1490
1353
|
return (
|
|
1491
|
-
|
|
1354
|
+
project_file_name,
|
|
1492
1355
|
projectChanged,
|
|
1493
|
-
{"error": f"File {
|
|
1356
|
+
{"error": f"File {project_file_name}: Permission denied"},
|
|
1494
1357
|
msg,
|
|
1495
1358
|
)
|
|
1496
1359
|
except Exception:
|
|
1497
1360
|
return (
|
|
1498
|
-
|
|
1361
|
+
project_file_name,
|
|
1499
1362
|
projectChanged,
|
|
1500
|
-
{"error": f"Error on file {
|
|
1363
|
+
{"error": f"Error on file {project_file_name}: {sys.exc_info()[1]}"},
|
|
1501
1364
|
msg,
|
|
1502
1365
|
)
|
|
1503
1366
|
|
|
@@ -1505,16 +1368,16 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1505
1368
|
pj = json.loads(file_content)
|
|
1506
1369
|
except json.decoder.JSONDecodeError:
|
|
1507
1370
|
return (
|
|
1508
|
-
|
|
1371
|
+
project_file_name,
|
|
1509
1372
|
projectChanged,
|
|
1510
1373
|
{"error": "This project file seems corrupted"},
|
|
1511
1374
|
msg,
|
|
1512
1375
|
)
|
|
1513
1376
|
except Exception:
|
|
1514
1377
|
return (
|
|
1515
|
-
|
|
1378
|
+
project_file_name,
|
|
1516
1379
|
projectChanged,
|
|
1517
|
-
{"error": f"Error on file {
|
|
1380
|
+
{"error": f"Error on file {project_file_name}: {sys.exc_info()[1]}"},
|
|
1518
1381
|
msg,
|
|
1519
1382
|
)
|
|
1520
1383
|
|
|
@@ -1534,11 +1397,9 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1534
1397
|
projectChanged = True
|
|
1535
1398
|
|
|
1536
1399
|
# 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__):
|
|
1400
|
+
if cfg.PROJECT_VERSION in pj and util.versiontuple(pj[cfg.PROJECT_VERSION]) > util.versiontuple(version.__version__):
|
|
1540
1401
|
return (
|
|
1541
|
-
|
|
1402
|
+
project_file_name,
|
|
1542
1403
|
projectChanged,
|
|
1543
1404
|
{
|
|
1544
1405
|
"error": (
|
|
@@ -1551,13 +1412,11 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1551
1412
|
|
|
1552
1413
|
# check if old version v. 0 *.obs
|
|
1553
1414
|
if cfg.PROJECT_VERSION not in pj:
|
|
1554
|
-
|
|
1555
1415
|
# convert VIDEO, AUDIO -> MEDIA
|
|
1556
1416
|
pj[cfg.PROJECT_VERSION] = cfg.project_format_version
|
|
1557
1417
|
projectChanged = True
|
|
1558
1418
|
|
|
1559
1419
|
for obs in [x for x in pj[cfg.OBSERVATIONS]]:
|
|
1560
|
-
|
|
1561
1420
|
# remove 'replace audio' key
|
|
1562
1421
|
if "replace audio" in pj[cfg.OBSERVATIONS][obs]:
|
|
1563
1422
|
del pj[cfg.OBSERVATIONS][obs]["replace audio"]
|
|
@@ -1583,15 +1442,13 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1583
1442
|
f"The project file was converted to the new format (v. {cfg.project_format_version}) in use with your version of BORIS.<br>"
|
|
1584
1443
|
"Choose a new file name for saving it."
|
|
1585
1444
|
)
|
|
1586
|
-
|
|
1445
|
+
project_file_name = ""
|
|
1587
1446
|
|
|
1588
1447
|
# update modifiers to JSON format
|
|
1589
1448
|
|
|
1590
1449
|
# check if project format version < 4 (modifiers were str)
|
|
1591
1450
|
project_lowerthan4 = False
|
|
1592
|
-
if cfg.PROJECT_VERSION in pj and util.versiontuple(
|
|
1593
|
-
pj[cfg.PROJECT_VERSION]
|
|
1594
|
-
) < util.versiontuple("4.0"):
|
|
1451
|
+
if cfg.PROJECT_VERSION in pj and util.versiontuple(pj[cfg.PROJECT_VERSION]) < util.versiontuple("4.0"):
|
|
1595
1452
|
for idx in pj[cfg.ETHOGRAM]:
|
|
1596
1453
|
if pj[cfg.ETHOGRAM][idx]["modifiers"]:
|
|
1597
1454
|
if isinstance(pj[cfg.ETHOGRAM][idx]["modifiers"], str):
|
|
@@ -1609,40 +1466,35 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1609
1466
|
pj[cfg.ETHOGRAM][idx]["modifiers"] = {}
|
|
1610
1467
|
|
|
1611
1468
|
if not project_lowerthan4:
|
|
1612
|
-
msg = "The project version was updated from {} to {}".format(
|
|
1613
|
-
pj[cfg.PROJECT_VERSION], cfg.project_format_version
|
|
1614
|
-
)
|
|
1469
|
+
msg = "The project version was updated from {} to {}".format(pj[cfg.PROJECT_VERSION], cfg.project_format_version)
|
|
1615
1470
|
pj[cfg.PROJECT_VERSION] = cfg.project_format_version
|
|
1616
1471
|
projectChanged = True
|
|
1617
1472
|
|
|
1618
1473
|
# add category key if not found
|
|
1619
1474
|
for idx in pj[cfg.ETHOGRAM]:
|
|
1620
|
-
if
|
|
1621
|
-
pj[cfg.ETHOGRAM][idx][
|
|
1475
|
+
if cfg.BEHAVIOR_CATEGORY not in pj[cfg.ETHOGRAM][idx]:
|
|
1476
|
+
pj[cfg.ETHOGRAM][idx][cfg.BEHAVIOR_CATEGORY] = ""
|
|
1622
1477
|
|
|
1623
1478
|
# if one file is present in player #1 -> set "media_info" key with value of media_file_info
|
|
1624
1479
|
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
|
-
):
|
|
1480
|
+
if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] in [cfg.MEDIA] and cfg.MEDIA_INFO not in pj[cfg.OBSERVATIONS][obs]:
|
|
1629
1481
|
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO] = {
|
|
1630
1482
|
cfg.LENGTH: {},
|
|
1631
1483
|
cfg.FPS: {},
|
|
1632
1484
|
cfg.HAS_VIDEO: {},
|
|
1633
1485
|
cfg.HAS_AUDIO: {},
|
|
1634
1486
|
}
|
|
1635
|
-
for player in
|
|
1487
|
+
for player in (cfg.PLAYER1, cfg.PLAYER2):
|
|
1636
1488
|
# fix bug Anne Maijer 2017-07-17
|
|
1637
1489
|
if pj[cfg.OBSERVATIONS][obs][cfg.FILE] == []:
|
|
1638
1490
|
pj[cfg.OBSERVATIONS][obs][cfg.FILE] = {"1": [], "2": []}
|
|
1639
1491
|
|
|
1640
1492
|
for media_file_path in pj[cfg.OBSERVATIONS][obs]["file"][player]:
|
|
1641
1493
|
# FIX: ffmpeg path
|
|
1642
|
-
ret,
|
|
1494
|
+
ret, ffmpeg_bin = util.check_ffmpeg_path()
|
|
1643
1495
|
if not ret:
|
|
1644
1496
|
return (
|
|
1645
|
-
|
|
1497
|
+
project_file_name,
|
|
1646
1498
|
projectChanged,
|
|
1647
1499
|
{"error": "FFmpeg path not found"},
|
|
1648
1500
|
"",
|
|
@@ -1653,64 +1505,35 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1653
1505
|
r = util.accurate_media_analysis(ffmpeg_bin, media_file_path)
|
|
1654
1506
|
|
|
1655
1507
|
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"]
|
|
1508
|
+
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO][cfg.LENGTH][media_file_path] = float(r["duration"])
|
|
1509
|
+
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO][cfg.FPS][media_file_path] = float(r["fps"])
|
|
1510
|
+
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO][cfg.HAS_VIDEO][media_file_path] = r["has_video"]
|
|
1511
|
+
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO][cfg.HAS_AUDIO][media_file_path] = r["has_audio"]
|
|
1668
1512
|
projectChanged = True
|
|
1669
1513
|
else: # file path not found
|
|
1670
1514
|
if (
|
|
1671
1515
|
cfg.MEDIA_FILE_INFO in pj[cfg.OBSERVATIONS][obs]
|
|
1672
1516
|
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
|
|
1517
|
+
and len(pj[cfg.OBSERVATIONS][obs][cfg.FILE][cfg.PLAYER1]) == 1
|
|
1518
|
+
and len(pj[cfg.OBSERVATIONS][obs][cfg.FILE][cfg.PLAYER2]) == 0
|
|
1677
1519
|
):
|
|
1678
|
-
media_md5_key = list(
|
|
1679
|
-
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_FILE_INFO].keys()
|
|
1680
|
-
)[0]
|
|
1520
|
+
media_md5_key = list(pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_FILE_INFO].keys())[0]
|
|
1681
1521
|
# duration
|
|
1682
1522
|
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO] = {
|
|
1683
1523
|
cfg.LENGTH: {
|
|
1684
|
-
media_file_path: pj[cfg.OBSERVATIONS][obs][
|
|
1685
|
-
cfg.MEDIA_FILE_INFO
|
|
1686
|
-
][media_md5_key]["video_length"]
|
|
1687
|
-
/ 1000
|
|
1524
|
+
media_file_path: pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_FILE_INFO][media_md5_key]["video_length"] / 1000
|
|
1688
1525
|
}
|
|
1689
1526
|
}
|
|
1690
1527
|
projectChanged = True
|
|
1691
1528
|
|
|
1692
1529
|
# FPS
|
|
1693
|
-
if
|
|
1694
|
-
"nframe"
|
|
1695
|
-
in pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_FILE_INFO][
|
|
1696
|
-
media_md5_key
|
|
1697
|
-
]
|
|
1698
|
-
):
|
|
1530
|
+
if "nframe" in pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_FILE_INFO][media_md5_key]:
|
|
1699
1531
|
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
|
-
)
|
|
1532
|
+
media_file_path: pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_FILE_INFO][media_md5_key]["nframe"]
|
|
1533
|
+
/ (pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_FILE_INFO][media_md5_key]["video_length"] / 1000)
|
|
1709
1534
|
}
|
|
1710
1535
|
else:
|
|
1711
|
-
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO][cfg.FPS] = {
|
|
1712
|
-
media_file_path: 0
|
|
1713
|
-
}
|
|
1536
|
+
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO][cfg.FPS] = {media_file_path: 0}
|
|
1714
1537
|
|
|
1715
1538
|
# update project to v.7 for time offset second player
|
|
1716
1539
|
project_lowerthan7 = False
|
|
@@ -1723,9 +1546,7 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1723
1546
|
for player in pj[cfg.OBSERVATIONS][obs][cfg.FILE]:
|
|
1724
1547
|
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO][cfg.OFFSET][player] = 0.0
|
|
1725
1548
|
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
|
-
)
|
|
1549
|
+
pj[cfg.OBSERVATIONS][obs][cfg.MEDIA_INFO][cfg.OFFSET]["2"] = float(pj[cfg.OBSERVATIONS][obs]["time offset second player"])
|
|
1729
1550
|
|
|
1730
1551
|
del pj[cfg.OBSERVATIONS][obs]["time offset second player"]
|
|
1731
1552
|
project_lowerthan7 = True
|
|
@@ -1739,49 +1560,49 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1739
1560
|
projectChanged = True
|
|
1740
1561
|
|
|
1741
1562
|
if project_lowerthan7:
|
|
1742
|
-
|
|
1743
1563
|
msg = f"The project was updated to the current project version ({cfg.project_format_version})."
|
|
1744
1564
|
|
|
1745
1565
|
try:
|
|
1746
|
-
old_project_file_name =
|
|
1747
|
-
|
|
1748
|
-
)
|
|
1749
|
-
copyfile(projectFileName, old_project_file_name)
|
|
1566
|
+
old_project_file_name = project_file_name.replace(".boris", f".v{pj['project_format_version']}.boris")
|
|
1567
|
+
copyfile(project_file_name, old_project_file_name)
|
|
1750
1568
|
msg += f"\n\nThe old file project was saved as {old_project_file_name}"
|
|
1751
1569
|
except Exception:
|
|
1752
|
-
|
|
1570
|
+
QMessageBox.critical(cfg.programName, f"Error saving old project to {old_project_file_name}")
|
|
1753
1571
|
|
|
1754
1572
|
pj[cfg.PROJECT_VERSION] = cfg.project_format_version
|
|
1755
1573
|
|
|
1756
|
-
|
|
1574
|
+
# sort events by time asc
|
|
1575
|
+
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
1576
|
+
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] in (cfg.LIVE, cfg.MEDIA):
|
|
1577
|
+
# sort events list using the first 3 items (time, subject, behavior)
|
|
1578
|
+
pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS].sort(key=lambda x: x[:3])
|
|
1579
|
+
|
|
1580
|
+
return project_file_name, projectChanged, pj, msg
|
|
1757
1581
|
|
|
1758
1582
|
|
|
1759
|
-
def event_type(code: str, ethogram: dict) -> str:
|
|
1583
|
+
def event_type(code: str, ethogram: dict) -> str | None:
|
|
1760
1584
|
"""
|
|
1761
|
-
returns type of event for code
|
|
1585
|
+
returns type of event for behavior code
|
|
1762
1586
|
|
|
1763
1587
|
Args:
|
|
1764
1588
|
ethogram (dict); ethogram of project
|
|
1765
1589
|
code (str): behavior code
|
|
1766
1590
|
|
|
1767
1591
|
Returns:
|
|
1768
|
-
str:
|
|
1592
|
+
str: behavior type
|
|
1769
1593
|
"""
|
|
1770
1594
|
|
|
1771
1595
|
for idx in ethogram:
|
|
1772
1596
|
if ethogram[idx][cfg.BEHAVIOR_CODE] == code:
|
|
1773
|
-
return ethogram[idx][cfg.TYPE]
|
|
1597
|
+
return ethogram[idx][cfg.TYPE]
|
|
1774
1598
|
return None
|
|
1775
1599
|
|
|
1776
1600
|
|
|
1777
|
-
def fix_unpaired_state_events(
|
|
1778
|
-
ethogram: dict, observation: dict, fix_at_time: dec
|
|
1779
|
-
) -> list:
|
|
1601
|
+
def fix_unpaired_state_events(ethogram: dict, observation: dict, fix_at_time: dec) -> list:
|
|
1780
1602
|
"""
|
|
1781
1603
|
fix unpaired state events in observation
|
|
1782
1604
|
|
|
1783
1605
|
Args:
|
|
1784
|
-
obsId (str): observation id
|
|
1785
1606
|
ethogram (dict): ethogram dictionary
|
|
1786
1607
|
observation (dict): observation dictionary
|
|
1787
1608
|
fix_at_time (Decimal): time to fix the unpaired events
|
|
@@ -1790,31 +1611,23 @@ def fix_unpaired_state_events(
|
|
|
1790
1611
|
list: list of events with state events fixed
|
|
1791
1612
|
"""
|
|
1792
1613
|
|
|
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}
|
|
1614
|
+
closing_events_to_add: list = []
|
|
1615
|
+
subjects: list = [event[cfg.EVENT_SUBJECT_FIELD_IDX] for event in observation[cfg.EVENTS]]
|
|
1616
|
+
ethogram_behaviors: dict = {ethogram[idx][cfg.BEHAVIOR_CODE] for idx in ethogram}
|
|
1796
1617
|
|
|
1797
1618
|
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
|
|
1619
|
+
behaviors: list = [
|
|
1620
|
+
event[cfg.EVENT_BEHAVIOR_FIELD_IDX] for event in observation[cfg.EVENTS] if event[cfg.EVENT_SUBJECT_FIELD_IDX] == subject
|
|
1803
1621
|
]
|
|
1804
1622
|
|
|
1805
1623
|
for behavior in sorted(set(behaviors)):
|
|
1806
|
-
if (behavior in ethogram_behaviors) and (
|
|
1807
|
-
cfg.STATE in event_type(behavior, ethogram).upper()
|
|
1808
|
-
):
|
|
1809
|
-
|
|
1624
|
+
if (behavior in ethogram_behaviors) and (event_type(behavior, ethogram) in cfg.STATE_EVENT_TYPES):
|
|
1810
1625
|
lst, memTime = [], {}
|
|
1811
1626
|
for event in [
|
|
1812
1627
|
event
|
|
1813
1628
|
for event in observation[cfg.EVENTS]
|
|
1814
|
-
if event[cfg.EVENT_BEHAVIOR_FIELD_IDX] == behavior
|
|
1815
|
-
and event[cfg.EVENT_SUBJECT_FIELD_IDX] == subject
|
|
1629
|
+
if event[cfg.EVENT_BEHAVIOR_FIELD_IDX] == behavior and event[cfg.EVENT_SUBJECT_FIELD_IDX] == subject
|
|
1816
1630
|
]:
|
|
1817
|
-
|
|
1818
1631
|
behav_modif = [
|
|
1819
1632
|
event[cfg.EVENT_BEHAVIOR_FIELD_IDX],
|
|
1820
1633
|
event[cfg.EVENT_MODIFIER_FIELD_IDX],
|
|
@@ -1828,19 +1641,78 @@ def fix_unpaired_state_events(
|
|
|
1828
1641
|
memTime[str(behav_modif)] = event[cfg.EVENT_TIME_FIELD_IDX]
|
|
1829
1642
|
|
|
1830
1643
|
for event in lst:
|
|
1644
|
+
last_event_time = max([fix_at_time] + [x[0] for x in closing_events_to_add])
|
|
1831
1645
|
|
|
1832
|
-
|
|
1833
|
-
[
|
|
1646
|
+
closing_events_to_add.append(
|
|
1647
|
+
[
|
|
1648
|
+
last_event_time + dec("0.001"),
|
|
1649
|
+
subject,
|
|
1650
|
+
behavior,
|
|
1651
|
+
event[1], # modifiers
|
|
1652
|
+
"Event automatically added by the fix unpaired state events function",
|
|
1653
|
+
cfg.NA, # frame index
|
|
1654
|
+
]
|
|
1834
1655
|
)
|
|
1835
1656
|
|
|
1657
|
+
return closing_events_to_add
|
|
1658
|
+
|
|
1659
|
+
|
|
1660
|
+
def fix_unpaired_state_events2(ethogram: dict, events: list, fix_at_time: dec) -> list:
|
|
1661
|
+
"""
|
|
1662
|
+
fix unpaired state events in events list
|
|
1663
|
+
|
|
1664
|
+
Args:
|
|
1665
|
+
ethogram (dict): ethogram dictionary
|
|
1666
|
+
events (list): list of events
|
|
1667
|
+
fix_at_time (Decimal): time to fix the unpaired events
|
|
1668
|
+
|
|
1669
|
+
Returns:
|
|
1670
|
+
list: list of events with state events fixed
|
|
1671
|
+
"""
|
|
1672
|
+
|
|
1673
|
+
logging.debug("fix_unpaired_state_events2 function")
|
|
1674
|
+
|
|
1675
|
+
closing_events_to_add: list = []
|
|
1676
|
+
subjects: list = [event[cfg.EVENT_SUBJECT_FIELD_IDX] for event in events]
|
|
1677
|
+
ethogram_behaviors: dict = {ethogram[idx][cfg.BEHAVIOR_CODE] for idx in ethogram}
|
|
1678
|
+
|
|
1679
|
+
for subject in sorted(set(subjects)):
|
|
1680
|
+
behaviors: list = [event[cfg.EVENT_BEHAVIOR_FIELD_IDX] for event in events if event[cfg.EVENT_SUBJECT_FIELD_IDX] == subject]
|
|
1681
|
+
|
|
1682
|
+
for behavior in sorted(set(behaviors)):
|
|
1683
|
+
if (behavior in ethogram_behaviors) and (event_type(behavior, ethogram) in cfg.STATE_EVENT_TYPES):
|
|
1684
|
+
lst: list = []
|
|
1685
|
+
memTime: dict = {}
|
|
1686
|
+
for event in [
|
|
1687
|
+
event
|
|
1688
|
+
for event in events
|
|
1689
|
+
if event[cfg.EVENT_BEHAVIOR_FIELD_IDX] == behavior and event[cfg.EVENT_SUBJECT_FIELD_IDX] == subject
|
|
1690
|
+
]:
|
|
1691
|
+
behav_modif = [
|
|
1692
|
+
event[cfg.EVENT_BEHAVIOR_FIELD_IDX],
|
|
1693
|
+
event[cfg.EVENT_MODIFIER_FIELD_IDX],
|
|
1694
|
+
]
|
|
1695
|
+
|
|
1696
|
+
if behav_modif in lst:
|
|
1697
|
+
lst.remove(behav_modif)
|
|
1698
|
+
del memTime[str(behav_modif)]
|
|
1699
|
+
else:
|
|
1700
|
+
lst.append(behav_modif)
|
|
1701
|
+
memTime[str(behav_modif)] = event[cfg.EVENT_TIME_FIELD_IDX]
|
|
1702
|
+
|
|
1703
|
+
for event in lst:
|
|
1704
|
+
last_event_time = max([fix_at_time] + [x[0] for x in closing_events_to_add])
|
|
1705
|
+
|
|
1836
1706
|
closing_events_to_add.append(
|
|
1837
1707
|
[
|
|
1838
|
-
last_event_time + dec("0.001"),
|
|
1708
|
+
# last_event_time + dec("0.001"),
|
|
1709
|
+
last_event_time,
|
|
1839
1710
|
subject,
|
|
1840
1711
|
behavior,
|
|
1841
|
-
event[1],
|
|
1842
|
-
"",
|
|
1843
|
-
|
|
1712
|
+
event[1], # modifiers
|
|
1713
|
+
"Event automatically added by the fix unpaired state events function",
|
|
1714
|
+
cfg.NA, # frame index
|
|
1715
|
+
]
|
|
1844
1716
|
)
|
|
1845
1717
|
|
|
1846
1718
|
return closing_events_to_add
|
|
@@ -1867,8 +1739,11 @@ def explore_project(self) -> None:
|
|
|
1867
1739
|
manage double-click on tablewidget of explore project results
|
|
1868
1740
|
"""
|
|
1869
1741
|
observation_operations.load_observation(self, obs_id, cfg.VIEW)
|
|
1870
|
-
|
|
1871
|
-
self.
|
|
1742
|
+
|
|
1743
|
+
self.tv_events.selectRow(event_idx - 1)
|
|
1744
|
+
index = self.tv_events.model().index(event_idx - 1, 0)
|
|
1745
|
+
self.tv_events.scrollTo(index, QAbstractItemView.EnsureVisible)
|
|
1746
|
+
# self.twEvents.scrollToItem(self.twEvents.item(event_idx - 1, 0))
|
|
1872
1747
|
|
|
1873
1748
|
elements_list = ("Subject", "Behavior", "Modifier", "Comment")
|
|
1874
1749
|
elements = []
|
|
@@ -1891,9 +1766,7 @@ def explore_project(self) -> None:
|
|
|
1891
1766
|
nb_fields += explore_dlg.elements[element].text() != ""
|
|
1892
1767
|
|
|
1893
1768
|
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
|
-
):
|
|
1769
|
+
for event_idx, event in enumerate(self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]):
|
|
1897
1770
|
nb_results = 0
|
|
1898
1771
|
for text, idx in (
|
|
1899
1772
|
(explore_dlg.elements["Subject"].text(), cfg.EVENT_SUBJECT_FIELD_IDX),
|
|
@@ -1904,14 +1777,8 @@ def explore_project(self) -> None:
|
|
|
1904
1777
|
if text:
|
|
1905
1778
|
if any(
|
|
1906
1779
|
(
|
|
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
|
-
),
|
|
1780
|
+
(explore_dlg.elements["Case sensitive"].isChecked() and text in event[idx]),
|
|
1781
|
+
(not explore_dlg.elements["Case sensitive"].isChecked() and text.upper() in event[idx].upper()),
|
|
1915
1782
|
)
|
|
1916
1783
|
):
|
|
1917
1784
|
nb_results += 1
|
|
@@ -1928,26 +1795,232 @@ def explore_project(self) -> None:
|
|
|
1928
1795
|
txt2 = ""
|
|
1929
1796
|
for element in elements_list:
|
|
1930
1797
|
if explore_dlg.elements[element].text():
|
|
1931
|
-
txt2 += (
|
|
1932
|
-
f"<b>{explore_dlg.elements[element].text()}</b> in {element}<br>"
|
|
1933
|
-
)
|
|
1798
|
+
txt2 += f"<b>{explore_dlg.elements[element].text()}</b> in {element}<br>"
|
|
1934
1799
|
if txt2:
|
|
1935
1800
|
txt += " for<br>"
|
|
1936
1801
|
self.results_dialog.lb.setText(txt + txt2)
|
|
1937
1802
|
self.results_dialog.tw.setColumnCount(2)
|
|
1938
1803
|
self.results_dialog.tw.setRowCount(len(results))
|
|
1939
|
-
self.results_dialog.tw.setHorizontalHeaderLabels(
|
|
1940
|
-
["Observation id", "row index"]
|
|
1941
|
-
)
|
|
1804
|
+
self.results_dialog.tw.setHorizontalHeaderLabels(["Observation id", "row index"])
|
|
1942
1805
|
|
|
1943
1806
|
for row, result in enumerate(results):
|
|
1944
1807
|
for i in range(0, 2):
|
|
1945
1808
|
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
|
-
)
|
|
1809
|
+
self.results_dialog.tw.item(row, i).setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
|
1949
1810
|
|
|
1950
1811
|
self.results_dialog.show()
|
|
1951
1812
|
|
|
1952
1813
|
else:
|
|
1953
1814
|
QMessageBox.information(self, cfg.programName, "No events found")
|
|
1815
|
+
|
|
1816
|
+
|
|
1817
|
+
def project2dataframe(pj: dict, observations_list: list = []) -> Tuple[str, pd.DataFrame]:
|
|
1818
|
+
"""
|
|
1819
|
+
returns a pandas dataframe containing observations data
|
|
1820
|
+
"""
|
|
1821
|
+
# print(pj.keys())
|
|
1822
|
+
|
|
1823
|
+
# print(pj["independent_variables"])
|
|
1824
|
+
|
|
1825
|
+
# indep_var = [pj["independent_variables"][idx]["label"] for idx in pj["independent_variables"]]
|
|
1826
|
+
|
|
1827
|
+
indep_variables = dict(
|
|
1828
|
+
[(pj[cfg.INDEPENDENT_VARIABLES][idx]["label"], pj[cfg.INDEPENDENT_VARIABLES][idx]["type"]) for idx in pj[cfg.INDEPENDENT_VARIABLES]]
|
|
1829
|
+
)
|
|
1830
|
+
|
|
1831
|
+
# print()
|
|
1832
|
+
# print(f"{indep_variables=}")
|
|
1833
|
+
|
|
1834
|
+
# n_max_set_modifiers = max([len(pj["behaviors_conf"][behavior_id]["modifiers"]) for behavior_id in pj["behaviors_conf"]])
|
|
1835
|
+
|
|
1836
|
+
# behavioral_categories
|
|
1837
|
+
behavioral_category = dict(
|
|
1838
|
+
[(pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE], pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CATEGORY]) for x in pj[cfg.ETHOGRAM]]
|
|
1839
|
+
)
|
|
1840
|
+
|
|
1841
|
+
# print(f"{pj["behaviors_conf"]=}")
|
|
1842
|
+
|
|
1843
|
+
# check all modifiers
|
|
1844
|
+
all_modifier_sets: list = []
|
|
1845
|
+
for behavior_id in pj[cfg.ETHOGRAM]:
|
|
1846
|
+
modifier_names: list = []
|
|
1847
|
+
set_count = 0
|
|
1848
|
+
if pj[cfg.ETHOGRAM][behavior_id][cfg.MODIFIERS] == "":
|
|
1849
|
+
continue
|
|
1850
|
+
for modifier in pj[cfg.ETHOGRAM][behavior_id][cfg.MODIFIERS].values():
|
|
1851
|
+
if modifier["name"]:
|
|
1852
|
+
modifier_names.append((pj[cfg.ETHOGRAM][behavior_id][cfg.BEHAVIOR_CODE], modifier["name"]))
|
|
1853
|
+
else:
|
|
1854
|
+
set_count += 1
|
|
1855
|
+
modifier_names.append((pj[cfg.ETHOGRAM][behavior_id][cfg.BEHAVIOR_CODE], f"set #{set_count}"))
|
|
1856
|
+
|
|
1857
|
+
# print(modifier_names)
|
|
1858
|
+
if modifier_names:
|
|
1859
|
+
all_modifier_sets.extend(modifier_names)
|
|
1860
|
+
|
|
1861
|
+
# print()
|
|
1862
|
+
# print(f"{all_modifier_sets=}")
|
|
1863
|
+
|
|
1864
|
+
# create df
|
|
1865
|
+
|
|
1866
|
+
data = {
|
|
1867
|
+
"Observation id": [],
|
|
1868
|
+
"Observation date": [],
|
|
1869
|
+
"Description": [],
|
|
1870
|
+
"Observation type": [],
|
|
1871
|
+
"Observation interval start": [],
|
|
1872
|
+
"Observation interval stop": [],
|
|
1873
|
+
# "Source": [],
|
|
1874
|
+
# "Time offset (s)": [],
|
|
1875
|
+
# "Coding duration": [],
|
|
1876
|
+
# "Media duration (s)": [],
|
|
1877
|
+
# "FPS (frame/s)": [],
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
for indep_var in indep_variables:
|
|
1881
|
+
data[f"independent variable '{indep_var}'"] = []
|
|
1882
|
+
|
|
1883
|
+
data = data | {
|
|
1884
|
+
"Subject": [],
|
|
1885
|
+
"Observation duration by subject by observation": [],
|
|
1886
|
+
"Behavior": [],
|
|
1887
|
+
"Behavioral category": [],
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
for modifier_set in all_modifier_sets:
|
|
1891
|
+
data[modifier_set] = []
|
|
1892
|
+
|
|
1893
|
+
data = data | {
|
|
1894
|
+
"Behavior type": [],
|
|
1895
|
+
"Start (s)": [],
|
|
1896
|
+
"Stop (s)": [],
|
|
1897
|
+
"Duration (s)": [],
|
|
1898
|
+
# "Media file name": [],
|
|
1899
|
+
# "Image index start": [],
|
|
1900
|
+
# "Image index stop": [],
|
|
1901
|
+
# "Image file path start": [],
|
|
1902
|
+
# "Image file path stop": [],
|
|
1903
|
+
"Comment start": [],
|
|
1904
|
+
"Comment stop": [],
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
#
|
|
1908
|
+
|
|
1909
|
+
type_ = {
|
|
1910
|
+
"Observation id": "string",
|
|
1911
|
+
"Observation date": "string",
|
|
1912
|
+
"Description": "string",
|
|
1913
|
+
"Observation type": "string",
|
|
1914
|
+
"Observation interval start": "float64",
|
|
1915
|
+
"Observation interval stop": "float64",
|
|
1916
|
+
# "Source": "string",
|
|
1917
|
+
# "Time offset (s)": "string",
|
|
1918
|
+
# "Coding duration": "float64",
|
|
1919
|
+
# "Media duration (s)": "string",
|
|
1920
|
+
# "FPS (frame/s)": "float64",
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
# TODO: set correct type in base of the var type
|
|
1924
|
+
for indep_var in indep_variables:
|
|
1925
|
+
type_[f"independent variable '{indep_var}'"] = "float64" if indep_variables[indep_var] == cfg.NUMERIC else "string"
|
|
1926
|
+
|
|
1927
|
+
type_ = type_ | {
|
|
1928
|
+
"Subject": "string",
|
|
1929
|
+
"Observation duration by subject by observation": "float64",
|
|
1930
|
+
"Behavior": "string",
|
|
1931
|
+
"Behavioral category": "string",
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
for modifer_set in all_modifier_sets:
|
|
1935
|
+
type_[modifer_set] = "string"
|
|
1936
|
+
|
|
1937
|
+
type_ = type_ | {
|
|
1938
|
+
"Behavior type": "string",
|
|
1939
|
+
"Start (s)": "float64",
|
|
1940
|
+
"Stop (s)": "float64",
|
|
1941
|
+
"Duration (s)": "float64",
|
|
1942
|
+
# "Media file name": "string",
|
|
1943
|
+
# "Image index start": "float64",
|
|
1944
|
+
# "Image index stop": "float64",
|
|
1945
|
+
# "Image file path start": "string",
|
|
1946
|
+
# "Image file path stop": "string",
|
|
1947
|
+
"Comment start": "string",
|
|
1948
|
+
"Comment stop": "string",
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
state_behaviors = util.state_behavior_codes(pj[cfg.ETHOGRAM])
|
|
1952
|
+
|
|
1953
|
+
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
1954
|
+
if observations_list and obs_id not in observations_list:
|
|
1955
|
+
continue
|
|
1956
|
+
# print(obs_id)
|
|
1957
|
+
stop_event_idx = set()
|
|
1958
|
+
for idx_event, event in enumerate(pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]):
|
|
1959
|
+
if idx_event in stop_event_idx:
|
|
1960
|
+
continue
|
|
1961
|
+
data["Observation id"].append(obs_id)
|
|
1962
|
+
data["Observation date"].append(pj[cfg.OBSERVATIONS][obs_id]["date"])
|
|
1963
|
+
data["Description"].append(" ".join(pj[cfg.OBSERVATIONS][obs_id]["description"].splitlines()))
|
|
1964
|
+
data["Observation type"].append(pj[cfg.OBSERVATIONS][obs_id]["type"])
|
|
1965
|
+
|
|
1966
|
+
data["Observation interval start"].append(pj[cfg.OBSERVATIONS][obs_id].get(cfg.OBSERVATION_TIME_INTERVAL, [None, None])[0])
|
|
1967
|
+
data["Observation interval stop"].append(pj[cfg.OBSERVATIONS][obs_id].get(cfg.OBSERVATION_TIME_INTERVAL, [None, None])[1])
|
|
1968
|
+
|
|
1969
|
+
# data["Source"].append("")
|
|
1970
|
+
# data["Time offset (s)"].append(pj["observations"][obs_id]["time offset"])
|
|
1971
|
+
# data["Coding duration"].append("")
|
|
1972
|
+
# data["Media duration (s)"].append("")
|
|
1973
|
+
# data["FPS (frame/s)"].append("")
|
|
1974
|
+
|
|
1975
|
+
for indep_var in indep_variables:
|
|
1976
|
+
data[f"independent variable '{indep_var}'"].append(
|
|
1977
|
+
pj[cfg.OBSERVATIONS][obs_id][cfg.INDEPENDENT_VARIABLES].get(indep_var, None)
|
|
1978
|
+
)
|
|
1979
|
+
|
|
1980
|
+
data["Subject"].append(event[cfg.EVENT_SUBJECT_FIELD_IDX] if event[cfg.EVENT_SUBJECT_FIELD_IDX] != "" else cfg.NO_FOCAL_SUBJECT)
|
|
1981
|
+
data["Observation duration by subject by observation"].append(-1)
|
|
1982
|
+
data["Behavior"].append(event[2])
|
|
1983
|
+
data["Behavioral category"].append(behavioral_category[event[2]])
|
|
1984
|
+
|
|
1985
|
+
count_set = 0
|
|
1986
|
+
for modifier_set in all_modifier_sets:
|
|
1987
|
+
if event[2] == modifier_set[0]:
|
|
1988
|
+
try:
|
|
1989
|
+
data[modifier_set].append(event[3].split("|")[count_set])
|
|
1990
|
+
except Exception:
|
|
1991
|
+
return f"Modifier error for {event[2]} in observation {obs_id}", pd.DataFrame()
|
|
1992
|
+
count_set += 1
|
|
1993
|
+
else:
|
|
1994
|
+
data[modifier_set].append(np.nan)
|
|
1995
|
+
|
|
1996
|
+
data["Behavior type"].append(cfg.STATE_EVENT if event[2] in state_behaviors else cfg.POINT_EVENT)
|
|
1997
|
+
data["Start (s)"].append(float(event[0]))
|
|
1998
|
+
if event[2] in state_behaviors:
|
|
1999
|
+
# search stop
|
|
2000
|
+
# print(f"==> {idx_event=} {event[1:4]=}")
|
|
2001
|
+
for idx_event2, event2 in enumerate(pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS][idx_event + 1 :], start=idx_event + 1):
|
|
2002
|
+
# print(f"{idx_event2=} {event2[1:4]=}")
|
|
2003
|
+
if event2[1:4] == event[1:4]:
|
|
2004
|
+
# print("found")
|
|
2005
|
+
stop_event_idx.add(idx_event2)
|
|
2006
|
+
data["Stop (s)"].append(float(event2[0]))
|
|
2007
|
+
data["Duration (s)"].append(float(event2[0] - event[0]))
|
|
2008
|
+
data["Comment start"].append(event[4])
|
|
2009
|
+
data["Comment stop"].append(event2[4])
|
|
2010
|
+
break
|
|
2011
|
+
else:
|
|
2012
|
+
return f"Some events are not paired in {obs_id}", pd.DataFrame()
|
|
2013
|
+
|
|
2014
|
+
else: # point
|
|
2015
|
+
data["Stop (s)"].append(float(event[0]))
|
|
2016
|
+
data["Duration (s)"].append(np.nan)
|
|
2017
|
+
data["Comment start"].append(event[4])
|
|
2018
|
+
data["Comment stop"].append(event[4])
|
|
2019
|
+
|
|
2020
|
+
# Set the display option to show all rows and columns
|
|
2021
|
+
pd.set_option("display.max_rows", None)
|
|
2022
|
+
pd.set_option("display.max_columns", None)
|
|
2023
|
+
|
|
2024
|
+
pd.DataFrame(data).info()
|
|
2025
|
+
|
|
2026
|
+
return "", pd.DataFrame(data)
|