boris-behav-obs 8.9.16__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 (129) hide show
  1. boris/__init__.py +1 -1
  2. boris/__main__.py +1 -1
  3. boris/about.py +36 -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 +161 -77
  24. boris/config_file.py +63 -83
  25. boris/connections.py +112 -57
  26. boris/converters.py +13 -37
  27. boris/converters_ui.py +187 -110
  28. boris/cooccurence.py +250 -0
  29. boris/core.py +2511 -1824
  30. boris/core_qrc.py +15895 -10185
  31. boris/core_ui.py +946 -792
  32. boris/db_functions.py +21 -41
  33. boris/dev.py +134 -0
  34. boris/dialog.py +505 -244
  35. boris/duration_widget.py +15 -20
  36. boris/edit_event.py +84 -28
  37. boris/edit_event_ui.py +214 -78
  38. boris/event_operations.py +517 -415
  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 +213 -583
  43. boris/export_observation.py +98 -611
  44. boris/external_processes.py +156 -97
  45. boris/geometric_measurement.py +652 -287
  46. boris/gui_utilities.py +91 -14
  47. boris/image_overlay.py +9 -9
  48. boris/import_observations.py +190 -98
  49. boris/ipc_mpv.py +325 -0
  50. boris/irr.py +26 -63
  51. boris/latency.py +34 -25
  52. boris/measurement_widget.py +14 -18
  53. boris/media_file.py +52 -84
  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 +655 -310
  60. boris/observation_operations.py +1036 -404
  61. boris/observation_ui.py +584 -356
  62. boris/observations_list.py +71 -53
  63. boris/otx_parser.py +74 -80
  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 +43 -46
  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 +685 -228
  81. boris/project.py +448 -293
  82. boris/project_functions.py +689 -254
  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 -199
  89. boris/select_subj_behav.py +67 -39
  90. boris/state_events.py +53 -37
  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 +766 -266
  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 +125 -28
  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.9.16.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/boris_ui.py +0 -886
  111. boris/converters.ui +0 -289
  112. boris/core.qrc +0 -35
  113. boris/core.ui +0 -1543
  114. boris/edit_event.ui +0 -175
  115. boris/icons/logo_eye.ico +0 -0
  116. boris/map_creator.py +0 -850
  117. boris/observation.ui +0 -773
  118. boris/param_panel.ui +0 -379
  119. boris/preferences.ui +0 -537
  120. boris/project.ui +0 -1069
  121. boris/project_server.py +0 -236
  122. boris/vlc.py +0 -10343
  123. boris/vlc_local.py +0 -90
  124. boris_behav_obs-8.9.16.dist-info/LICENSE.TXT +0 -674
  125. boris_behav_obs-8.9.16.dist-info/METADATA +0 -129
  126. boris_behav_obs-8.9.16.dist-info/RECORD +0 -108
  127. boris_behav_obs-8.9.16.dist-info/entry_points.txt +0 -2
  128. {boris → boris_behav_obs-9.7.6.dist-info/licenses}/LICENSE.TXT +0 -0
  129. {boris_behav_obs-8.9.16.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,40 +158,37 @@ 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:
187
184
  mutually_exclusive_list.append(behav_code)
188
185
 
189
186
  if behav_id in connections:
190
-
191
187
  modifier_sets = [modifiers[modifier_set]["set_name"] for modifier_set in connections[behav_id]]
192
- print(f"{modifier_sets=}")
193
188
  else:
194
189
  modifier_sets = []
195
190
 
196
191
  if parent_name: # behavior
197
-
198
192
  if (not key or len(key) > 1) and stop_key:
199
193
  key = stop_key
200
194
 
@@ -213,7 +207,6 @@ def otx_to_boris(file_path: str) -> dict:
213
207
  behaviors_list.append(behav_code)
214
208
 
215
209
  else: # behavioral category
216
-
217
210
  behav_category.append(behav_code)
218
211
 
219
212
  behaviors_boris: dict = {}
@@ -243,7 +236,7 @@ def otx_to_boris(file_path: str) -> dict:
243
236
  "description": modifiers[modif_key]["description"],
244
237
  }
245
238
 
246
- pprint.pprint(behaviors_boris)
239
+ logging.debug(behaviors_boris)
247
240
 
248
241
  # subjects
249
242
  subjects = {}
@@ -253,11 +246,11 @@ def otx_to_boris(file_path: str) -> dict:
253
246
  subject_name = re.sub("<[^>]*>", "", subject.getElementsByTagName("CDS_ELE_NAME")[0].toxml())
254
247
  try:
255
248
  key = re.sub("<[^>]*>", "", subject.getElementsByTagName("CDS_ELE_START_KEYCODE")[0].toxml())
256
- except:
249
+ except Exception:
257
250
  key = ""
258
251
  try:
259
252
  parent_name = re.sub("<[^>]*>", "", subject.getElementsByTagName("CDS_ELE_PARENT_NAME")[0].toxml())
260
- except:
253
+ except Exception:
261
254
  parent_name = ""
262
255
 
263
256
  if parent_name:
@@ -270,7 +263,6 @@ def otx_to_boris(file_path: str) -> dict:
270
263
  variables = {}
271
264
  itemlist = xmldoc.getElementsByTagName("VL_VARIABLE")
272
265
  for item in itemlist:
273
-
274
266
  variable = minidom.parseString(item.toxml())
275
267
 
276
268
  variable_label = re.sub("<[^>]*>", "", variable.getElementsByTagName("VL_LABEL")[0].toxml())
@@ -293,7 +285,7 @@ def otx_to_boris(file_path: str) -> dict:
293
285
 
294
286
  try:
