boris-behav-obs 8.12__py3-none-any.whl → 9.7.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of boris-behav-obs might be problematic. Click here for more details.

Files changed (128) hide show
  1. boris/__init__.py +1 -1
  2. boris/__main__.py +1 -1
  3. boris/about.py +28 -39
  4. boris/add_modifier.py +122 -109
  5. boris/add_modifier_ui.py +239 -135
  6. boris/advanced_event_filtering.py +81 -45
  7. boris/analysis_plugins/__init__.py +0 -0
  8. boris/analysis_plugins/_latency.py +59 -0
  9. boris/analysis_plugins/irr_cohen_kappa.py +109 -0
  10. boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
  11. boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
  12. boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
  13. boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
  14. boris/analysis_plugins/number_of_occurences.py +22 -0
  15. boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
  16. boris/analysis_plugins/time_budget.py +61 -0
  17. boris/behav_coding_map_creator.py +228 -229
  18. boris/behavior_binary_table.py +33 -50
  19. boris/behaviors_coding_map.py +17 -18
  20. boris/boris_cli.py +6 -25
  21. boris/cmd_arguments.py +12 -1
  22. boris/coding_pad.py +42 -49
  23. boris/config.py +141 -65
  24. boris/config_file.py +58 -67
  25. boris/connections.py +107 -61
  26. boris/converters.py +13 -37
  27. boris/converters_ui.py +187 -110
  28. boris/cooccurence.py +250 -0
  29. boris/core.py +2373 -1786
  30. boris/core_qrc.py +15895 -10743
  31. boris/core_ui.py +943 -798
  32. boris/db_functions.py +17 -42
  33. boris/dev.py +109 -8
  34. boris/dialog.py +482 -236
  35. boris/duration_widget.py +9 -14
  36. boris/edit_event.py +61 -31
  37. boris/edit_event_ui.py +208 -97
  38. boris/event_operations.py +408 -293
  39. boris/events_cursor.py +25 -17
  40. boris/events_snapshots.py +36 -82
  41. boris/exclusion_matrix.py +4 -9
  42. boris/export_events.py +184 -223
  43. boris/export_observation.py +74 -100
  44. boris/external_processes.py +123 -98
  45. boris/geometric_measurement.py +644 -290
  46. boris/gui_utilities.py +91 -14
  47. boris/image_overlay.py +4 -4
  48. boris/import_observations.py +190 -98
  49. boris/ipc_mpv.py +325 -0
  50. boris/irr.py +20 -57
  51. boris/latency.py +31 -24
  52. boris/measurement_widget.py +14 -18
  53. boris/media_file.py +17 -19
  54. boris/menu_options.py +17 -6
  55. boris/modifier_coding_map_creator.py +1013 -0
  56. boris/modifiers_coding_map.py +7 -9
  57. boris/mpv.py +1 -0
  58. boris/mpv2.py +732 -705
  59. boris/observation.py +533 -221
  60. boris/observation_operations.py +1025 -390
  61. boris/observation_ui.py +572 -362
  62. boris/observations_list.py +71 -53
  63. boris/otx_parser.py +74 -68
  64. boris/param_panel.py +31 -16
  65. boris/param_panel_ui.py +254 -138
  66. boris/player_dock_widget.py +90 -60
  67. boris/plot_data_module.py +25 -33
  68. boris/plot_events.py +127 -90
  69. boris/plot_events_rt.py +17 -31
  70. boris/plot_spectrogram_rt.py +95 -30
  71. boris/plot_waveform_rt.py +32 -21
  72. boris/plugins.py +431 -0
  73. boris/portion/__init__.py +18 -8
  74. boris/portion/const.py +35 -18
  75. boris/portion/dict.py +5 -5
  76. boris/portion/func.py +2 -2
  77. boris/portion/interval.py +21 -41
  78. boris/portion/io.py +41 -32
  79. boris/preferences.py +306 -83
  80. boris/preferences_ui.py +684 -227
  81. boris/project.py +448 -293
  82. boris/project_functions.py +671 -238
  83. boris/project_import_export.py +213 -222
  84. boris/project_ui.py +674 -438
  85. boris/qrc_boris.py +6 -3
  86. boris/qrc_boris5.py +6 -3
  87. boris/select_modifiers.py +74 -48
  88. boris/select_observations.py +20 -198
  89. boris/select_subj_behav.py +67 -39
  90. boris/state_events.py +52 -35
  91. boris/subjects_pad.py +6 -9
  92. boris/synthetic_time_budget.py +45 -28
  93. boris/time_budget_functions.py +171 -171
  94. boris/time_budget_widget.py +84 -114
  95. boris/transitions.py +41 -47
  96. boris/utilities.py +627 -236
  97. boris/version.py +3 -3
  98. boris/video_equalizer.py +16 -14
  99. boris/video_equalizer_ui.py +199 -130
  100. boris/video_operations.py +95 -29
  101. boris/view_df.py +104 -0
  102. boris/view_df_ui.py +75 -0
  103. boris/write_event.py +538 -0
  104. boris_behav_obs-9.7.6.dist-info/METADATA +139 -0
  105. boris_behav_obs-9.7.6.dist-info/RECORD +109 -0
  106. {boris_behav_obs-8.12.dist-info → boris_behav_obs-9.7.6.dist-info}/WHEEL +1 -1
  107. boris_behav_obs-9.7.6.dist-info/entry_points.txt +2 -0
  108. boris/README.TXT +0 -22
  109. boris/add_modifier.ui +0 -323
  110. boris/converters.ui +0 -289
  111. boris/core.qrc +0 -36
  112. boris/core.ui +0 -1556
  113. boris/edit_event.ui +0 -233
  114. boris/icons/logo_eye.ico +0 -0
  115. boris/map_creator.py +0 -850
  116. boris/observation.ui +0 -814
  117. boris/param_panel.ui +0 -379
  118. boris/preferences.ui +0 -537
  119. boris/project.ui +0 -1069
  120. boris/project_server.py +0 -236
  121. boris/vlc.py +0 -10343
  122. boris/vlc_local.py +0 -90
  123. boris_behav_obs-8.12.dist-info/LICENSE.TXT +0 -674
  124. boris_behav_obs-8.12.dist-info/METADATA +0 -128
  125. boris_behav_obs-8.12.dist-info/RECORD +0 -108
  126. boris_behav_obs-8.12.dist-info/entry_points.txt +0 -3
  127. {boris → boris_behav_obs-9.7.6.dist-info/licenses}/LICENSE.TXT +0 -0
  128. {boris_behav_obs-8.12.dist-info → boris_behav_obs-9.7.6.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2023 Olivier Friard
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 PyQt5.QtGui import QColor
25
- from PyQt5.QtWidgets import (
24
+ from PySide6.QtGui import QColor
25
+ from PySide6.QtWidgets import (
26
26
  QTableWidgetItem,
27
27
  QLabel,
28
28
  QLineEdit,
@@ -50,12 +50,21 @@ 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
- return self.sortKey < other.sortKey
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):
57
- def __init__(self, data: list, header: list, column_type: list, not_paired: list = [], parent=None):
58
-
60
+ def __init__(
61
+ self,
62
+ data: list,
63
+ header: list,
64
+ column_type: list,
65
+ not_paired: list = [],
66
+ parent=None,
67
+ ):
59
68
  super(observationsList_widget, self).__init__(parent)
60
69
 
61
70
  self.data = data
@@ -78,7 +87,17 @@ class observationsList_widget(QDialog):
78
87
 
79
88
  self.cbLogic = QComboBox(self)
80
89
  self.cbLogic.addItems(
81
- ["contains", "does not contain", "=", "!=", ">", "<", ">=", "<=", "between (use and to separate terms)"]
90
+ [
91
+ "contains",
92
+ "does not contain",
93
+ "=",
94
+ "!=",
95
+ ">",
96
+ "<",
97
+ ">=",
98
+ "<=",
99
+ "between (use and to separate terms)",
100
+ ]
82
101
  )
83
102
  self.cbLogic.currentIndexChanged.connect(self.view_filter)
84
103
 
@@ -105,7 +124,7 @@ class observationsList_widget(QDialog):
105
124
  self.pbUnSelectAll.clicked.connect(lambda: self.pbSelection_clicked("unselect"))
106
125
  hbox2.addWidget(self.pbUnSelectAll)
107
126
 
108
- self.pbCancel = QPushButton("Cancel", clicked=self.pbCancel_clicked)
127
+ self.pbCancel = QPushButton(cfg.CANCEL, clicked=self.pbCancel_clicked)
109
128
  hbox2.addWidget(self.pbCancel)
110
129
 
111
130
  self.pbOpen = QPushButton("Start", clicked=self.pbOpen_clicked)
@@ -117,7 +136,7 @@ class observationsList_widget(QDialog):
117
136
  self.pbEdit = QPushButton("Edit", clicked=self.pbEdit_clicked)
118
137
  hbox2.addWidget(self.pbEdit)
119
138
 
120
- self.pbOk = QPushButton("OK", clicked=self.pbOk_clicked)
139
+ self.pbOk = QPushButton(cfg.OK, clicked=self.pbOk_clicked)
121
140
  hbox2.addWidget(self.pbOk)
122
141
 
123
142
  self.gridLayout.addLayout(hbox2, 3, 0, 1, 3)
@@ -142,7 +161,6 @@ class observationsList_widget(QDialog):
142
161
  self.label.setText(f"{self.view.rowCount()} observation{'s' * (self.view.rowCount() > 1)}")
143
162
 
144
163
  def view_doubleClicked(self, index):
145
-
146
164
  if self.mode == cfg.MULTIPLE:
147
165
  return
148
166
 
@@ -155,7 +173,9 @@ class observationsList_widget(QDialog):
155
173
  return
156
174
 
157
175
  response = dialog.MessageDialog(
158
- cfg.programName, "What do you want to do with this observation?", list(commands_index.keys()) + [cfg.CANCEL]
176
+ cfg.programName,
177
+ "What do you want to do with this observation?",
178
+ list(commands_index.keys()) + [cfg.CANCEL],
159
179
  )
160
180
  if response == cfg.CANCEL:
161
181
  return
@@ -166,10 +186,10 @@ class observationsList_widget(QDialog):
166
186
  """
167
187
  select or unselect all filtered observations
168
188
  """
169
-
170
- for idx in range(self.view.rowCount()):
171
- table_item = self.view.item(idx, 0)
172
- table_item.setSelected(mode == "select")
189
+ if mode == "select":
190
+ self.view.selectAll()
191
+ if mode == "unselect":
192
+ self.view.clearSelection()
173
193
 
174
194
  def pbCancel_clicked(self):
175
195
  self.close()
@@ -187,7 +207,6 @@ class observationsList_widget(QDialog):
187
207
  self.done(4)
188
208
 
189
209
  def set_item(self, r, c):
190
-
191
210
  if self.column_type[c] == cfg.NUMERIC:
192
211
  try:
193
212
  item = MyTableWidgetItem(self.data[r][c], float(self.data[r][c]))
@@ -208,7 +227,7 @@ class observationsList_widget(QDialog):
208
227
  filter
209
228
  """
210
229
 
211
- def str2float(s):
230
+ def str2float(s: str):
212
231
  """
213
232
  convert str in float or return str
214
233
  """
@@ -223,60 +242,60 @@ class observationsList_widget(QDialog):
223
242
  def not_in(s, lst):
224
243
  return s not in lst
225
244
 
226
- def equal(s, l):
227
- l_num, s_num = str2float(l), str2float(s)
228
- if type(l_num) == type(s_num):
229
- return l_num == s_num
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
230
249
  else:
231
- return l == s
250
+ return x == s
232
251
 
233
- def not_equal(s, l):
234
- l_num, s_num = str2float(l), str2float(s)
235
- if type(l_num) == type(s_num):
236
- return l_num != s_num
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
237
256
  else:
238
- return l != s
257
+ return x != s
239
258
 
240
- def gt(s, l):
241
- l_num, s_num = str2float(l), str2float(s)
242
- if type(l_num) == type(s_num):
243
- return l_num > s_num
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
244
263
  else:
245
- return l > s
264
+ return x > s
246
265
 
247
- def lt(s, l):
248
- l_num, s_num = str2float(l), str2float(s)
249
- if type(l_num) == type(s_num):
250
- return l_num < s_num
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
251
270
  else:
252
- return l < s
271
+ return x < s
253
272
 
254
- def gt_or_equal(s, l):
255
- l_num, s_num = str2float(l), str2float(s)
256
- if type(l_num) == type(s_num):
257
- return l_num >= s_num
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
258
277
  else:
259
- return l >= s
278
+ return x >= s
260
279
 
261
- def lt_or_equal(s, l):
262
- l_num, s_num = str2float(l), str2float(s)
263
- if type(l_num) == type(s_num):
264
- return l_num <= s_num
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
265
284
  else:
266
- return l <= s
285
+ return x <= s
267
286
 
268
- def between(s, l):
287
+ def between(s, x):
269
288
  if len(s.split(" AND ")) != 2:
270
289
  return None
271
290
  s1, s2 = s.split(" AND ")
272
291
  s1_num, s2_num = str2float(s1), str2float(s2)
273
292
  if type(s1_num) != type(s2_num):
274
293
  return None
275
- l_num = str2float(l)
276
- if type(s1_num) == type(l_num):
277
- return l_num >= s1_num and l_num <= s2_num
294
+ x_num = str2float(x)
295
+ if type(s1_num) == type(x_num):
296
+ return s1_num <= x_num <= s2_num
278
297
  else:
279
- return l >= s1 and l <= s2
298
+ return s1 <= x <= s2
280
299
 
281
300
  if not self.lineEdit.text():
282
301
  self.view.setRowCount(len(self.data))
@@ -286,7 +305,6 @@ class observationsList_widget(QDialog):
286
305
  self.view.setItem(r, c, self.set_item(r, c))
287
306
 
288
307
  else:
289
-
290
308
  if self.cbLogic.currentText() == "contains":
291
309
  logic = in_
292
310
  if self.cbLogic.currentText() == "does not contain":
boris/otx_parser.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2023 Olivier Friard
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 pprint
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 {"error": "error when extracting file"}
62
+ except Exception:
63
+ return {"fatal": True}, ["Error when extracting file from OTB"]
62
64
  else:
63
- return {"error": "error when extracting file"}
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 {"error": "parsing error"}
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 {"error": "parsing error"}
75
+ except Exception:
76
+ return {"fatal": True}, ["XML parsing error"]
75
77
 
76
78
  else:
77
- return {"error": "The file must be in OTB, OTX or ODX format"}
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
- print("modifiers")
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
- print("connections")
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
- pprint.pprint(behaviors_boris)
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
- """print(f"{CREATION_DATETIME=}") # ex: 2022-05-18 10:04:09.474512"""
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
- timestamp = dec(str(round(full_timestamp - day_timestamp, 3)))
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
- OBS_EVENT_BEHAVIOR_MODIFIER = (
385
- event.getElementsByTagName("OBS_EVENT_BEHAVIOR")[0]
386
- .getElementsByTagName("OBS_EVENT_BEHAVIOR_MODIFIER")[0]
387
- .childNodes[0]
388
- .data
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
- # print(f"{timestamp=}")
397
- # print(f"{OBS_EVENT_SUBJECT=}")
398
- # print(f"{OBS_EVENT_BEHAVIOR=}")
399
- # print(f"{OBS_EVENT_BEHAVIOR_MODIFIER=}")
400
- # print(f"{OBS_EVENT_COMMENT=}")
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
- project["msg"] = "The keys longer than one char were deleted"
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
- otx_to_boris(sys.argv[1])
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
- # pprint.pprint(otx_to_boris(sys.argv[1]))
441
+ pprint.pprint(project)
442
+ pprint.pprint(errors)