boris-behav-obs 8.16.6__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 -40
- 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 +101 -49
- 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 +134 -0
- 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 +127 -36
- 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 +25 -17
- boris/time_budget_functions.py +169 -169
- boris/time_budget_widget.py +71 -86
- 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.6.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.6.dist-info/LICENSE.TXT +0 -674
- boris_behav_obs-8.16.6.dist-info/METADATA +0 -134
- boris_behav_obs-8.16.6.dist-info/RECORD +0 -106
- boris_behav_obs-8.16.6.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.6.dist-info → boris_behav_obs-9.7.1.dist-info}/top_level.txt +0 -0
boris/observations_list.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
|
|
|
7
7
|
This program is free software; you can redistribute it and/or modify
|
|
@@ -21,8 +21,8 @@ Copyright 2012-2023 Olivier Friard
|
|
|
21
21
|
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
|
-
from
|
|
25
|
-
from
|
|
24
|
+
from PySide6.QtGui import QColor
|
|
25
|
+
from PySide6.QtWidgets import (
|
|
26
26
|
QTableWidgetItem,
|
|
27
27
|
QLabel,
|
|
28
28
|
QLineEdit,
|
|
@@ -50,7 +50,10 @@ class MyTableWidgetItem(QTableWidgetItem):
|
|
|
50
50
|
|
|
51
51
|
# Qt uses a simple < check for sorting items, override this to use the sortKey
|
|
52
52
|
def __lt__(self, other):
|
|
53
|
-
|
|
53
|
+
if isinstance(self.sortKey, str) and isinstance(other.sortKey, str):
|
|
54
|
+
return self.sortKey.lower() < other.sortKey.lower()
|
|
55
|
+
else:
|
|
56
|
+
return self.sortKey < other.sortKey
|
|
54
57
|
|
|
55
58
|
|
|
56
59
|
class observationsList_widget(QDialog):
|
|
@@ -62,7 +65,6 @@ class observationsList_widget(QDialog):
|
|
|
62
65
|
not_paired: list = [],
|
|
63
66
|
parent=None,
|
|
64
67
|
):
|
|
65
|
-
|
|
66
68
|
super(observationsList_widget, self).__init__(parent)
|
|
67
69
|
|
|
68
70
|
self.data = data
|
|
@@ -122,7 +124,7 @@ class observationsList_widget(QDialog):
|
|
|
122
124
|
self.pbUnSelectAll.clicked.connect(lambda: self.pbSelection_clicked("unselect"))
|
|
123
125
|
hbox2.addWidget(self.pbUnSelectAll)
|
|
124
126
|
|
|
125
|
-
self.pbCancel = QPushButton(
|
|
127
|
+
self.pbCancel = QPushButton(cfg.CANCEL, clicked=self.pbCancel_clicked)
|
|
126
128
|
hbox2.addWidget(self.pbCancel)
|
|
127
129
|
|
|
128
130
|
self.pbOpen = QPushButton("Start", clicked=self.pbOpen_clicked)
|
|
@@ -134,7 +136,7 @@ class observationsList_widget(QDialog):
|
|
|
134
136
|
self.pbEdit = QPushButton("Edit", clicked=self.pbEdit_clicked)
|
|
135
137
|
hbox2.addWidget(self.pbEdit)
|
|
136
138
|
|
|
137
|
-
self.pbOk = QPushButton(
|
|
139
|
+
self.pbOk = QPushButton(cfg.OK, clicked=self.pbOk_clicked)
|
|
138
140
|
hbox2.addWidget(self.pbOk)
|
|
139
141
|
|
|
140
142
|
self.gridLayout.addLayout(hbox2, 3, 0, 1, 3)
|
|
@@ -156,12 +158,9 @@ class observationsList_widget(QDialog):
|
|
|
156
158
|
self.comboBox.addItems(header)
|
|
157
159
|
|
|
158
160
|
self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
|
159
|
-
self.label.setText(
|
|
160
|
-
f"{self.view.rowCount()} observation{'s' * (self.view.rowCount() > 1)}"
|
|
161
|
-
)
|
|
161
|
+
self.label.setText(f"{self.view.rowCount()} observation{'s' * (self.view.rowCount() > 1)}")
|
|
162
162
|
|
|
163
163
|
def view_doubleClicked(self, index):
|
|
164
|
-
|
|
165
164
|
if self.mode == cfg.MULTIPLE:
|
|
166
165
|
return
|
|
167
166
|
|
|
@@ -187,10 +186,10 @@ class observationsList_widget(QDialog):
|
|
|
187
186
|
"""
|
|
188
187
|
select or unselect all filtered observations
|
|
189
188
|
"""
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
189
|
+
if mode == "select":
|
|
190
|
+
self.view.selectAll()
|
|
191
|
+
if mode == "unselect":
|
|
192
|
+
self.view.clearSelection()
|
|
194
193
|
|
|
195
194
|
def pbCancel_clicked(self):
|
|
196
195
|
self.close()
|
|
@@ -208,7 +207,6 @@ class observationsList_widget(QDialog):
|
|
|
208
207
|
self.done(4)
|
|
209
208
|
|
|
210
209
|
def set_item(self, r, c):
|
|
211
|
-
|
|
212
210
|
if self.column_type[c] == cfg.NUMERIC:
|
|
213
211
|
try:
|
|
214
212
|
item = MyTableWidgetItem(self.data[r][c], float(self.data[r][c]))
|
|
@@ -244,60 +242,60 @@ class observationsList_widget(QDialog):
|
|
|
244
242
|
def not_in(s, lst):
|
|
245
243
|
return s not in lst
|
|
246
244
|
|
|
247
|
-
def equal(s,
|
|
248
|
-
|
|
249
|
-
if type(
|
|
250
|
-
return
|
|
245
|
+
def equal(s, x):
|
|
246
|
+
x_num, s_num = str2float(x), str2float(s)
|
|
247
|
+
if type(x_num) == type(s_num):
|
|
248
|
+
return x_num == s_num
|
|
251
249
|
else:
|
|
252
|
-
return
|
|
250
|
+
return x == s
|
|
253
251
|
|
|
254
|
-
def not_equal(s,
|
|
255
|
-
|
|
256
|
-
if type(
|
|
257
|
-
return
|
|
252
|
+
def not_equal(s, x):
|
|
253
|
+
x_num, s_num = str2float(x), str2float(s)
|
|
254
|
+
if type(x_num) == type(s_num):
|
|
255
|
+
return x_num != s_num
|
|
258
256
|
else:
|
|
259
|
-
return
|
|
257
|
+
return x != s
|
|
260
258
|
|
|
261
|
-
def gt(s,
|
|
262
|
-
|
|
263
|
-
if type(
|
|
264
|
-
return
|
|
259
|
+
def gt(s, x):
|
|
260
|
+
x_num, s_num = str2float(x), str2float(s)
|
|
261
|
+
if type(x_num) == type(s_num):
|
|
262
|
+
return x_num > s_num
|
|
265
263
|
else:
|
|
266
|
-
return
|
|
264
|
+
return x > s
|
|
267
265
|
|
|
268
|
-
def lt(s,
|
|
269
|
-
|
|
270
|
-
if type(
|
|
271
|
-
return
|
|
266
|
+
def lt(s, x):
|
|
267
|
+
x_num, s_num = str2float(x), str2float(s)
|
|
268
|
+
if type(x_num) == type(s_num):
|
|
269
|
+
return x_num < s_num
|
|
272
270
|
else:
|
|
273
|
-
return
|
|
271
|
+
return x < s
|
|
274
272
|
|
|
275
|
-
def gt_or_equal(s,
|
|
276
|
-
|
|
277
|
-
if type(
|
|
278
|
-
return
|
|
273
|
+
def gt_or_equal(s, x):
|
|
274
|
+
x_num, s_num = str2float(x), str2float(s)
|
|
275
|
+
if type(x_num) == type(s_num):
|
|
276
|
+
return x_num >= s_num
|
|
279
277
|
else:
|
|
280
|
-
return
|
|
278
|
+
return x >= s
|
|
281
279
|
|
|
282
|
-
def lt_or_equal(s,
|
|
283
|
-
|
|
284
|
-
if type(
|
|
285
|
-
return
|
|
280
|
+
def lt_or_equal(s, x):
|
|
281
|
+
x_num, s_num = str2float(x), str2float(s)
|
|
282
|
+
if type(x_num) == type(s_num):
|
|
283
|
+
return x_num <= s_num
|
|
286
284
|
else:
|
|
287
|
-
return
|
|
285
|
+
return x <= s
|
|
288
286
|
|
|
289
|
-
def between(s,
|
|
287
|
+
def between(s, x):
|
|
290
288
|
if len(s.split(" AND ")) != 2:
|
|
291
289
|
return None
|
|
292
290
|
s1, s2 = s.split(" AND ")
|
|
293
291
|
s1_num, s2_num = str2float(s1), str2float(s2)
|
|
294
292
|
if type(s1_num) != type(s2_num):
|
|
295
293
|
return None
|
|
296
|
-
|
|
297
|
-
if type(s1_num) == type(
|
|
298
|
-
return
|
|
294
|
+
x_num = str2float(x)
|
|
295
|
+
if type(s1_num) == type(x_num):
|
|
296
|
+
return s1_num <= x_num <= s2_num
|
|
299
297
|
else:
|
|
300
|
-
return
|
|
298
|
+
return s1 <= x <= s2
|
|
301
299
|
|
|
302
300
|
if not self.lineEdit.text():
|
|
303
301
|
self.view.setRowCount(len(self.data))
|
|
@@ -307,7 +305,6 @@ class observationsList_widget(QDialog):
|
|
|
307
305
|
self.view.setItem(r, c, self.set_item(r, c))
|
|
308
306
|
|
|
309
307
|
else:
|
|
310
|
-
|
|
311
308
|
if self.cbLogic.currentText() == "contains":
|
|
312
309
|
logic = in_
|
|
313
310
|
if self.cbLogic.currentText() == "does not contain":
|
|
@@ -334,11 +331,7 @@ class observationsList_widget(QDialog):
|
|
|
334
331
|
if logic(search, row[self.comboBox.currentIndex()].upper()):
|
|
335
332
|
self.view.setRowCount(self.view.rowCount() + 1)
|
|
336
333
|
for c, _ in enumerate(row):
|
|
337
|
-
self.view.setItem(
|
|
338
|
-
self.view.rowCount() - 1, c, self.set_item(r, c)
|
|
339
|
-
)
|
|
334
|
+
self.view.setItem(self.view.rowCount() - 1, c, self.set_item(r, c))
|
|
340
335
|
except Exception:
|
|
341
336
|
pass
|
|
342
|
-
self.label.setText(
|
|
343
|
-
f"{self.view.rowCount()} observation{'s' * (self.view.rowCount() > 1)}"
|
|
344
|
-
)
|
|
337
|
+
self.label.setText(f"{self.view.rowCount()} observation{'s' * (self.view.rowCount() > 1)}")
|
boris/otx_parser.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
BORIS
|
|
3
3
|
Behavioral Observation Research Interactive Software
|
|
4
|
-
Copyright 2012-
|
|
4
|
+
Copyright 2012-2025 Olivier Friard
|
|
5
5
|
|
|
6
6
|
This file is part of BORIS.
|
|
7
7
|
|
|
@@ -30,15 +30,16 @@ import re
|
|
|
30
30
|
import zipfile
|
|
31
31
|
import pathlib as pl
|
|
32
32
|
from xml.dom import minidom
|
|
33
|
-
import
|
|
33
|
+
import logging
|
|
34
|
+
from typing import Tuple
|
|
34
35
|
|
|
35
36
|
try:
|
|
36
37
|
from . import config as cfg
|
|
37
|
-
except:
|
|
38
|
+
except Exception:
|
|
38
39
|
import config as cfg
|
|
39
40
|
|
|
40
41
|
|
|
41
|
-
def otx_to_boris(file_path: str) -> dict:
|
|
42
|
+
def otx_to_boris(file_path: str) -> Tuple[dict, list]:
|
|
42
43
|
"""
|
|
43
44
|
convert otx/otb/odx file in a BORIS project
|
|
44
45
|
|
|
@@ -49,6 +50,7 @@ def otx_to_boris(file_path: str) -> dict:
|
|
|
49
50
|
|
|
50
51
|
Returns:
|
|
51
52
|
dict: BORIS project
|
|
53
|
+
list: list of errors during importation
|
|
52
54
|
"""
|
|
53
55
|
|
|
54
56
|
if pl.Path(file_path).suffix == ".otb":
|
|
@@ -57,26 +59,27 @@ def otx_to_boris(file_path: str) -> dict:
|
|
|
57
59
|
if files_list:
|
|
58
60
|
try:
|
|
59
61
|
file_zip.extract(files_list[0])
|
|
60
|
-
except:
|
|
61
|
-
return {"
|
|
62
|
+
except Exception:
|
|
63
|
+
return {"fatal": True}, ["Error when extracting file from OTB"]
|
|
62
64
|
else:
|
|
63
|
-
return {"
|
|
65
|
+
return {"fatal": True}, ["Error when extracting file"]
|
|
64
66
|
|
|
65
67
|
try:
|
|
66
68
|
xmldoc = minidom.parse(files_list[0])
|
|
67
|
-
except:
|
|
68
|
-
return {"
|
|
69
|
+
except Exception:
|
|
70
|
+
return {"fatal": True}, ["XML parsing error"]
|
|
69
71
|
|
|
70
72
|
elif pl.Path(file_path).suffix in (".odx", ".otx"):
|
|
71
73
|
try:
|
|
72
74
|
xmldoc = minidom.parse(file_path)
|
|
73
|
-
except:
|
|
74
|
-
return {"
|
|
75
|
+
except Exception:
|
|
76
|
+
return {"fatal": True}, ["XML parsing error"]
|
|
75
77
|
|
|
76
78
|
else:
|
|
77
|
-
return {"
|
|
79
|
+
return {"fatal": True}, ["The file must be in OTB, OTX or ODX format"]
|
|
78
80
|
|
|
79
|
-
flag_long_key = False
|
|
81
|
+
flag_long_key: bool = False
|
|
82
|
+
error_list: list = []
|
|
80
83
|
|
|
81
84
|
# metadata
|
|
82
85
|
for item in xmldoc.getElementsByTagName("MET_METADATA"):
|
|
@@ -86,22 +89,18 @@ def otx_to_boris(file_path: str) -> dict:
|
|
|
86
89
|
except Exception:
|
|
87
90
|
project_name = ""
|
|
88
91
|
try:
|
|
89
|
-
project_description = re.sub(
|
|
90
|
-
"<[^>]*>", "", metadata.getElementsByTagName("MET_PROJECT_DESCRIPTION")[0].toxml()
|
|
91
|
-
)
|
|
92
|
+
project_description = re.sub("<[^>]*>", "", metadata.getElementsByTagName("MET_PROJECT_DESCRIPTION")[0].toxml())
|
|
92
93
|
except Exception:
|
|
93
94
|
project_description = ""
|
|
94
95
|
|
|
95
96
|
try:
|
|
96
|
-
project_creation_date = re.sub(
|
|
97
|
-
"<[^>]*>", "", metadata.getElementsByTagName("MET_CREATION_DATETIME")[0].toxml()
|
|
98
|
-
)
|
|
97
|
+
project_creation_date = re.sub("<[^>]*>", "", metadata.getElementsByTagName("MET_CREATION_DATETIME")[0].toxml())
|
|
99
98
|
except Exception:
|
|
100
99
|
project_creation_date = ""
|
|
101
100
|
|
|
102
101
|
# modifiers
|
|
103
102
|
modifiers: dict = {}
|
|
104
|
-
modifiers_set = {}
|
|
103
|
+
# modifiers_set = {}
|
|
105
104
|
itemlist = xmldoc.getElementsByTagName("CDS_MODIFIER")
|
|
106
105
|
for item in itemlist:
|
|
107
106
|
modif = minidom.parseString(item.toxml())
|
|
@@ -112,16 +111,16 @@ def otx_to_boris(file_path: str) -> dict:
|
|
|
112
111
|
|
|
113
112
|
try:
|
|
114
113
|
modif_parent_id = re.sub("<[^>]*>", "", modif.getElementsByTagName("CDS_ELE_PARENT_ID")[0].toxml())
|
|
115
|
-
except:
|
|
114
|
+
except Exception:
|
|
116
115
|
modif_parent_id = ""
|
|
117
116
|
|
|
118
117
|
try:
|
|
119
118
|
description = re.sub("<[^>]*>", "", modif.getElementsByTagName("CDS_ELE_DESCRIPTION")[0].toxml())
|
|
120
|
-
except:
|
|
119
|
+
except Exception:
|
|
121
120
|
description = ""
|
|
122
121
|
try:
|
|
123
122
|
key = re.sub("<[^>]*>", "", modif.getElementsByTagName("CDS_ELE_START_KEYCODE")[0].toxml())
|
|
124
|
-
except:
|
|
123
|
+
except Exception:
|
|
125
124
|
key = ""
|
|
126
125
|
|
|
127
126
|
if modif_parent_id:
|
|
@@ -132,8 +131,7 @@ def otx_to_boris(file_path: str) -> dict:
|
|
|
132
131
|
flag_long_key = True
|
|
133
132
|
modifiers[modif_id] = {"set_name": modif_code, "key": key, "description": description, "values": []}
|
|
134
133
|
|
|
135
|
-
|
|
136
|
-
pprint.pprint(modifiers)
|
|
134
|
+
logging.debug(modifiers)
|
|
137
135
|
|
|
138
136
|
# connect modifiers to behaviors
|
|
139
137
|
connections: dict = {}
|
|
@@ -143,8 +141,7 @@ def otx_to_boris(file_path: str) -> dict:
|
|
|
143
141
|
connections[item.attributes["CDS_ELEMENT_ID"].value] = []
|
|
144
142
|
connections[item.attributes["CDS_ELEMENT_ID"].value].append(item.attributes["CDS_MODIFIER_ID"].value)
|
|
145
143
|
|
|
146
|
-
|
|
147
|
-
pprint.pprint(connections)
|
|
144
|
+
logging.debug(connections)
|
|
148
145
|
|
|
149
146
|
# behaviors
|
|
150
147
|
behaviors: dict = {}
|
|
@@ -161,26 +158,26 @@ def otx_to_boris(file_path: str) -> dict:
|
|
|
161
158
|
|
|
162
159
|
try:
|
|
163
160
|
description = re.sub("<[^>]*>", "", behav.getElementsByTagName("CDS_ELE_DESCRIPTION")[0].toxml())
|
|
164
|
-
except:
|
|
161
|
+
except Exception:
|
|
165
162
|
description = ""
|
|
166
163
|
try:
|
|
167
164
|
key = re.sub("<[^>]*>", "", behav.getElementsByTagName("CDS_ELE_START_KEYCODE")[0].toxml())
|
|
168
|
-
except:
|
|
165
|
+
except Exception:
|
|
169
166
|
key = ""
|
|
170
167
|
|
|
171
168
|
try:
|
|
172
169
|
stop_key = re.sub("<[^>]*>", "", behav.getElementsByTagName("CDS_ELE_STOP_KEYCODE")[0].toxml())
|
|
173
|
-
except:
|
|
170
|
+
except Exception:
|
|
174
171
|
stop_key = ""
|
|
175
172
|
|
|
176
173
|
try:
|
|
177
174
|
parent_name = re.sub("<[^>]*>", "", behav.getElementsByTagName("CDS_ELE_PARENT_NAME")[0].toxml())
|
|
178
|
-
except:
|
|
175
|
+
except Exception:
|
|
179
176
|
parent_name = ""
|
|
180
177
|
|
|
181
178
|
try:
|
|
182
179
|
mutually_exclusive = re.sub("<[^>]*>", "", behav.getElementsByTagName("CDS_ELE_MUT_EXCLUSIVE")[0].toxml())
|
|
183
|
-
except:
|
|
180
|
+
except Exception:
|
|
184
181
|
mutually_exclusive = ""
|
|
185
182
|
|
|
186
183
|
if mutually_exclusive == "Y" and parent_name:
|
|
@@ -192,7 +189,6 @@ def otx_to_boris(file_path: str) -> dict:
|
|
|
192
189
|
modifier_sets = []
|
|
193
190
|
|
|
194
191
|
if parent_name: # behavior
|
|
195
|
-
|
|
196
192
|
if (not key or len(key) > 1) and stop_key:
|
|
197
193
|
key = stop_key
|
|
198
194
|
|
|
@@ -211,7 +207,6 @@ def otx_to_boris(file_path: str) -> dict:
|
|
|
211
207
|
behaviors_list.append(behav_code)
|
|
212
208
|
|
|
213
209
|
else: # behavioral category
|
|
214
|
-
|
|
215
210
|
behav_category.append(behav_code)
|
|
216
211
|
|
|
217
212
|
behaviors_boris: dict = {}
|
|
@@ -241,7 +236,7 @@ def otx_to_boris(file_path: str) -> dict:
|
|
|
241
236
|
"description": modifiers[modif_key]["description"],
|
|
242
237
|
}
|
|
243
238
|
|
|
244
|
-
|
|
239
|
+
logging.debug(behaviors_boris)
|
|
245
240
|
|
|
246
241
|
# subjects
|
|
247
242
|
subjects = {}
|
|
@@ -251,11 +246,11 @@ def otx_to_boris(file_path: str) -> dict:
|
|
|
251
246
|
subject_name = re.sub("<[^>]*>", "", subject.getElementsByTagName("CDS_ELE_NAME")[0].toxml())
|
|
252
247
|
try:
|
|
253
248
|
key = re.sub("<[^>]*>", "", subject.getElementsByTagName("CDS_ELE_START_KEYCODE")[0].toxml())
|
|
254
|
-
except:
|
|
249
|
+
except Exception:
|
|
255
250
|
key = ""
|
|
256
251
|
try:
|
|
257
252
|
parent_name = re.sub("<[^>]*>", "", subject.getElementsByTagName("CDS_ELE_PARENT_NAME")[0].toxml())
|
|
258
|
-
except:
|
|
253
|
+
except Exception:
|
|
259
254
|
parent_name = ""
|
|
260
255
|
|
|
261
256
|
if parent_name:
|
|
@@ -268,7 +263,6 @@ def otx_to_boris(file_path: str) -> dict:
|
|
|
268
263
|
variables = {}
|
|
269
264
|
itemlist = xmldoc.getElementsByTagName("VL_VARIABLE")
|
|
270
265
|
for item in itemlist:
|
|
271
|
-
|
|
272
266
|
variable = minidom.parseString(item.toxml())
|
|
273
267
|
|
|
274
268
|
variable_label = re.sub("<[^>]*>", "", variable.getElementsByTagName("VL_LABEL")[0].toxml())
|
|
@@ -291,7 +285,7 @@ def otx_to_boris(file_path: str) -> dict:
|
|
|
291
285
|
|
|
292
286
|
try:
|
|
293
287
|
variable_description = re.sub("<[^>]*>", "", modif.getElementsByTagName("VL_DESCRIPTION")[0].toxml())
|
|
294
|
-
except:
|
|
288
|
+
except Exception:
|
|
295
289
|
variable_description = ""
|
|
296
290
|
|
|
297
291
|
try:
|
|
@@ -301,7 +295,7 @@ def otx_to_boris(file_path: str) -> dict:
|
|
|
301
295
|
values_list.append(re.sub("<[^>]*>", "", value.toxml()))
|
|
302
296
|
values_str = ",".join(values_list)
|
|
303
297
|
|
|
304
|
-
except:
|
|
298
|
+
except Exception:
|
|
305
299
|
values_str = ""
|
|
306
300
|
|
|
307
301
|
variables[variable_id] = {
|
|
@@ -355,24 +349,23 @@ def otx_to_boris(file_path: str) -> dict:
|
|
|
355
349
|
OBS_EVENT_LOGS = OBS_OBSERVATION.getElementsByTagName("OBS_EVENT_LOGS")[0]
|
|
356
350
|
|
|
357
351
|
for OBS_EVENT_LOG in OBS_EVENT_LOGS.getElementsByTagName("OBS_EVENT_LOG"):
|
|
358
|
-
|
|
359
352
|
CREATION_DATETIME = OBS_EVENT_LOG.getAttribute("CREATION_DATETIME")
|
|
360
353
|
|
|
361
|
-
CREATION_DATETIME = CREATION_DATETIME.replace(" ", "T").split(".")[0]
|
|
354
|
+
CREATION_DATETIME = CREATION_DATETIME.replace(" ", "T") # .split(".")[0]
|
|
362
355
|
|
|
363
|
-
|
|
356
|
+
logging.debug(f"{CREATION_DATETIME=}") # ex: 2022-05-18 10:04:09.474512"""
|
|
364
357
|
|
|
365
358
|
project[cfg.OBSERVATIONS][obs_id]["date"] = CREATION_DATETIME
|
|
366
359
|
|
|
367
360
|
for event in OBS_EVENT_LOG.getElementsByTagName("OBS_EVENT"):
|
|
368
|
-
|
|
369
361
|
OBS_EVENT_TIMESTAMP = event.getElementsByTagName("OBS_EVENT_TIMESTAMP")[0].childNodes[0].data
|
|
370
362
|
|
|
371
|
-
day_timestamp = dt.datetime.strptime(OBS_EVENT_TIMESTAMP.split(" ")[0], "%Y-%m-%d").timestamp()
|
|
372
|
-
|
|
373
363
|
full_timestamp = dt.datetime.strptime(OBS_EVENT_TIMESTAMP, "%Y-%m-%d %H:%M:%S.%f").timestamp()
|
|
364
|
+
logging.debug(f"{full_timestamp=}")
|
|
374
365
|
|
|
375
|
-
|
|
366
|
+
# day_timestamp = dt.datetime.strptime(OBS_EVENT_TIMESTAMP.split(" ")[0], "%Y-%m-%d").timestamp()
|
|
367
|
+
# timestamp = dec(str(round(full_timestamp - day_timestamp, 3)))
|
|
368
|
+
timestamp = dec(full_timestamp).quantize(dec(".001"))
|
|
376
369
|
|
|
377
370
|
try:
|
|
378
371
|
OBS_EVENT_SUBJECT = event.getElementsByTagName("OBS_EVENT_SUBJECT")[0].getAttribute("NAME")
|
|
@@ -380,24 +373,34 @@ def otx_to_boris(file_path: str) -> dict:
|
|
|
380
373
|
OBS_EVENT_SUBJECT = ""
|
|
381
374
|
|
|
382
375
|
OBS_EVENT_BEHAVIOR = event.getElementsByTagName("OBS_EVENT_BEHAVIOR")[0].getAttribute("NAME")
|
|
376
|
+
logging.debug(f"{OBS_EVENT_BEHAVIOR=}")
|
|
377
|
+
if not OBS_EVENT_BEHAVIOR:
|
|
378
|
+
logging.warning(f"Behavior missing in observation {obs_id} at {timestamp}")
|
|
379
|
+
error_list.append(f"Behavior missing in observation {obs_id} at {timestamp}")
|
|
380
|
+
continue
|
|
383
381
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
382
|
+
# modifier
|
|
383
|
+
try:
|
|
384
|
+
OBS_EVENT_BEHAVIOR_MODIFIER = (
|
|
385
|
+
event.getElementsByTagName("OBS_EVENT_BEHAVIOR")[0]
|
|
386
|
+
.getElementsByTagName("OBS_EVENT_BEHAVIOR_MODIFIER")[0]
|
|
387
|
+
.childNodes[0]
|
|
388
|
+
.data
|
|
389
|
+
)
|
|
390
|
+
except Exception:
|
|
391
|
+
OBS_EVENT_BEHAVIOR_MODIFIER: str = ""
|
|
390
392
|
|
|
393
|
+
# comment
|
|
391
394
|
try:
|
|
392
|
-
OBS_EVENT_COMMENT = event.getElementsByTagName("OBS_EVENT_COMMENT")[0].childNodes[0].data
|
|
395
|
+
OBS_EVENT_COMMENT: str = event.getElementsByTagName("OBS_EVENT_COMMENT")[0].childNodes[0].data
|
|
393
396
|
except Exception:
|
|
394
|
-
OBS_EVENT_COMMENT = ""
|
|
397
|
+
OBS_EVENT_COMMENT: str = ""
|
|
395
398
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
399
|
+
logging.debug(f"{timestamp=}")
|
|
400
|
+
logging.debug(f"{OBS_EVENT_SUBJECT=}")
|
|
401
|
+
logging.debug(f"{OBS_EVENT_BEHAVIOR=}")
|
|
402
|
+
logging.debug(f"{OBS_EVENT_BEHAVIOR_MODIFIER=}")
|
|
403
|
+
logging.debug(f"{OBS_EVENT_COMMENT=}")
|
|
401
404
|
|
|
402
405
|
project[cfg.OBSERVATIONS][obs_id][cfg.EVENTS].append(
|
|
403
406
|
[
|
|
@@ -409,10 +412,6 @@ def otx_to_boris(file_path: str) -> dict:
|
|
|
409
412
|
]
|
|
410
413
|
)
|
|
411
414
|
|
|
412
|
-
# print(80 * "-")
|
|
413
|
-
|
|
414
|
-
# print(80 * "=")
|
|
415
|
-
|
|
416
415
|
project[cfg.PROJECT_NAME] = project_name
|
|
417
416
|
project[cfg.PROJECT_DATE] = project_creation_date.replace(" ", "T")
|
|
418
417
|
project[cfg.ETHOGRAM] = behaviors_boris
|
|
@@ -422,15 +421,22 @@ def otx_to_boris(file_path: str) -> dict:
|
|
|
422
421
|
project[cfg.INDEPENDENT_VARIABLES] = variables_boris
|
|
423
422
|
|
|
424
423
|
if flag_long_key:
|
|
425
|
-
|
|
424
|
+
error_list.append("The keys longer than one char were deleted.")
|
|
425
|
+
logging.debug("The keys longer than one char were deleted.")
|
|
426
426
|
|
|
427
|
-
return project
|
|
427
|
+
return project, error_list
|
|
428
428
|
|
|
429
429
|
|
|
430
430
|
if __name__ == "__main__":
|
|
431
431
|
import sys
|
|
432
432
|
import pprint
|
|
433
433
|
|
|
434
|
-
|
|
434
|
+
logging.basicConfig(
|
|
435
|
+
format="%(asctime)s,%(msecs)d %(module)s l.%(lineno)d %(levelname)s %(message)s",
|
|
436
|
+
datefmt="%H:%M:%S",
|
|
437
|
+
level=logging.DEBUG,
|
|
438
|
+
)
|
|
439
|
+
project, errors = otx_to_boris(sys.argv[1])
|
|
435
440
|
|
|
436
|
-
|
|
441
|
+
pprint.pprint(project)
|
|
442
|
+
pprint.pprint(errors)
|