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.
Files changed (125) hide show
  1. boris/__init__.py +1 -1
  2. boris/__main__.py +1 -1
  3. boris/about.py +24 -40
  4. boris/add_modifier.py +88 -80
  5. boris/add_modifier_ui.py +235 -131
  6. boris/advanced_event_filtering.py +23 -29
  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 +16 -34
  23. boris/config.py +101 -49
  24. boris/config_file.py +55 -64
  25. boris/connections.py +105 -58
  26. boris/converters.py +13 -37
  27. boris/converters_ui.py +187 -110
  28. boris/cooccurence.py +250 -0
  29. boris/core.py +2108 -1275
  30. boris/core_qrc.py +15892 -10829
  31. boris/core_ui.py +941 -806
  32. boris/db_functions.py +17 -42
  33. boris/dev.py +134 -0
  34. boris/dialog.py +461 -242
  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 +405 -281
  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 +180 -203
  43. boris/export_observation.py +60 -73
  44. boris/external_processes.py +123 -98
  45. boris/geometric_measurement.py +427 -218
  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 +304 -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 +16 -6
  55. boris/modifier_coding_map_creator.py +1013 -0
  56. boris/modifiers_coding_map.py +7 -9
  57. boris/mpv2.py +127 -36
  58. boris/observation.py +493 -210
  59. boris/observation_operations.py +1010 -391
  60. boris/observation_ui.py +573 -363
  61. boris/observations_list.py +51 -58
  62. boris/otx_parser.py +74 -68
  63. boris/param_panel.py +45 -59
  64. boris/param_panel_ui.py +254 -138
  65. boris/player_dock_widget.py +91 -56
  66. boris/plot_data_module.py +18 -53
  67. boris/plot_events.py +56 -153
  68. boris/plot_events_rt.py +16 -30
  69. boris/plot_spectrogram_rt.py +80 -56
  70. boris/plot_waveform_rt.py +23 -48
  71. boris/plugins.py +431 -0
  72. boris/portion/__init__.py +18 -8
  73. boris/portion/const.py +35 -18
  74. boris/portion/dict.py +5 -5
  75. boris/portion/func.py +2 -2
  76. boris/portion/interval.py +21 -41
  77. boris/portion/io.py +41 -32
  78. boris/preferences.py +298 -123
  79. boris/preferences_ui.py +664 -225
  80. boris/project.py +293 -270
  81. boris/project_functions.py +610 -537
  82. boris/project_import_export.py +204 -213
  83. boris/project_ui.py +673 -441
  84. boris/qrc_boris.py +6 -3
  85. boris/qrc_boris5.py +6 -3
  86. boris/select_modifiers.py +62 -90
  87. boris/select_observations.py +19 -197
  88. boris/select_subj_behav.py +67 -39
  89. boris/state_events.py +51 -33
  90. boris/subjects_pad.py +6 -8
  91. boris/synthetic_time_budget.py +25 -17
  92. boris/time_budget_functions.py +169 -169
  93. boris/time_budget_widget.py +71 -86
  94. boris/transitions.py +41 -41
  95. boris/utilities.py +562 -222
  96. boris/version.py +3 -3
  97. boris/video_equalizer.py +16 -14
  98. boris/video_equalizer_ui.py +199 -130
  99. boris/video_operations.py +78 -28
  100. boris/view_df.py +104 -0
  101. boris/view_df_ui.py +75 -0
  102. boris/write_event.py +240 -136
  103. boris_behav_obs-9.7.1.dist-info/METADATA +140 -0
  104. boris_behav_obs-9.7.1.dist-info/RECORD +109 -0
  105. {boris_behav_obs-8.16.6.dist-info → boris_behav_obs-9.7.1.dist-info}/WHEEL +1 -1
  106. boris_behav_obs-9.7.1.dist-info/entry_points.txt +2 -0
  107. boris/README.TXT +0 -22
  108. boris/add_modifier.ui +0 -323
  109. boris/converters.ui +0 -289
  110. boris/core.qrc +0 -37
  111. boris/core.ui +0 -1571
  112. boris/edit_event.ui +0 -233
  113. boris/icons/logo_eye.ico +0 -0
  114. boris/map_creator.py +0 -982
  115. boris/observation.ui +0 -814
  116. boris/param_panel.ui +0 -379
  117. boris/preferences.ui +0 -537
  118. boris/project.ui +0 -1074
  119. boris/vlc_local.py +0 -90
  120. boris_behav_obs-8.16.6.dist-info/LICENSE.TXT +0 -674
  121. boris_behav_obs-8.16.6.dist-info/METADATA +0 -134
  122. boris_behav_obs-8.16.6.dist-info/RECORD +0 -106
  123. boris_behav_obs-8.16.6.dist-info/entry_points.txt +0 -2
  124. {boris → boris_behav_obs-9.7.1.dist-info/licenses}/LICENSE.TXT +0 -0
  125. {boris_behav_obs-8.16.6.dist-info → boris_behav_obs-9.7.1.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,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
- 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):
@@ -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("Cancel", clicked=self.pbCancel_clicked)
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("OK", clicked=self.pbOk_clicked)
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
- for idx in range(self.view.rowCount()):
192
- table_item = self.view.item(idx, 0)
193
- table_item.setSelected(mode == "select")
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, l):
248
- l_num, s_num = str2float(l), str2float(s)
249
- if type(l_num) == type(s_num):
250
- 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
251
249
  else:
252
- return l == s
250
+ return x == s
253
251
 
254
- def not_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
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 l != s
257
+ return x != s
260
258
 
261
- def gt(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
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 l > s
264
+ return x > s
267
265
 
268
- def lt(s, l):
269
- l_num, s_num = str2float(l), str2float(s)
270
- if type(l_num) == type(s_num):
271
- 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
272
270
  else:
273
- return l < s
271
+ return x < s
274
272
 
275
- def gt_or_equal(s, l):
276
- l_num, s_num = str2float(l), str2float(s)
277
- if type(l_num) == type(s_num):
278
- 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
279
277
  else:
280
- return l >= s
278
+ return x >= s
281
279
 
282
- def lt_or_equal(s, l):
283
- l_num, s_num = str2float(l), str2float(s)
284
- if type(l_num) == type(s_num):
285
- 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
286
284
  else:
287
- return l <= s
285
+ return x <= s
288
286
 
289
- def between(s, l):
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
- l_num = str2float(l)
297
- if type(s1_num) == type(l_num):
298
- 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
299
297
  else:
300
- return l >= s1 and l <= s2
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-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)