boris-behav-obs 9.7.7__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 +26 -0
- boris/__main__.py +25 -0
- boris/about.py +143 -0
- boris/add_modifier.py +635 -0
- boris/add_modifier_ui.py +303 -0
- boris/advanced_event_filtering.py +455 -0
- 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 +1110 -0
- boris/behavior_binary_table.py +305 -0
- boris/behaviors_coding_map.py +239 -0
- boris/boris_cli.py +340 -0
- boris/cmd_arguments.py +49 -0
- boris/coding_pad.py +280 -0
- boris/config.py +785 -0
- boris/config_file.py +356 -0
- boris/connections.py +409 -0
- boris/converters.py +333 -0
- boris/converters_ui.py +225 -0
- boris/cooccurence.py +250 -0
- boris/core.py +5901 -0
- boris/core_qrc.py +15958 -0
- boris/core_ui.py +1107 -0
- boris/db_functions.py +324 -0
- boris/dev.py +134 -0
- boris/dialog.py +1108 -0
- boris/duration_widget.py +238 -0
- boris/edit_event.py +245 -0
- boris/edit_event_ui.py +233 -0
- boris/event_operations.py +1040 -0
- boris/events_cursor.py +61 -0
- boris/events_snapshots.py +596 -0
- boris/exclusion_matrix.py +141 -0
- boris/export_events.py +1006 -0
- boris/export_observation.py +1203 -0
- boris/external_processes.py +332 -0
- boris/geometric_measurement.py +941 -0
- boris/gui_utilities.py +135 -0
- boris/image_overlay.py +72 -0
- boris/import_observations.py +242 -0
- boris/ipc_mpv.py +325 -0
- boris/irr.py +634 -0
- boris/latency.py +244 -0
- boris/measurement_widget.py +161 -0
- boris/media_file.py +115 -0
- boris/menu_options.py +213 -0
- boris/modifier_coding_map_creator.py +1013 -0
- boris/modifiers_coding_map.py +157 -0
- boris/mpv.py +2016 -0
- boris/mpv2.py +2193 -0
- boris/observation.py +1453 -0
- boris/observation_operations.py +2538 -0
- boris/observation_ui.py +679 -0
- boris/observations_list.py +337 -0
- boris/otx_parser.py +442 -0
- boris/param_panel.py +201 -0
- boris/param_panel_ui.py +305 -0
- boris/player_dock_widget.py +198 -0
- boris/plot_data_module.py +536 -0
- boris/plot_events.py +634 -0
- boris/plot_events_rt.py +237 -0
- boris/plot_spectrogram_rt.py +316 -0
- boris/plot_waveform_rt.py +230 -0
- boris/plugins.py +431 -0
- boris/portion/__init__.py +31 -0
- boris/portion/const.py +95 -0
- boris/portion/dict.py +365 -0
- boris/portion/func.py +52 -0
- boris/portion/interval.py +581 -0
- boris/portion/io.py +181 -0
- boris/preferences.py +510 -0
- boris/preferences_ui.py +770 -0
- boris/project.py +2007 -0
- boris/project_functions.py +2041 -0
- boris/project_import_export.py +1096 -0
- boris/project_ui.py +794 -0
- boris/qrc_boris.py +10389 -0
- boris/qrc_boris5.py +2579 -0
- boris/select_modifiers.py +312 -0
- boris/select_observations.py +210 -0
- boris/select_subj_behav.py +286 -0
- boris/state_events.py +197 -0
- boris/subjects_pad.py +106 -0
- boris/synthetic_time_budget.py +290 -0
- boris/time_budget_functions.py +1136 -0
- boris/time_budget_widget.py +1039 -0
- boris/transitions.py +365 -0
- boris/utilities.py +1810 -0
- boris/version.py +24 -0
- boris/video_equalizer.py +159 -0
- boris/video_equalizer_ui.py +248 -0
- boris/video_operations.py +310 -0
- boris/view_df.py +104 -0
- boris/view_df_ui.py +75 -0
- boris/write_event.py +538 -0
- boris_behav_obs-9.7.7.dist-info/METADATA +139 -0
- boris_behav_obs-9.7.7.dist-info/RECORD +109 -0
- boris_behav_obs-9.7.7.dist-info/WHEEL +5 -0
- boris_behav_obs-9.7.7.dist-info/entry_points.txt +2 -0
- boris_behav_obs-9.7.7.dist-info/licenses/LICENSE.TXT +674 -0
- boris_behav_obs-9.7.7.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1203 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BORIS
|
|
3
|
+
Behavioral Observation Research Interactive Software
|
|
4
|
+
Copyright 2012-2025 Olivier Friard
|
|
5
|
+
|
|
6
|
+
This program is free software; you can redistribute it and/or modify
|
|
7
|
+
it under the terms of the GNU General Public License as published by
|
|
8
|
+
the Free Software Foundation; either version 2 of the License, or
|
|
9
|
+
(at your option) any later version.
|
|
10
|
+
|
|
11
|
+
This program is distributed in the hope that it will be useful,
|
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
GNU General Public License for more details.
|
|
15
|
+
|
|
16
|
+
You should have received a copy of the GNU General Public License
|
|
17
|
+
along with this program; if not, write to the Free Software
|
|
18
|
+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
19
|
+
MA 02110-1301, USA.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from decimal import Decimal as dec
|
|
23
|
+
import tablib
|
|
24
|
+
import logging
|
|
25
|
+
import os
|
|
26
|
+
import sys
|
|
27
|
+
import datetime as dt
|
|
28
|
+
import math
|
|
29
|
+
import pathlib
|
|
30
|
+
from io import StringIO
|
|
31
|
+
import pandas as pd
|
|
32
|
+
from typing import Tuple
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
import pyreadr
|
|
36
|
+
|
|
37
|
+
flag_pyreadr_loaded = True
|
|
38
|
+
except ModuleNotFoundError:
|
|
39
|
+
flag_pyreadr_loaded = False
|
|
40
|
+
|
|
41
|
+
from . import dialog
|
|
42
|
+
from . import config as cfg
|
|
43
|
+
from . import utilities as util
|
|
44
|
+
from . import project_functions
|
|
45
|
+
from . import observation_operations
|
|
46
|
+
from . import db_functions
|
|
47
|
+
from . import event_operations
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def export_events_jwatcher(
|
|
51
|
+
parameters: dict, obsId: str, observation: list, ethogram: dict, file_name: str, output_format: str
|
|
52
|
+
) -> Tuple[bool, str]:
|
|
53
|
+
"""
|
|
54
|
+
export events jwatcher .dat format
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
parameters (dict): subjects, behaviors
|
|
58
|
+
obsId (str): observation id
|
|
59
|
+
observation (dict): observation
|
|
60
|
+
ethogram (dict): ethogram of project
|
|
61
|
+
file_name (str): file name for exporting events
|
|
62
|
+
output_format (str): Not used for compatibility with export_events function
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
bool: result: True if OK else False
|
|
66
|
+
str: error message
|
|
67
|
+
"""
|
|
68
|
+
for subject in parameters[cfg.SELECTED_SUBJECTS]:
|
|
69
|
+
# select events for current subject
|
|
70
|
+
events = []
|
|
71
|
+
for event in observation[cfg.EVENTS]:
|
|
72
|
+
if event[cfg.EVENT_SUBJECT_FIELD_IDX] == subject or (
|
|
73
|
+
subject == cfg.NO_FOCAL_SUBJECT and event[cfg.EVENT_SUBJECT_FIELD_IDX] == ""
|
|
74
|
+
):
|
|
75
|
+
events.append(event)
|
|
76
|
+
|
|
77
|
+
if not events:
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
total_length = 0 # in seconds
|
|
81
|
+
if observation[cfg.EVENTS]:
|
|
82
|
+
total_length = observation[cfg.EVENTS][-1][0] - observation[cfg.EVENTS][0][0] # last event time - first event time
|
|
83
|
+
|
|
84
|
+
file_name_subject = str(pathlib.Path(file_name).parent / pathlib.Path(file_name).stem) + "_" + subject + ".dat"
|
|
85
|
+
|
|
86
|
+
rows = ["FirstLineOfData"] # to be completed
|
|
87
|
+
rows.append("#-----------------------------------------------------------")
|
|
88
|
+
rows.append(f"# Name: {pathlib.Path(file_name_subject).name}")
|
|
89
|
+
rows.append("# Format: Focal Data File 1.0")
|
|
90
|
+
rows.append(f"# Updated: {dt.datetime.now().isoformat()}")
|
|
91
|
+
rows.append("#-----------------------------------------------------------")
|
|
92
|
+
rows.append("")
|
|
93
|
+
rows.append(f"FocalMasterFile={pathlib.Path(file_name_subject).with_suffix('.fmf')}")
|
|
94
|
+
rows.append("")
|
|
95
|
+
|
|
96
|
+
rows.append(f"# Observation started: {observation['date']}")
|
|
97
|
+
try:
|
|
98
|
+
start_time = dt.datetime.strptime(observation["date"], "%Y-%m-%dT%H:%M:%S")
|
|
99
|
+
except ValueError:
|
|
100
|
+
start_time = dt.datetime(1970, 1, 1, 0, 0)
|
|
101
|
+
start_time_epoch = int((start_time - dt.datetime(1970, 1, 1, 0, 0)).total_seconds() * 1000)
|
|
102
|
+
rows.append(f"StartTime={start_time_epoch}")
|
|
103
|
+
|
|
104
|
+
stop_time = (start_time + dt.timedelta(seconds=float(total_length))).isoformat()
|
|
105
|
+
stop_time_epoch = int(start_time_epoch + float(total_length) * 1000)
|
|
106
|
+
|
|
107
|
+
rows.append(f"# Observation stopped: {stop_time}")
|
|
108
|
+
rows.append(f"StopTime={stop_time_epoch}")
|
|
109
|
+
|
|
110
|
+
rows.extend([""] * 3)
|
|
111
|
+
rows.append("#BEGIN DATA")
|
|
112
|
+
rows[0] = f"FirstLineOfData={len(rows) + 1}"
|
|
113
|
+
|
|
114
|
+
all_observed_behaviors = []
|
|
115
|
+
mem_number_of_state_events = {}
|
|
116
|
+
for event in events:
|
|
117
|
+
behav_code = event[cfg.EVENT_BEHAVIOR_FIELD_IDX]
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
behavior_key = [ethogram[k][cfg.BEHAVIOR_KEY] for k in ethogram if ethogram[k][cfg.BEHAVIOR_CODE] == behav_code][0]
|
|
121
|
+
except Exception:
|
|
122
|
+
# coded behavior not defined in ethogram
|
|
123
|
+
continue
|
|
124
|
+
if [ethogram[k][cfg.TYPE] for k in ethogram if ethogram[k][cfg.BEHAVIOR_CODE] == behav_code] in [
|
|
125
|
+
[cfg.STATE_EVENT],
|
|
126
|
+
[cfg.STATE_EVENT_WITH_CODING_MAP],
|
|
127
|
+
]:
|
|
128
|
+
if behav_code in mem_number_of_state_events:
|
|
129
|
+
mem_number_of_state_events[behav_code] += 1
|
|
130
|
+
else:
|
|
131
|
+
mem_number_of_state_events[behav_code] = 1
|
|
132
|
+
# skip the STOP event in case of STATE
|
|
133
|
+
if mem_number_of_state_events[behav_code] % 2 == 0:
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
rows.append(f"{int(event[cfg.EVENT_TIME_FIELD_IDX] * 1000)}, {behavior_key}")
|
|
137
|
+
if (event[cfg.EVENT_BEHAVIOR_FIELD_IDX], behavior_key) not in all_observed_behaviors:
|
|
138
|
+
all_observed_behaviors.append((event[cfg.EVENT_BEHAVIOR_FIELD_IDX], behavior_key))
|
|
139
|
+
|
|
140
|
+
rows.append(f"{int(events[-1][0] * 1000)}, EOF\n")
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
with open(file_name_subject, "w") as f_out:
|
|
144
|
+
f_out.write("\n".join(rows))
|
|
145
|
+
except Exception:
|
|
146
|
+
return False, f"File DAT not created for subject {subject}: {sys.exc_info()[1]}"
|
|
147
|
+
|
|
148
|
+
# create fmf file
|
|
149
|
+
fmf_file_path = pathlib.Path(file_name_subject).with_suffix(".fmf")
|
|
150
|
+
fmf_creation_answer = ""
|
|
151
|
+
if fmf_file_path.exists():
|
|
152
|
+
fmf_creation_answer = dialog.MessageDialog(
|
|
153
|
+
cfg.programName,
|
|
154
|
+
(f"The {fmf_file_path} file already exists.<br>What do you want to do?"),
|
|
155
|
+
[cfg.OVERWRITE, "Skip file creation", cfg.CANCEL],
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
if fmf_creation_answer == cfg.CANCEL:
|
|
159
|
+
return True, ""
|
|
160
|
+
|
|
161
|
+
rows = []
|
|
162
|
+
rows.append("#-----------------------------------------------------------")
|
|
163
|
+
rows.append(f"# Name: {pathlib.Path(file_name_subject).with_suffix('.fmf').name}")
|
|
164
|
+
rows.append("# Format: Focal Master File 1.0")
|
|
165
|
+
rows.append(f"# Updated: {dt.datetime.now().isoformat()}")
|
|
166
|
+
rows.append("#-----------------------------------------------------------")
|
|
167
|
+
for behav, key in all_observed_behaviors:
|
|
168
|
+
rows.append(f"Behaviour.name.{key}={behav}")
|
|
169
|
+
behav_description = [ethogram[k][cfg.DESCRIPTION] for k in ethogram if ethogram[k][cfg.BEHAVIOR_CODE] == behav][0]
|
|
170
|
+
rows.append(f"Behaviour.description.{key}={behav_description}")
|
|
171
|
+
|
|
172
|
+
rows.append(f"DurationMilliseconds={int(float(total_length) * 1000)}")
|
|
173
|
+
rows.append("CountUp=false")
|
|
174
|
+
rows.append("Question.1=")
|
|
175
|
+
rows.append("Question.2=")
|
|
176
|
+
rows.append("Question.3=")
|
|
177
|
+
rows.append("Question.4=")
|
|
178
|
+
rows.append("Question.5=")
|
|
179
|
+
rows.append("Question.6=")
|
|
180
|
+
rows.append("Notes=")
|
|
181
|
+
rows.append("Supplementary=\n")
|
|
182
|
+
|
|
183
|
+
if fmf_creation_answer == cfg.OVERWRITE or fmf_creation_answer == "":
|
|
184
|
+
try:
|
|
185
|
+
with open(fmf_file_path, "w") as f_out:
|
|
186
|
+
f_out.write("\n".join(rows))
|
|
187
|
+
except Exception:
|
|
188
|
+
return False, f"File FMF not created: {sys.exc_info()[1]}"
|
|
189
|
+
|
|
190
|
+
# create FAF file
|
|
191
|
+
faf_file_path = pathlib.Path(file_name_subject).with_suffix(".faf")
|
|
192
|
+
faf_creation_answer = ""
|
|
193
|
+
if faf_file_path.exists():
|
|
194
|
+
faf_creation_answer = dialog.MessageDialog(
|
|
195
|
+
cfg.programName,
|
|
196
|
+
(f"The {faf_file_path} file already exists.<br>What do you want to do?"),
|
|
197
|
+
[cfg.OVERWRITE, "Skip file creation", cfg.CANCEL],
|
|
198
|
+
)
|
|
199
|
+
if faf_creation_answer == cfg.CANCEL:
|
|
200
|
+
return True, ""
|
|
201
|
+
|
|
202
|
+
rows = []
|
|
203
|
+
rows.append("#-----------------------------------------------------------")
|
|
204
|
+
rows.append("# Name: {}".format(pathlib.Path(file_name_subject).with_suffix(".faf").name))
|
|
205
|
+
rows.append("# Format: Focal Analysis Master File 1.0")
|
|
206
|
+
rows.append("# Updated: {}".format(dt.datetime.now().isoformat()))
|
|
207
|
+
rows.append("#-----------------------------------------------------------")
|
|
208
|
+
rows.append("FocalMasterFile={}".format(str(pathlib.Path(file_name_subject).with_suffix(".fmf"))))
|
|
209
|
+
rows.append("")
|
|
210
|
+
rows.append("TimeBinDuration=0.0")
|
|
211
|
+
rows.append("EndWithLastCompleteBin=true")
|
|
212
|
+
rows.append("")
|
|
213
|
+
rows.append("ScoreFromBeginning=true")
|
|
214
|
+
rows.append("ScoreFromBehavior=false")
|
|
215
|
+
rows.append("ScoreFromFirstBehavior=false")
|
|
216
|
+
rows.append("ScoreFromOffset=false")
|
|
217
|
+
rows.append("")
|
|
218
|
+
rows.append("Offset=0.0")
|
|
219
|
+
rows.append("BehaviorToScoreFrom=")
|
|
220
|
+
rows.append("")
|
|
221
|
+
rows.append("OutOfSightCode=")
|
|
222
|
+
rows.append("")
|
|
223
|
+
rows.append("Report.StateNaturalInterval.Occurrence=false")
|
|
224
|
+
rows.append("Report.StateNaturalInterval.TotalTime=false")
|
|
225
|
+
rows.append("Report.StateNaturalInterval.Average=false")
|
|
226
|
+
rows.append("Report.StateNaturalInterval.StandardDeviation=false")
|
|
227
|
+
rows.append("Report.StateNaturalInterval.ProportionOfTime=false")
|
|
228
|
+
rows.append("Report.StateNaturalInterval.ProportionOfTimeInSight=false")
|
|
229
|
+
rows.append("Report.StateNaturalInterval.ConditionalProportionOfTime=false")
|
|
230
|
+
rows.append("")
|
|
231
|
+
rows.append("Report.StateNaturalDuration.Occurrence=false")
|
|
232
|
+
rows.append("Report.StateNaturalDuration.TotalTime=false")
|
|
233
|
+
rows.append("Report.StateNaturalDuration.Average=false")
|
|
234
|
+
rows.append("Report.StateNaturalDuration.StandardDeviation=false")
|
|
235
|
+
rows.append("Report.StateNaturalDuration.ProportionOfTime=false")
|
|
236
|
+
rows.append("Report.StateNaturalDuration.ProportionOfTimeInSight=false")
|
|
237
|
+
rows.append("Report.StateNaturalDuration.ConditionalProportionOfTime=false")
|
|
238
|
+
rows.append("")
|
|
239
|
+
rows.append("Report.StateAllInterval.Occurrence=false")
|
|
240
|
+
rows.append("Report.StateAllInterval.TotalTime=false")
|
|
241
|
+
rows.append("Report.StateAllInterval.Average=false")
|
|
242
|
+
rows.append("Report.StateAllInterval.StandardDeviation=false")
|
|
243
|
+
rows.append("Report.StateAllInterval.ProportionOfTime=false")
|
|
244
|
+
rows.append("Report.StateAllInterval.ProportionOfTimeInSight=false")
|
|
245
|
+
rows.append("Report.StateAllInterval.ConditionalProportionOfTime=false")
|
|
246
|
+
rows.append("")
|
|
247
|
+
rows.append("Report.StateAllDuration.Occurrence=true")
|
|
248
|
+
rows.append("Report.StateAllDuration.TotalTime=true")
|
|
249
|
+
rows.append("Report.StateAllDuration.Average=true")
|
|
250
|
+
rows.append("Report.StateAllDuration.StandardDeviation=false")
|
|
251
|
+
rows.append("Report.StateAllDuration.ProportionOfTime=false")
|
|
252
|
+
rows.append("Report.StateAllDuration.ProportionOfTimeInSight=true")
|
|
253
|
+
rows.append("Report.StateAllDuration.ConditionalProportionOfTime=false")
|
|
254
|
+
rows.append("")
|
|
255
|
+
rows.append("Report.EventNaturalInterval.EventCount=false")
|
|
256
|
+
rows.append("Report.EventNaturalInterval.Occurrence=false")
|
|
257
|
+
rows.append("Report.EventNaturalInterval.Average=false")
|
|
258
|
+
rows.append("Report.EventNaturalInterval.StandardDeviation=false")
|
|
259
|
+
rows.append("Report.EventNaturalInterval.ConditionalNatEventCount=false")
|
|
260
|
+
rows.append("Report.EventNaturalInterval.ConditionalNatRate=false")
|
|
261
|
+
rows.append("Report.EventNaturalInterval.ConditionalNatIntervalOccurance=false")
|
|
262
|
+
rows.append("Report.EventNaturalInterval.ConditionalNatIntervalAverage=false")
|
|
263
|
+
rows.append("Report.EventNaturalInterval.ConditionalNatIntervalStandardDeviation=false")
|
|
264
|
+
rows.append("Report.EventNaturalInterval.ConditionalAllEventCount=false")
|
|
265
|
+
rows.append("Report.EventNaturalInterval.ConditionalAllRate=false")
|
|
266
|
+
rows.append("Report.EventNaturalInterval.ConditionalAllIntervalOccurance=false")
|
|
267
|
+
rows.append("Report.EventNaturalInterval.ConditionalAllIntervalAverage=false")
|
|
268
|
+
rows.append("Report.EventNaturalInterval.ConditionalAllIntervalStandardDeviation=false")
|
|
269
|
+
rows.append("")
|
|
270
|
+
rows.append("AllCodesMutuallyExclusive=true")
|
|
271
|
+
rows.append("")
|
|
272
|
+
|
|
273
|
+
for behav, key in all_observed_behaviors:
|
|
274
|
+
rows.append(f"Behavior.isModified.{key}=false")
|
|
275
|
+
rows.append(f"Behavior.isSubtracted.{key}=false")
|
|
276
|
+
rows.append(f"Behavior.isIgnored.{key}=false")
|
|
277
|
+
rows.append(f"Behavior.isEventAnalyzed.{key}=false")
|
|
278
|
+
rows.append(f"Behavior.switchesOff.{key}=")
|
|
279
|
+
rows.append("")
|
|
280
|
+
|
|
281
|
+
if faf_creation_answer == "" or faf_creation_answer == cfg.OVERWRITE:
|
|
282
|
+
try:
|
|
283
|
+
with open(pathlib.Path(file_name_subject).with_suffix(".faf"), "w") as f_out:
|
|
284
|
+
f_out.write("\n".join(rows))
|
|
285
|
+
except Exception:
|
|
286
|
+
return False, f"File FAF not created: {sys.exc_info()[1]}"
|
|
287
|
+
|
|
288
|
+
return True, ""
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def export_tabular_events(
|
|
292
|
+
pj: dict, parameters: dict, obs_id: str, observation: dict, ethogram: dict, file_name: str, output_format: str
|
|
293
|
+
) -> Tuple[bool, str]:
|
|
294
|
+
"""
|
|
295
|
+
export events for one observation (obs_id)
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
parameters (dict): subjects, behaviors
|
|
299
|
+
obs_id (str): observation id
|
|
300
|
+
observation (dict): observation
|
|
301
|
+
ethogram (dict): ethogram of project
|
|
302
|
+
file_name (str): file name for exporting events
|
|
303
|
+
output_format (str): output for exporting events
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
bool: result: True if OK else False
|
|
307
|
+
str: error message
|
|
308
|
+
"""
|
|
309
|
+
|
|
310
|
+
logging.debug(f"function: export tabular events for {obs_id}")
|
|
311
|
+
logging.debug(f"parameters: {parameters}")
|
|
312
|
+
|
|
313
|
+
interval = parameters["time"]
|
|
314
|
+
|
|
315
|
+
start_coding, end_coding, coding_duration = observation_operations.coding_time(pj[cfg.OBSERVATIONS], [obs_id])
|
|
316
|
+
start_interval, end_interval = observation_operations.time_intervals_range(pj[cfg.OBSERVATIONS], [obs_id])
|
|
317
|
+
|
|
318
|
+
if interval == cfg.TIME_EVENTS:
|
|
319
|
+
min_time = start_coding
|
|
320
|
+
max_time = end_coding
|
|
321
|
+
|
|
322
|
+
if interval == cfg.TIME_FULL_OBS:
|
|
323
|
+
if observation[cfg.TYPE] == cfg.MEDIA:
|
|
324
|
+
max_media_duration, _ = observation_operations.media_duration(pj[cfg.OBSERVATIONS], [obs_id])
|
|
325
|
+
min_time = dec("0")
|
|
326
|
+
max_time = max_media_duration
|
|
327
|
+
coding_duration = max_media_duration
|
|
328
|
+
|
|
329
|
+
if observation[cfg.TYPE] in (cfg.LIVE, cfg.IMAGES):
|
|
330
|
+
min_time = start_coding
|
|
331
|
+
max_time = end_coding
|
|
332
|
+
|
|
333
|
+
if interval == cfg.TIME_ARBITRARY_INTERVAL:
|
|
334
|
+
min_time = parameters[cfg.START_TIME]
|
|
335
|
+
max_time = parameters[cfg.END_TIME]
|
|
336
|
+
|
|
337
|
+
if interval == cfg.TIME_OBS_INTERVAL:
|
|
338
|
+
max_media_duration, _ = observation_operations.media_duration(pj[cfg.OBSERVATIONS], [obs_id])
|
|
339
|
+
min_time = start_interval
|
|
340
|
+
# Use max media duration for max time if no interval is defined (=0)
|
|
341
|
+
max_time = end_interval if end_interval != 0 else max_media_duration
|
|
342
|
+
|
|
343
|
+
logging.debug(f"min_time: {min_time} max_time: {max_time}")
|
|
344
|
+
|
|
345
|
+
events_with_status = project_functions.events_start_stop(ethogram, observation[cfg.EVENTS], observation[cfg.TYPE])
|
|
346
|
+
|
|
347
|
+
# check max number of modifiers
|
|
348
|
+
max_modifiers = 0
|
|
349
|
+
for event in events_with_status:
|
|
350
|
+
if not math.isnan(min_time) and not math.isnan(max_time): # obs not from pictures
|
|
351
|
+
if min_time <= event[cfg.EVENT_TIME_FIELD_IDX] <= max_time:
|
|
352
|
+
if event[cfg.EVENT_MODIFIER_FIELD_IDX]:
|
|
353
|
+
max_modifiers = max(max_modifiers, len(event[cfg.EVENT_MODIFIER_FIELD_IDX].split("|")))
|
|
354
|
+
else:
|
|
355
|
+
if event[cfg.EVENT_MODIFIER_FIELD_IDX]:
|
|
356
|
+
max_modifiers = max(max_modifiers, len(event[cfg.EVENT_MODIFIER_FIELD_IDX].split("|")))
|
|
357
|
+
|
|
358
|
+
# media file number
|
|
359
|
+
media_nb = util.count_media_file(observation[cfg.FILE])
|
|
360
|
+
|
|
361
|
+
rows: list = []
|
|
362
|
+
|
|
363
|
+
# fields and type
|
|
364
|
+
fields_type: dict = {
|
|
365
|
+
"Observation id": str,
|
|
366
|
+
"Observation date": dt.datetime,
|
|
367
|
+
"Description": str,
|
|
368
|
+
"Observation duration": float,
|
|
369
|
+
"Observation type": str,
|
|
370
|
+
"Source": str,
|
|
371
|
+
"Time offset (s)": str,
|
|
372
|
+
}
|
|
373
|
+
if media_nb == 1:
|
|
374
|
+
fields_type["Media duration (s)"] = float
|
|
375
|
+
fields_type["FPS"] = float
|
|
376
|
+
else:
|
|
377
|
+
fields_type["Media duration (s)"] = str
|
|
378
|
+
fields_type["FPS"] = str
|
|
379
|
+
|
|
380
|
+
# independent variables
|
|
381
|
+
if cfg.INDEPENDENT_VARIABLES in observation:
|
|
382
|
+
for variable in observation[cfg.INDEPENDENT_VARIABLES]:
|
|
383
|
+
# TODO check variable type
|
|
384
|
+
fields_type[variable] = str
|
|
385
|
+
|
|
386
|
+
fields_type.update({"Subject": str, "Behavior": str, "Behavioral category": str})
|
|
387
|
+
|
|
388
|
+
# modifiers
|
|
389
|
+
for idx in range(max_modifiers):
|
|
390
|
+
fields_type[f"Modifier #{idx + 1}"] = str
|
|
391
|
+
|
|
392
|
+
fields_type.update(
|
|
393
|
+
{
|
|
394
|
+
"Behavior type": str,
|
|
395
|
+
"Time": float,
|
|
396
|
+
"Media file name": str,
|
|
397
|
+
"Image index": float, # add image index and image file path to header
|
|
398
|
+
"Image file path": str,
|
|
399
|
+
"Comment": str,
|
|
400
|
+
}
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
# add header
|
|
404
|
+
rows.append(list(fields_type.keys()))
|
|
405
|
+
|
|
406
|
+
behavioral_category = project_functions.behavior_category(ethogram)
|
|
407
|
+
|
|
408
|
+
duration1 = [] # in seconds
|
|
409
|
+
if observation[cfg.TYPE] == cfg.MEDIA:
|
|
410
|
+
try:
|
|
411
|
+
for mediaFile in observation[cfg.FILE][cfg.PLAYER1]:
|
|
412
|
+
duration1.append(observation[cfg.MEDIA_INFO][cfg.LENGTH][mediaFile])
|
|
413
|
+
except KeyError:
|
|
414
|
+
pass
|
|
415
|
+
|
|
416
|
+
for event in events_with_status:
|
|
417
|
+
if (not math.isnan(min_time)) and not (min_time <= event[cfg.EVENT_TIME_FIELD_IDX] <= max_time):
|
|
418
|
+
continue
|
|
419
|
+
if (
|
|
420
|
+
(event[cfg.EVENT_SUBJECT_FIELD_IDX] in parameters[cfg.SELECTED_SUBJECTS])
|
|
421
|
+
or (event[cfg.EVENT_SUBJECT_FIELD_IDX] == "" and cfg.NO_FOCAL_SUBJECT in parameters[cfg.SELECTED_SUBJECTS])
|
|
422
|
+
) and (event[cfg.EVENT_BEHAVIOR_FIELD_IDX] in parameters[cfg.SELECTED_BEHAVIORS]):
|
|
423
|
+
fields: list = []
|
|
424
|
+
fields.append(obs_id)
|
|
425
|
+
fields.append(observation.get("date", "").replace("T", " "))
|
|
426
|
+
fields.append(util.eol2space(observation.get(cfg.DESCRIPTION, "")))
|
|
427
|
+
# total length
|
|
428
|
+
fields.append(coding_duration if not coding_duration.is_nan() else cfg.NA)
|
|
429
|
+
|
|
430
|
+
if observation[cfg.TYPE] == cfg.MEDIA:
|
|
431
|
+
fields.append("Media file(s)")
|
|
432
|
+
|
|
433
|
+
media_file_str, fps_str, media_durations_str = "", "", ""
|
|
434
|
+
for player in observation[cfg.FILE]:
|
|
435
|
+
media_file_lst, fps_lst, media_durations_lst = [], [], []
|
|
436
|
+
if observation[cfg.FILE][player]:
|
|
437
|
+
for media_file in observation[cfg.FILE][player]:
|
|
438
|
+
media_file_lst.append(media_file)
|
|
439
|
+
fps_lst.append(f"{observation[cfg.MEDIA_INFO][cfg.FPS].get(media_file, cfg.NA):.3f}")
|
|
440
|
+
media_durations_lst.append(f"{observation[cfg.MEDIA_INFO][cfg.LENGTH].get(media_file, cfg.NA):.3f}")
|
|
441
|
+
if player > "1":
|
|
442
|
+
media_file_str += "|"
|
|
443
|
+
fps_str += "|"
|
|
444
|
+
media_durations_str += "|"
|
|
445
|
+
media_file_str += f"player #{player}:" + ";".join(media_file_lst)
|
|
446
|
+
fps_str += ";".join(fps_lst)
|
|
447
|
+
media_durations_str += ";".join(media_durations_lst)
|
|
448
|
+
|
|
449
|
+
"""
|
|
450
|
+
# number of players
|
|
451
|
+
n_players = len([x for x in observation[cfg.FILE] if observation[cfg.FILE][x]])
|
|
452
|
+
media_file_str, fps_str = "", ""
|
|
453
|
+
for player in observation[cfg.FILE]:
|
|
454
|
+
if observation[cfg.FILE][player]:
|
|
455
|
+
if media_file_str:
|
|
456
|
+
media_file_str += " "
|
|
457
|
+
if fps_str:
|
|
458
|
+
fps_str += " "
|
|
459
|
+
if n_players > 1:
|
|
460
|
+
media_file_str += f"player #{player}: "
|
|
461
|
+
fps_str += f"player #{player}: "
|
|
462
|
+
media_list, fps_list = [], []
|
|
463
|
+
for media_file in observation[cfg.FILE][player]:
|
|
464
|
+
media_list.append(media_file)
|
|
465
|
+
fps_list.append(f"{observation[cfg.MEDIA_INFO][cfg.FPS].get(media_file, cfg.NA):.3f}")
|
|
466
|
+
media_file_str += ";".join(media_list)
|
|
467
|
+
fps_str += ";".join(fps_list)
|
|
468
|
+
"""
|
|
469
|
+
|
|
470
|
+
fields.append(media_file_str)
|
|
471
|
+
|
|
472
|
+
elif observation[cfg.TYPE] == cfg.LIVE:
|
|
473
|
+
fields.append("Live observation")
|
|
474
|
+
fields.append(cfg.NA)
|
|
475
|
+
media_durations_str = cfg.NA
|
|
476
|
+
fps_str = cfg.NA
|
|
477
|
+
|
|
478
|
+
elif observation[cfg.TYPE] == cfg.IMAGES:
|
|
479
|
+
fields.append("From directories of images")
|
|
480
|
+
dir_list = []
|
|
481
|
+
for dir in observation[cfg.DIRECTORIES_LIST]:
|
|
482
|
+
dir_list.append(dir)
|
|
483
|
+
fields.append(";".join(dir_list))
|
|
484
|
+
media_durations_str = cfg.NA
|
|
485
|
+
fps_str = cfg.NA
|
|
486
|
+
|
|
487
|
+
else:
|
|
488
|
+
fields.append("")
|
|
489
|
+
|
|
490
|
+
# time offset
|
|
491
|
+
fields.append(observation[cfg.TIME_OFFSET])
|
|
492
|
+
|
|
493
|
+
# media duration
|
|
494
|
+
fields.append(media_durations_str)
|
|
495
|
+
|
|
496
|
+
# FPS
|
|
497
|
+
fields.append(fps_str)
|
|
498
|
+
|
|
499
|
+
# indep var
|
|
500
|
+
if cfg.INDEPENDENT_VARIABLES in observation:
|
|
501
|
+
for variable in observation[cfg.INDEPENDENT_VARIABLES]:
|
|
502
|
+
fields.append(observation[cfg.INDEPENDENT_VARIABLES][variable])
|
|
503
|
+
|
|
504
|
+
fields.append(event[cfg.PJ_OBS_FIELDS[observation[cfg.TYPE]][cfg.SUBJECT]])
|
|
505
|
+
fields.append(event[cfg.PJ_OBS_FIELDS[observation[cfg.TYPE]][cfg.BEHAVIOR_CODE]])
|
|
506
|
+
|
|
507
|
+
# behavioral category
|
|
508
|
+
try:
|
|
509
|
+
behav_category = behavioral_category[event[cfg.PJ_OBS_FIELDS[observation[cfg.TYPE]][cfg.BEHAVIOR_CODE]]]
|
|
510
|
+
except Exception:
|
|
511
|
+
behav_category = ""
|
|
512
|
+
fields.append(behav_category)
|
|
513
|
+
|
|
514
|
+
# modifiers
|
|
515
|
+
if max_modifiers:
|
|
516
|
+
modifiers = event[cfg.PJ_OBS_FIELDS[observation[cfg.TYPE]][cfg.MODIFIER]].split("|")
|
|
517
|
+
while len(modifiers) < max_modifiers:
|
|
518
|
+
modifiers.append("")
|
|
519
|
+
|
|
520
|
+
for m in modifiers:
|
|
521
|
+
fields.append(m)
|
|
522
|
+
|
|
523
|
+
# status (START/STOP)
|
|
524
|
+
fields.append(event[-1])
|
|
525
|
+
|
|
526
|
+
# time
|
|
527
|
+
fields.append(util.convertTime(time_format=cfg.S, sec=event[cfg.EVENT_TIME_FIELD_IDX]))
|
|
528
|
+
|
|
529
|
+
# check video file name containing the event
|
|
530
|
+
if observation[cfg.TYPE] == cfg.MEDIA:
|
|
531
|
+
video_file_name = observation_operations.event2media_file_name(observation, event[cfg.EVENT_TIME_FIELD_IDX])
|
|
532
|
+
if video_file_name is None:
|
|
533
|
+
video_file_name = "Not found"
|
|
534
|
+
|
|
535
|
+
elif observation[cfg.TYPE] in (cfg.LIVE, cfg.IMAGES):
|
|
536
|
+
video_file_name = cfg.NA
|
|
537
|
+
fields.append(video_file_name)
|
|
538
|
+
|
|
539
|
+
# image file index
|
|
540
|
+
if observation[cfg.TYPE] == cfg.IMAGES:
|
|
541
|
+
fields.append(event[cfg.PJ_OBS_FIELDS[cfg.IMAGES][cfg.IMAGE_INDEX]]) # image file index
|
|
542
|
+
elif observation[cfg.TYPE] == cfg.MEDIA:
|
|
543
|
+
frame_idx = event_operations.read_event_field(event, cfg.MEDIA, cfg.FRAME_INDEX)
|
|
544
|
+
fields.append(frame_idx) # frame index
|
|
545
|
+
elif observation[cfg.TYPE] == cfg.LIVE:
|
|
546
|
+
fields.append(cfg.NA)
|
|
547
|
+
else:
|
|
548
|
+
fields.append("")
|
|
549
|
+
|
|
550
|
+
# image file path
|
|
551
|
+
if observation[cfg.TYPE] == cfg.IMAGES:
|
|
552
|
+
fields.append(event[cfg.PJ_OBS_FIELDS[cfg.IMAGES][cfg.IMAGE_PATH]]) # image file path
|
|
553
|
+
elif observation[cfg.TYPE] in (cfg.LIVE, cfg.MEDIA):
|
|
554
|
+
fields.append(cfg.NA)
|
|
555
|
+
else:
|
|
556
|
+
fields.append("")
|
|
557
|
+
|
|
558
|
+
# comment
|
|
559
|
+
fields.append(event[cfg.PJ_OBS_FIELDS[observation[cfg.TYPE]][cfg.COMMENT]].replace(os.linesep, " "))
|
|
560
|
+
|
|
561
|
+
rows.append(fields)
|
|
562
|
+
|
|
563
|
+
max_len = max([len(r) for r in rows])
|
|
564
|
+
data = tablib.Dataset()
|
|
565
|
+
|
|
566
|
+
data.title = util.safe_xl_worksheet_title(obs_id, output_format)
|
|
567
|
+
|
|
568
|
+
for row in rows:
|
|
569
|
+
data.append(util.complete(row, max_len))
|
|
570
|
+
|
|
571
|
+
r, msg = dataset_write(data, file_name, output_format, dtype=fields_type)
|
|
572
|
+
|
|
573
|
+
return r, msg
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def dataset_write(dataset: tablib.Dataset, file_name: str, output_format: str, dtype: dict = {}) -> tuple: # -> tuple[bool, str]:
|
|
577
|
+
"""
|
|
578
|
+
write a tablib dataset with aggregated events or tabular events to file in specified format (output_format)
|
|
579
|
+
|
|
580
|
+
Args:
|
|
581
|
+
dataset (tablib.dataset): dataset to write
|
|
582
|
+
file_name (str): file name
|
|
583
|
+
output_format (str): format of output
|
|
584
|
+
dtype (dict): type of field
|
|
585
|
+
|
|
586
|
+
Returns:
|
|
587
|
+
bool: result. True if OK else False
|
|
588
|
+
str: error message
|
|
589
|
+
"""
|
|
590
|
+
|
|
591
|
+
logging.debug("function: dataset_write")
|
|
592
|
+
|
|
593
|
+
try:
|
|
594
|
+
if output_format in (cfg.PANDAS_DF_EXT, cfg.RDS_EXT):
|
|
595
|
+
# build pandas dataframe from the tsv export of tablib dataset
|
|
596
|
+
date_type: list = []
|
|
597
|
+
for field_name in dtype:
|
|
598
|
+
if dtype[field_name] == dt.datetime:
|
|
599
|
+
date_type.append(field_name)
|
|
600
|
+
# delete data type from dtype
|
|
601
|
+
for field_name in date_type:
|
|
602
|
+
del dtype[field_name]
|
|
603
|
+
|
|
604
|
+
df = pd.read_csv(
|
|
605
|
+
StringIO(dataset.export(cfg.TSV_EXT)),
|
|
606
|
+
sep="\t",
|
|
607
|
+
dtype=dtype,
|
|
608
|
+
parse_dates=date_type,
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
if output_format == cfg.PANDAS_DF_EXT:
|
|
612
|
+
df.to_pickle(file_name)
|
|
613
|
+
|
|
614
|
+
if output_format == cfg.RDS_EXT and flag_pyreadr_loaded:
|
|
615
|
+
pyreadr.write_rds(file_name, df)
|
|
616
|
+
|
|
617
|
+
return True, ""
|
|
618
|
+
|
|
619
|
+
if output_format in (cfg.CSV_EXT, cfg.TSV_EXT, cfg.HTML_EXT):
|
|
620
|
+
with open(file_name, "wb") as f:
|
|
621
|
+
f.write(str.encode(dataset.export(output_format)))
|
|
622
|
+
return True, ""
|
|
623
|
+
|
|
624
|
+
if output_format in (cfg.ODS_EXT, cfg.XLS_EXT, cfg.XLSX_EXT):
|
|
625
|
+
dataset.title = util.safe_xl_worksheet_title(dataset.title, output_format)
|
|
626
|
+
|
|
627
|
+
with open(file_name, "wb") as f:
|
|
628
|
+
f.write(dataset.export(output_format))
|
|
629
|
+
return True, ""
|
|
630
|
+
|
|
631
|
+
return False, f"Format {output_format} not found"
|
|
632
|
+
|
|
633
|
+
except Exception:
|
|
634
|
+
return False, str(sys.exc_info()[1])
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def export_aggregated_events(pj: dict, parameters: dict, obsId: str, force_number_modifiers: int = 0) -> Tuple[tablib.Dataset, int]:
|
|
638
|
+
"""
|
|
639
|
+
export aggregated events of one observation
|
|
640
|
+
|
|
641
|
+
Args:
|
|
642
|
+
pj (dict): BORIS project
|
|
643
|
+
parameters (dict): subjects, behaviors
|
|
644
|
+
obsId (str): observation id
|
|
645
|
+
force_number_modifiers (int): force the number of modifiers to return
|
|
646
|
+
|
|
647
|
+
Returns:
|
|
648
|
+
tablib.Dataset:
|
|
649
|
+
int: Maximum number of modifiers
|
|
650
|
+
|
|
651
|
+
"""
|
|
652
|
+
logging.debug(f"function: export aggregated events {obsId} parameters: {parameters} ")
|
|
653
|
+
|
|
654
|
+
observation = pj[cfg.OBSERVATIONS][obsId]
|
|
655
|
+
interval = parameters["time"]
|
|
656
|
+
|
|
657
|
+
data = tablib.Dataset()
|
|
658
|
+
|
|
659
|
+
start_coding, end_coding, coding_duration = observation_operations.coding_time(pj[cfg.OBSERVATIONS], [obsId])
|
|
660
|
+
start_interval, end_interval = observation_operations.time_intervals_range(pj[cfg.OBSERVATIONS], [obsId])
|
|
661
|
+
|
|
662
|
+
if start_coding is None and end_coding is None: # no events
|
|
663
|
+
return data, 0
|
|
664
|
+
|
|
665
|
+
if interval == cfg.TIME_EVENTS:
|
|
666
|
+
min_time = float(start_coding)
|
|
667
|
+
max_time = float(end_coding)
|
|
668
|
+
|
|
669
|
+
if interval == cfg.TIME_FULL_OBS:
|
|
670
|
+
if observation[cfg.TYPE] == cfg.MEDIA:
|
|
671
|
+
max_media_duration, _ = observation_operations.media_duration(pj[cfg.OBSERVATIONS], [obsId])
|
|
672
|
+
min_time = float(0)
|
|
673
|
+
max_time = float(max_media_duration)
|
|
674
|
+
coding_duration = max_media_duration
|
|
675
|
+
if observation[cfg.TYPE] in (cfg.LIVE, cfg.IMAGES):
|
|
676
|
+
min_time = float(start_coding)
|
|
677
|
+
max_time = float(end_coding)
|
|
678
|
+
|
|
679
|
+
if interval == cfg.TIME_ARBITRARY_INTERVAL:
|
|
680
|
+
min_time = float(parameters[cfg.START_TIME])
|
|
681
|
+
max_time = float(parameters[cfg.END_TIME])
|
|
682
|
+
|
|
683
|
+
if interval == cfg.TIME_OBS_INTERVAL:
|
|
684
|
+
max_media_duration, _ = observation_operations.media_duration(pj[cfg.OBSERVATIONS], [obsId])
|
|
685
|
+
min_time = float(start_interval)
|
|
686
|
+
# Use max media duration for max time if no interval is defined (=0)
|
|
687
|
+
max_time = float(end_interval) if end_interval != 0 else float(max_media_duration)
|
|
688
|
+
|
|
689
|
+
logging.debug(f"min_time: {min_time} max_time: {max_time}")
|
|
690
|
+
|
|
691
|
+
# obs description
|
|
692
|
+
obs_description = util.eol2space(observation[cfg.DESCRIPTION])
|
|
693
|
+
|
|
694
|
+
_, _, connector = db_functions.load_aggregated_events_in_db(
|
|
695
|
+
pj, parameters[cfg.SELECTED_SUBJECTS], [obsId], parameters[cfg.SELECTED_BEHAVIORS]
|
|
696
|
+
)
|
|
697
|
+
if connector is None:
|
|
698
|
+
logging.critical("error when loading aggregated events in DB")
|
|
699
|
+
return data, 0
|
|
700
|
+
|
|
701
|
+
cursor = connector.cursor()
|
|
702
|
+
|
|
703
|
+
# adapt start and stop to the selected time interval
|
|
704
|
+
if not math.isnan(min_time) and not math.isnan(max_time): # obs with timestamp
|
|
705
|
+
# delete events outside time interval
|
|
706
|
+
cursor.execute(
|
|
707
|
+
"DELETE FROM aggregated_events WHERE observation = ? AND (start < ? AND stop < ?) OR (start > ? AND stop > ?)",
|
|
708
|
+
(
|
|
709
|
+
obsId,
|
|
710
|
+
min_time,
|
|
711
|
+
min_time,
|
|
712
|
+
max_time,
|
|
713
|
+
max_time,
|
|
714
|
+
),
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
cursor.execute(
|
|
718
|
+
"UPDATE aggregated_events SET start = ? WHERE observation = ? AND start < ? AND stop BETWEEN ? AND ?",
|
|
719
|
+
(
|
|
720
|
+
min_time,
|
|
721
|
+
obsId,
|
|
722
|
+
min_time,
|
|
723
|
+
min_time,
|
|
724
|
+
max_time,
|
|
725
|
+
),
|
|
726
|
+
)
|
|
727
|
+
cursor.execute(
|
|
728
|
+
"UPDATE aggregated_events SET stop = ? WHERE observation = ? AND stop > ? AND start BETWEEN ? AND ?",
|
|
729
|
+
(
|
|
730
|
+
max_time,
|
|
731
|
+
obsId,
|
|
732
|
+
max_time,
|
|
733
|
+
min_time,
|
|
734
|
+
max_time,
|
|
735
|
+
),
|
|
736
|
+
)
|
|
737
|
+
|
|
738
|
+
cursor.execute(
|
|
739
|
+
"UPDATE aggregated_events SET start = ?, stop = ? WHERE observation = ? AND start < ? AND stop > ?",
|
|
740
|
+
(
|
|
741
|
+
min_time,
|
|
742
|
+
max_time,
|
|
743
|
+
obsId,
|
|
744
|
+
min_time,
|
|
745
|
+
max_time,
|
|
746
|
+
),
|
|
747
|
+
)
|
|
748
|
+
|
|
749
|
+
behavioral_category = project_functions.behavior_category(pj[cfg.ETHOGRAM])
|
|
750
|
+
|
|
751
|
+
cursor.execute("SELECT DISTINCT modifiers FROM aggregated_events")
|
|
752
|
+
|
|
753
|
+
if not force_number_modifiers:
|
|
754
|
+
max_modifiers: int = 0
|
|
755
|
+
for row in cursor.fetchall():
|
|
756
|
+
if row["modifiers"]:
|
|
757
|
+
max_modifiers = max(max_modifiers, row["modifiers"].count("|") + 1)
|
|
758
|
+
else:
|
|
759
|
+
max_modifiers = force_number_modifiers
|
|
760
|
+
|
|
761
|
+
for subject in parameters[cfg.SELECTED_SUBJECTS]:
|
|
762
|
+
# calculate observation duration by subject (by obs)
|
|
763
|
+
cursor.execute(("SELECT SUM(stop - start) AS duration FROM aggregated_events WHERE subject = ? "), (subject,))
|
|
764
|
+
duration_by_subject_by_obs = cursor.fetchone()["duration"]
|
|
765
|
+
if duration_by_subject_by_obs is not None:
|
|
766
|
+
duration_by_subject_by_obs = round(duration_by_subject_by_obs, 3)
|
|
767
|
+
|
|
768
|
+
for behavior in parameters[cfg.SELECTED_BEHAVIORS]:
|
|
769
|
+
cursor.execute(
|
|
770
|
+
"SELECT DISTINCT modifiers FROM aggregated_events WHERE subject=? AND behavior=? ORDER BY modifiers",
|
|
771
|
+
(
|
|
772
|
+
subject,
|
|
773
|
+
behavior,
|
|
774
|
+
),
|
|
775
|
+
)
|
|
776
|
+
|
|
777
|
+
rows_distinct_modifiers = list(x[0] for x in cursor.fetchall())
|
|
778
|
+
|
|
779
|
+
for distinct_modifiers in rows_distinct_modifiers:
|
|
780
|
+
cursor.execute(
|
|
781
|
+
(
|
|
782
|
+
"SELECT start, stop, type, modifiers, comment, comment_stop, "
|
|
783
|
+
"image_index_start, image_index_stop, image_path_start, image_path_stop "
|
|
784
|
+
"FROM aggregated_events "
|
|
785
|
+
"WHERE subject = ? AND behavior = ? AND modifiers = ? ORDER BY start, image_index_start"
|
|
786
|
+
),
|
|
787
|
+
(subject, behavior, distinct_modifiers),
|
|
788
|
+
)
|
|
789
|
+
|
|
790
|
+
for row in cursor.fetchall():
|
|
791
|
+
media_file_name = cfg.NA
|
|
792
|
+
|
|
793
|
+
if observation[cfg.TYPE] == cfg.MEDIA:
|
|
794
|
+
observation_type = "Media file"
|
|
795
|
+
|
|
796
|
+
# get the media file name of the start of event
|
|
797
|
+
media_file_name = observation_operations.event2media_file_name(observation, row["start"])
|
|
798
|
+
if media_file_name is None:
|
|
799
|
+
media_file_name = "Not found"
|
|
800
|
+
|
|
801
|
+
media_file_str, fps_str, media_durations_str = "", "", ""
|
|
802
|
+
|
|
803
|
+
for player in observation[cfg.FILE]:
|
|
804
|
+
media_file_lst, fps_lst, media_durations_lst = [], [], []
|
|
805
|
+
if observation[cfg.FILE][player]:
|
|
806
|
+
for media_file in observation[cfg.FILE][player]:
|
|
807
|
+
media_file_lst.append(media_file)
|
|
808
|
+
fps_lst.append(f"{observation[cfg.MEDIA_INFO][cfg.FPS].get(media_file, cfg.NA):.3f}")
|
|
809
|
+
media_durations_lst.append(f"{observation[cfg.MEDIA_INFO][cfg.LENGTH].get(media_file, cfg.NA):.3f}")
|
|
810
|
+
if player > "1":
|
|
811
|
+
media_file_str += "|"
|
|
812
|
+
fps_str += "|"
|
|
813
|
+
media_durations_str += "|"
|
|
814
|
+
media_file_str += f"player #{player}:" + ";".join(media_file_lst)
|
|
815
|
+
fps_str += ";".join(fps_lst)
|
|
816
|
+
media_durations_str += ";".join(media_durations_lst)
|
|
817
|
+
|
|
818
|
+
if observation[cfg.TYPE] == cfg.LIVE:
|
|
819
|
+
observation_type = "Live observation"
|
|
820
|
+
media_file_str = cfg.NA
|
|
821
|
+
fps_str = cfg.NA
|
|
822
|
+
media_durations_str = cfg.NA
|
|
823
|
+
|
|
824
|
+
if observation[cfg.TYPE] == cfg.IMAGES:
|
|
825
|
+
observation_type = "From pictures"
|
|
826
|
+
media_file_str = ""
|
|
827
|
+
for dir in observation[cfg.DIRECTORIES_LIST]:
|
|
828
|
+
media_file_str += f"{dir}; "
|
|
829
|
+
fps_str = cfg.NA
|
|
830
|
+
# TODO: number of pictures in each directory
|
|
831
|
+
media_durations_str = cfg.NA
|
|
832
|
+
|
|
833
|
+
row_data = []
|
|
834
|
+
|
|
835
|
+
row_data.extend(
|
|
836
|
+
[
|
|
837
|
+
obsId,
|
|
838
|
+
observation["date"].replace("T", " "),
|
|
839
|
+
obs_description,
|
|
840
|
+
observation_type,
|
|
841
|
+
media_file_str, # list of media used in observation
|
|
842
|
+
pj[cfg.OBSERVATIONS][obsId][cfg.TIME_OFFSET],
|
|
843
|
+
f"{coding_duration:.3f}" if not coding_duration.is_nan() else cfg.NA,
|
|
844
|
+
media_durations_str,
|
|
845
|
+
fps_str,
|
|
846
|
+
]
|
|
847
|
+
)
|
|
848
|
+
|
|
849
|
+
# independent variables
|
|
850
|
+
if cfg.INDEPENDENT_VARIABLES in pj:
|
|
851
|
+
for idx_var in util.sorted_keys(pj[cfg.INDEPENDENT_VARIABLES]):
|
|
852
|
+
if pj[cfg.INDEPENDENT_VARIABLES][idx_var]["label"] in observation[cfg.INDEPENDENT_VARIABLES]:
|
|
853
|
+
var_value = observation[cfg.INDEPENDENT_VARIABLES][pj[cfg.INDEPENDENT_VARIABLES][idx_var]["label"]]
|
|
854
|
+
if pj[cfg.INDEPENDENT_VARIABLES][idx_var]["type"] == "timestamp":
|
|
855
|
+
var_value = var_value.replace("T", " ")
|
|
856
|
+
|
|
857
|
+
row_data.append(var_value)
|
|
858
|
+
else:
|
|
859
|
+
row_data.append("")
|
|
860
|
+
|
|
861
|
+
row_data.extend(
|
|
862
|
+
[
|
|
863
|
+
subject,
|
|
864
|
+
duration_by_subject_by_obs,
|
|
865
|
+
behavior,
|
|
866
|
+
behavioral_category[behavior] if behavioral_category[behavior] else "Not defined",
|
|
867
|
+
]
|
|
868
|
+
)
|
|
869
|
+
|
|
870
|
+
# modifiers
|
|
871
|
+
if max_modifiers:
|
|
872
|
+
modifiers = row["modifiers"].split("|")
|
|
873
|
+
while len(modifiers) < max_modifiers:
|
|
874
|
+
modifiers.append("")
|
|
875
|
+
for modifier in modifiers:
|
|
876
|
+
row_data.append(modifier)
|
|
877
|
+
|
|
878
|
+
if row["type"] == cfg.POINT:
|
|
879
|
+
row_data.extend(
|
|
880
|
+
[
|
|
881
|
+
cfg.POINT,
|
|
882
|
+
f"{row['start']:.3f}" if row["start"] is not None else cfg.NA, # start
|
|
883
|
+
f"{row['stop']:.3f}" if row["stop"] is not None else cfg.NA, # stop
|
|
884
|
+
cfg.NA, # duration
|
|
885
|
+
media_file_name, # Media file name
|
|
886
|
+
]
|
|
887
|
+
)
|
|
888
|
+
|
|
889
|
+
if row["type"] == cfg.STATE:
|
|
890
|
+
row_data.extend(
|
|
891
|
+
[
|
|
892
|
+
cfg.STATE,
|
|
893
|
+
f"{row['start']:.3f}" if row["start"] is not None else cfg.NA,
|
|
894
|
+
f"{row['stop']:.3f}" if row["stop"] is not None else cfg.NA,
|
|
895
|
+
# duration
|
|
896
|
+
f"{row['stop'] - row['start']:.3f}" if (row["stop"] is not None) and (row["start"] is not None) else cfg.NA,
|
|
897
|
+
media_file_name, # Media file name
|
|
898
|
+
]
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
row_data.extend(
|
|
902
|
+
[
|
|
903
|
+
row["image_index_start"],
|
|
904
|
+
row["image_index_stop"],
|
|
905
|
+
row["image_path_start"],
|
|
906
|
+
row["image_path_stop"],
|
|
907
|
+
row["comment"],
|
|
908
|
+
row["comment_stop"] if (row["type"] == cfg.STATE) else "",
|
|
909
|
+
]
|
|
910
|
+
)
|
|
911
|
+
|
|
912
|
+
data.append(row_data)
|
|
913
|
+
|
|
914
|
+
return data, max_modifiers
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
def events_to_behavioral_sequences(pj, obs_id: str, subj: str, parameters: dict, behav_seq_separator: str) -> str:
|
|
918
|
+
"""
|
|
919
|
+
return the behavioral sequence (behavioral string) for subject in obs_id
|
|
920
|
+
|
|
921
|
+
Args:
|
|
922
|
+
pj (dict): project
|
|
923
|
+
obs_id (str): observation id
|
|
924
|
+
subj (str): subject
|
|
925
|
+
parameters (dict): parameters
|
|
926
|
+
behav_seq_separator (str): separator of behviors in behavioral sequences
|
|
927
|
+
|
|
928
|
+
Returns:
|
|
929
|
+
str: behavioral string for selected subject in selected observation
|
|
930
|
+
"""
|
|
931
|
+
|
|
932
|
+
out: str = ""
|
|
933
|
+
current_states: list = []
|
|
934
|
+
# add status (POINT, START, STOP) to event
|
|
935
|
+
events_with_status = project_functions.events_start_stop(
|
|
936
|
+
pj[cfg.ETHOGRAM], pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS], pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE]
|
|
937
|
+
)
|
|
938
|
+
|
|
939
|
+
for event in events_with_status:
|
|
940
|
+
# check if event in selected behaviors
|
|
941
|
+
|
|
942
|
+
if event[cfg.EVENT_BEHAVIOR_FIELD_IDX] not in parameters[cfg.SELECTED_BEHAVIORS]:
|
|
943
|
+
continue
|
|
944
|
+
|
|
945
|
+
if event[cfg.EVENT_SUBJECT_FIELD_IDX] == subj or (subj == cfg.NO_FOCAL_SUBJECT and event[cfg.EVENT_SUBJECT_FIELD_IDX] == ""):
|
|
946
|
+
# if event[cfg.EVENT_STATUS_FIELD_IDX] == cfg.POINT:
|
|
947
|
+
if event[-1] == cfg.POINT: # status is last element
|
|
948
|
+
if current_states:
|
|
949
|
+
out += "+".join(current_states) + "+" + event[cfg.EVENT_BEHAVIOR_FIELD_IDX]
|
|
950
|
+
else:
|
|
951
|
+
out += event[cfg.EVENT_BEHAVIOR_FIELD_IDX]
|
|
952
|
+
|
|
953
|
+
if parameters[cfg.INCLUDE_MODIFIERS]:
|
|
954
|
+
out += "&" + event[cfg.EVENT_MODIFIER_FIELD_IDX].replace("|", "+")
|
|
955
|
+
|
|
956
|
+
out += behav_seq_separator
|
|
957
|
+
|
|
958
|
+
# if event[cfg.EVENT_STATUS_FIELD_IDX] == cfg.START:
|
|
959
|
+
if event[-1] == cfg.START: # status is last element
|
|
960
|
+
if parameters[cfg.INCLUDE_MODIFIERS]:
|
|
961
|
+
current_states.append(
|
|
962
|
+
(
|
|
963
|
+
f"{event[cfg.EVENT_BEHAVIOR_FIELD_IDX]}"
|
|
964
|
+
f"{'&' if event[cfg.EVENT_MODIFIER_FIELD_IDX] else ''}"
|
|
965
|
+
f"{event[cfg.EVENT_MODIFIER_FIELD_IDX].replace('|', ';')}"
|
|
966
|
+
)
|
|
967
|
+
)
|
|
968
|
+
else:
|
|
969
|
+
current_states.append(event[cfg.EVENT_BEHAVIOR_FIELD_IDX])
|
|
970
|
+
|
|
971
|
+
out += "+".join(sorted(current_states))
|
|
972
|
+
|
|
973
|
+
out += behav_seq_separator
|
|
974
|
+
|
|
975
|
+
# if event[cfg.EVENT_STATUS_FIELD_IDX] == cfg.STOP:
|
|
976
|
+
if event[-1] == cfg.STOP:
|
|
977
|
+
if parameters[cfg.INCLUDE_MODIFIERS]:
|
|
978
|
+
behav_modif = (
|
|
979
|
+
f"{event[cfg.EVENT_BEHAVIOR_FIELD_IDX]}"
|
|
980
|
+
f"{'&' if event[cfg.EVENT_MODIFIER_FIELD_IDX] else ''}"
|
|
981
|
+
f"{event[cfg.EVENT_MODIFIER_FIELD_IDX].replace('|', ';')}"
|
|
982
|
+
)
|
|
983
|
+
else:
|
|
984
|
+
behav_modif = event[cfg.EVENT_BEHAVIOR_FIELD_IDX]
|
|
985
|
+
if behav_modif in current_states:
|
|
986
|
+
current_states.remove(behav_modif)
|
|
987
|
+
|
|
988
|
+
if current_states:
|
|
989
|
+
out += "+".join(sorted(current_states))
|
|
990
|
+
|
|
991
|
+
out += behav_seq_separator
|
|
992
|
+
|
|
993
|
+
# remove last separator (if separator not empty)
|
|
994
|
+
if behav_seq_separator:
|
|
995
|
+
out = out[0 : -len(behav_seq_separator)]
|
|
996
|
+
|
|
997
|
+
return out
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
def events_to_behavioral_sequences_all_subj(pj, obs_id: str, subjects_list: list, parameters: dict, behav_seq_separator: str) -> str:
|
|
1001
|
+
"""
|
|
1002
|
+
return the behavioral sequences for all selected subjects in obs_id
|
|
1003
|
+
|
|
1004
|
+
Args:
|
|
1005
|
+
pj (dict): project
|
|
1006
|
+
obs_id (str): observation id
|
|
1007
|
+
subjects_list (list): list of subjects
|
|
1008
|
+
parameters (dict): parameters
|
|
1009
|
+
behav_seq_separator (str): separator of behviors in behavioral sequences
|
|
1010
|
+
|
|
1011
|
+
Returns:
|
|
1012
|
+
str: behavioral sequences for all selected subjects in selected observation
|
|
1013
|
+
"""
|
|
1014
|
+
|
|
1015
|
+
out = ""
|
|
1016
|
+
current_states = {i: [] for i in subjects_list}
|
|
1017
|
+
events_with_status = project_functions.events_start_stop(
|
|
1018
|
+
pj[cfg.ETHOGRAM], pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS], pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE]
|
|
1019
|
+
)
|
|
1020
|
+
|
|
1021
|
+
for event in events_with_status:
|
|
1022
|
+
# check if event in selected behaviors
|
|
1023
|
+
if event[cfg.EVENT_BEHAVIOR_FIELD_IDX] not in parameters[cfg.SELECTED_BEHAVIORS]:
|
|
1024
|
+
continue
|
|
1025
|
+
|
|
1026
|
+
if (event[cfg.EVENT_SUBJECT_FIELD_IDX] in subjects_list) or (
|
|
1027
|
+
event[cfg.EVENT_SUBJECT_FIELD_IDX] == "" and cfg.NO_FOCAL_SUBJECT in subjects_list
|
|
1028
|
+
):
|
|
1029
|
+
subject = event[cfg.EVENT_SUBJECT_FIELD_IDX] if event[cfg.EVENT_SUBJECT_FIELD_IDX] else cfg.NO_FOCAL_SUBJECT
|
|
1030
|
+
|
|
1031
|
+
if event[-1] == cfg.POINT:
|
|
1032
|
+
if current_states[subject]:
|
|
1033
|
+
out += f"[{subject}]" + "+".join(current_states[subject]) + "+" + event[cfg.EVENT_BEHAVIOR_FIELD_IDX]
|
|
1034
|
+
else:
|
|
1035
|
+
out += f"[{subject}]" + event[cfg.EVENT_BEHAVIOR_FIELD_IDX]
|
|
1036
|
+
|
|
1037
|
+
if parameters[cfg.INCLUDE_MODIFIERS]:
|
|
1038
|
+
out += "&" + event[cfg.EVENT_MODIFIER_FIELD_IDX].replace("|", "+")
|
|
1039
|
+
|
|
1040
|
+
out += behav_seq_separator
|
|
1041
|
+
|
|
1042
|
+
if event[-1] == cfg.START:
|
|
1043
|
+
if parameters[cfg.INCLUDE_MODIFIERS]:
|
|
1044
|
+
current_states[subject].append(
|
|
1045
|
+
(
|
|
1046
|
+
f"{event[cfg.EVENT_BEHAVIOR_FIELD_IDX]}"
|
|
1047
|
+
f"{'&' if event[cfg.EVENT_MODIFIER_FIELD_IDX] else ''}"
|
|
1048
|
+
f"{event[cfg.EVENT_MODIFIER_FIELD_IDX].replace('|', ';')}"
|
|
1049
|
+
)
|
|
1050
|
+
)
|
|
1051
|
+
else:
|
|
1052
|
+
current_states[subject].append(event[cfg.EVENT_BEHAVIOR_FIELD_IDX])
|
|
1053
|
+
|
|
1054
|
+
out += f"[{subject}]" + "+".join(sorted(current_states[subject]))
|
|
1055
|
+
|
|
1056
|
+
out += behav_seq_separator
|
|
1057
|
+
|
|
1058
|
+
if event[-1] == cfg.STOP:
|
|
1059
|
+
if parameters[cfg.INCLUDE_MODIFIERS]:
|
|
1060
|
+
behav_modif = (
|
|
1061
|
+
f"{event[cfg.EVENT_BEHAVIOR_FIELD_IDX]}"
|
|
1062
|
+
f"{'&' if event[cfg.EVENT_MODIFIER_FIELD_IDX] else ''}"
|
|
1063
|
+
f"{event[cfg.EVENT_MODIFIER_FIELD_IDX].replace('|', ';')}"
|
|
1064
|
+
)
|
|
1065
|
+
else:
|
|
1066
|
+
behav_modif = event[cfg.EVENT_BEHAVIOR_FIELD_IDX]
|
|
1067
|
+
if behav_modif in current_states[subject]:
|
|
1068
|
+
current_states[subject].remove(behav_modif)
|
|
1069
|
+
|
|
1070
|
+
if current_states[subject]:
|
|
1071
|
+
out += f"[{subject}]" + "+".join(sorted(current_states[subject]))
|
|
1072
|
+
|
|
1073
|
+
out += behav_seq_separator
|
|
1074
|
+
|
|
1075
|
+
# remove last separator (if separator not empty)
|
|
1076
|
+
if behav_seq_separator:
|
|
1077
|
+
out = out[0 : -len(behav_seq_separator)]
|
|
1078
|
+
|
|
1079
|
+
return out
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
def events_to_timed_behavioral_sequences(
|
|
1083
|
+
pj: dict, obs_id: str, subject: str, parameters: dict, precision: float, behav_seq_separator: str
|
|
1084
|
+
) -> str:
|
|
1085
|
+
"""
|
|
1086
|
+
return the behavioral string for subject in obsId
|
|
1087
|
+
|
|
1088
|
+
Args:
|
|
1089
|
+
pj (dict): project
|
|
1090
|
+
obs_id (str): observation id
|
|
1091
|
+
subj (str): subject
|
|
1092
|
+
parameters (dict): parameters
|
|
1093
|
+
precision (float): time value for scan sample
|
|
1094
|
+
behav_seq_separator (str): separator of behviors in behavioral sequences
|
|
1095
|
+
|
|
1096
|
+
Returns:
|
|
1097
|
+
str: behavioral string for selected subject in selected observation
|
|
1098
|
+
"""
|
|
1099
|
+
|
|
1100
|
+
out: str = ""
|
|
1101
|
+
|
|
1102
|
+
state_behaviors_codes = util.state_behavior_codes(pj[cfg.ETHOGRAM])
|
|
1103
|
+
delta = dec(str(round(precision, 3)))
|
|
1104
|
+
out = ""
|
|
1105
|
+
t = dec("0.000")
|
|
1106
|
+
|
|
1107
|
+
current = []
|
|
1108
|
+
while t < pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS][-1][0]:
|
|
1109
|
+
"""
|
|
1110
|
+
if out:
|
|
1111
|
+
out += behav_seq_separator
|
|
1112
|
+
"""
|
|
1113
|
+
csbs = util.get_current_states_modifiers_by_subject(
|
|
1114
|
+
state_behaviors_codes,
|
|
1115
|
+
pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS],
|
|
1116
|
+
{"": {"name": subject}},
|
|
1117
|
+
t,
|
|
1118
|
+
include_modifiers=False,
|
|
1119
|
+
)[""]
|
|
1120
|
+
if csbs:
|
|
1121
|
+
if current:
|
|
1122
|
+
if csbs == current[-1]:
|
|
1123
|
+
current.append("+".join(csbs))
|
|
1124
|
+
else:
|
|
1125
|
+
out.append(current)
|
|
1126
|
+
current = [csbs]
|
|
1127
|
+
else:
|
|
1128
|
+
current = [csbs]
|
|
1129
|
+
|
|
1130
|
+
t += delta
|
|
1131
|
+
|
|
1132
|
+
return out
|
|
1133
|
+
|
|
1134
|
+
|
|
1135
|
+
def observation_to_behavioral_sequences(pj, selected_observations, parameters, behaviors_separator, separated_subjects, timed, file_name):
|
|
1136
|
+
try:
|
|
1137
|
+
with open(file_name, "w", encoding="utf-8") as out_file:
|
|
1138
|
+
for obs_id in selected_observations:
|
|
1139
|
+
# observation id
|
|
1140
|
+
out_file.write("\n" + f"# observation id: {obs_id}" + "\n")
|
|
1141
|
+
# observation description
|
|
1142
|
+
descr = pj[cfg.OBSERVATIONS][obs_id]["description"]
|
|
1143
|
+
if "\r\n" in descr:
|
|
1144
|
+
descr = descr.replace("\r\n", "\n# ")
|
|
1145
|
+
elif "\r" in descr:
|
|
1146
|
+
descr = descr.replace("\r", "\n# ")
|
|
1147
|
+
out_file.write(f"# observation description: {descr}\n\n")
|
|
1148
|
+
# media file name
|
|
1149
|
+
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.MEDIA:
|
|
1150
|
+
out_file.write(f"# Observation type: Media file{os.linesep}")
|
|
1151
|
+
media_file_str = ""
|
|
1152
|
+
|
|
1153
|
+
for player in pj[cfg.OBSERVATIONS][obs_id][cfg.FILE]:
|
|
1154
|
+
if pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][player]:
|
|
1155
|
+
media_file_str += f"player #{player}: "
|
|
1156
|
+
# fps_str += f"player #{player}: "
|
|
1157
|
+
for media_file in pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][player]:
|
|
1158
|
+
media_file_str += f"{media_file}; "
|
|
1159
|
+
# fps_str += f"{pj[cfg.OBSERVATIONS][obs_id][cfg.MEDIA_INFO][cfg.FPS].get(media_file, cfg.NA):.3f}; "
|
|
1160
|
+
|
|
1161
|
+
out_file.write(f"# Media file path: {media_file_str}\n\n")
|
|
1162
|
+
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.LIVE:
|
|
1163
|
+
out_file.write(f"# Observation type: Live observation{os.linesep}{os.linesep}")
|
|
1164
|
+
|
|
1165
|
+
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.IMAGES:
|
|
1166
|
+
out_file.write(f"# Observation type: From pictures{os.linesep}{os.linesep}")
|
|
1167
|
+
|
|
1168
|
+
# independent variables
|
|
1169
|
+
if cfg.INDEPENDENT_VARIABLES in pj[cfg.OBSERVATIONS][obs_id]:
|
|
1170
|
+
out_file.write("# Independent variables\n")
|
|
1171
|
+
|
|
1172
|
+
for variable in pj[cfg.OBSERVATIONS][obs_id][cfg.INDEPENDENT_VARIABLES]:
|
|
1173
|
+
out_file.write(f"# {variable}: {pj[cfg.OBSERVATIONS][obs_id][cfg.INDEPENDENT_VARIABLES][variable]}\n")
|
|
1174
|
+
out_file.write("\n")
|
|
1175
|
+
|
|
1176
|
+
# one sequence for all subjects
|
|
1177
|
+
if not separated_subjects:
|
|
1178
|
+
out = events_to_behavioral_sequences_all_subj(
|
|
1179
|
+
pj, obs_id, parameters[cfg.SELECTED_SUBJECTS], parameters, behaviors_separator
|
|
1180
|
+
)
|
|
1181
|
+
if out:
|
|
1182
|
+
out_file.write(out + "\n")
|
|
1183
|
+
|
|
1184
|
+
# one sequence by subject
|
|
1185
|
+
if separated_subjects:
|
|
1186
|
+
# selected subjects
|
|
1187
|
+
for subject in parameters[cfg.SELECTED_SUBJECTS]:
|
|
1188
|
+
out_file.write(f"\n# {subject if subject else cfg.NO_FOCAL_SUBJECT}:\n")
|
|
1189
|
+
|
|
1190
|
+
if not timed:
|
|
1191
|
+
out = events_to_behavioral_sequences(pj, obs_id, subject, parameters, behaviors_separator)
|
|
1192
|
+
|
|
1193
|
+
if timed:
|
|
1194
|
+
out = events_to_timed_behavioral_sequences(pj, obs_id, subject, parameters, 0.001, behaviors_separator)
|
|
1195
|
+
|
|
1196
|
+
if out:
|
|
1197
|
+
out_file.write(out + "\n")
|
|
1198
|
+
|
|
1199
|
+
return True, ""
|
|
1200
|
+
|
|
1201
|
+
except Exception:
|
|
1202
|
+
error_type, error_file_name, error_lineno = util.error_info(sys.exc_info())
|
|
1203
|
+
return False, f"{error_type} {error_file_name} {error_lineno}"
|