295
287
  variable_description = re.sub("<[^>]*>", "", modif.getElementsByTagName("VL_DESCRIPTION")[0].toxml())
296
- except:
288
+ except Exception:
297
289
  variable_description = ""
298
290
 
299
291
  try:
@@ -303,7 +295,7 @@ def otx_to_boris(file_path: str) -> dict:
303
295
  values_list.append(re.sub("<[^>]*>", "", value.toxml()))
304
296
  values_str = ",".join(values_list)
305
297
 
306
- except:
298
+ except Exception:
307
299
  values_str = ""
308
300
 
309
301
  variables[variable_id] = {
@@ -331,16 +323,10 @@ def otx_to_boris(file_path: str) -> dict:
331
323
 
332
324
  observations = xmldoc.getElementsByTagName("OBS_OBSERVATION")
333
325
 
334
- """
335
- print(f"{len(observations)=}")
336
- print()
337
- """
338
-
339
326
  for OBS_OBSERVATION in observations:
340
327
  # OBS_OBSERVATION = minidom.parseString(OBS_OBSERVATION.toxml())
341
328
 
342
329
  obs_id = OBS_OBSERVATION.getAttribute("NAME")
343
- """print(f"{obs_id=}")"""
344
330
 
345
331
  project[cfg.OBSERVATIONS][obs_id] = dict(
346
332
  {
@@ -362,27 +348,24 @@ def otx_to_boris(file_path: str) -> dict:
362
348
 
363
349
  OBS_EVENT_LOGS = OBS_OBSERVATION.getElementsByTagName("OBS_EVENT_LOGS")[0]
364
350
 
365
- # print(f"{OBS_EVENT_LOGS=}")
366
-
367
351
  for OBS_EVENT_LOG in OBS_EVENT_LOGS.getElementsByTagName("OBS_EVENT_LOG"):
368
-
369
352
  CREATION_DATETIME = OBS_EVENT_LOG.getAttribute("CREATION_DATETIME")
370
353
 
371
- CREATION_DATETIME = CREATION_DATETIME.replace(" ", "T").split(".")[0]
354
+ CREATION_DATETIME = CREATION_DATETIME.replace(" ", "T") # .split(".")[0]
372
355
 
373
- """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"""
374
357
 
375
358
  project[cfg.OBSERVATIONS][obs_id]["date"] = CREATION_DATETIME
376
359
 
377
360
  for event in OBS_EVENT_LOG.getElementsByTagName("OBS_EVENT"):
378
-
379
361
  OBS_EVENT_TIMESTAMP = event.getElementsByTagName("OBS_EVENT_TIMESTAMP")[0].childNodes[0].data
380
362
 
381
- day_timestamp = dt.datetime.strptime(OBS_EVENT_TIMESTAMP.split(" ")[0], "%Y-%m-%d").timestamp()
382
-
383
363
  full_timestamp = dt.datetime.strptime(OBS_EVENT_TIMESTAMP, "%Y-%m-%d %H:%M:%S.%f").timestamp()
364
+ logging.debug(f"{full_timestamp=}")
384
365
 
385
- 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"))
386
369
 
387
370
  try:
388
371
  OBS_EVENT_SUBJECT = event.getElementsByTagName("OBS_EVENT_SUBJECT")[0].getAttribute("NAME")
@@ -390,26 +373,34 @@ def otx_to_boris(file_path: str) -> dict:
390
373
  OBS_EVENT_SUBJECT = ""
391
374
 
392
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
393
381
 
394
- OBS_EVENT_BEHAVIOR_MODIFIER = (
395
- event.getElementsByTagName("OBS_EVENT_BEHAVIOR")[0]
396
- .getElementsByTagName("OBS_EVENT_BEHAVIOR_MODIFIER")[0]
397
- .childNodes[0]
398
- .data
399
- )
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 = ""
400
392
 
393
+ # comment
401
394
  try:
402
- 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
403
396
  except Exception:
404
- OBS_EVENT_COMMENT = ""
397
+ OBS_EVENT_COMMENT: str = ""
405
398
 
406
- """
407
- print(f"{timestamp=}")
408
- print(f"{OBS_EVENT_SUBJECT=}")
409
- print(f"{OBS_EVENT_BEHAVIOR=}")
410
- print(f"{OBS_EVENT_BEHAVIOR_MODIFIER=}")
411
- print(f"{OBS_EVENT_COMMENT=}")
412
- """
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=}")
413
404
 
414
405
  project[cfg.OBSERVATIONS][obs_id][cfg.EVENTS].append(
415
406
  [
@@ -421,10 +412,6 @@ def otx_to_boris(file_path: str) -> dict:
421
412
  ]
422
413
  )
423
414
 
424
- # print(80 * "-")
425
-
426
- # print(80 * "=")
427
-
428
415
  project[cfg.PROJECT_NAME] = project_name
429
416
  project[cfg.PROJECT_DATE] = project_creation_date.replace(" ", "T")
430
417
  project[cfg.ETHOGRAM] = behaviors_boris
@@ -434,15 +421,22 @@ def otx_to_boris(file_path: str) -> dict:
434
421
  project[cfg.INDEPENDENT_VARIABLES] = variables_boris
435
422
 
436
423
  if flag_long_key:
437
- 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.")
438
426
 
439
- return project
427
+ return project, error_list
440
428
 
441
429
 
442
430
  if __name__ == "__main__":
443
431
  import sys
444
432
  import pprint
445
433
 
446
- 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])
447
440
 
448
- # pprint.pprint(otx_to_boris(sys.argv[1]))
441
+ pprint.pprint(project)
442
+ pprint.pprint(errors)