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
boris/qrc_boris.py CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  # Resource object code
4
4
  #
5
- # Created by: The Resource Compiler for PyQt5 (Qt v5.13.0)
5
+ # Created by: The Resource Compiler for PySide6 (Qt v5.13.0)
6
6
  #
7
7
  # WARNING! All changes made in this file will be lost!
8
8
 
9
- from PyQt5 import QtCore
9
+ from PySide6 import QtCore
10
10
 
11
11
  qt_resource_data = b"\
12
12
  \x00\x00\x04\xba\
@@ -10369,7 +10369,7 @@ qt_resource_struct_v2 = b"\
10369
10369
  \x00\x00\x01\x70\xe3\xd1\xd7\xb0\
10370
10370
  "
10371
10371
 
10372
- qt_version = [int(v) for v in QtCore.qVersion().split('.')]
10372
+ qt_version = [int(v) for v in QtCore.qVersion().split(".")]
10373
10373
  if qt_version < [5, 8, 0]:
10374
10374
  rcc_version = 1
10375
10375
  qt_resource_struct = qt_resource_struct_v1
@@ -10377,10 +10377,13 @@ else:
10377
10377
  rcc_version = 2
10378
10378
  qt_resource_struct = qt_resource_struct_v2
10379
10379
 
10380
+
10380
10381
  def qInitResources():
10381
10382
  QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
10382
10383
 
10384
+
10383
10385
  def qCleanupResources():
10384
10386
  QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
10385
10387
 
10388
+
10386
10389
  qInitResources()
boris/qrc_boris5.py CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  # Resource object code
4
4
  #
5
- # Created by: The Resource Compiler for PyQt5 (Qt v5.11.2)
5
+ # Created by: The Resource Compiler for PySide6 (Qt v5.11.2)
6
6
  #
7
7
  # WARNING! All changes made in this file will be lost!
8
8
 
9
- from PyQt5 import QtCore
9
+ from PySide6 import QtCore
10
10
 
11
11
  qt_resource_data = b"\
12
12
  \x00\x00\x04\xa6\
@@ -2559,7 +2559,7 @@ qt_resource_struct_v2 = b"\
2559
2559
  \x00\x00\x01\x68\x32\x38\xb4\xfd\
2560
2560
  "
2561
2561
 
2562
- qt_version = [int(v) for v in QtCore.qVersion().split('.')]
2562
+ qt_version = [int(v) for v in QtCore.qVersion().split(".")]
2563
2563
  if qt_version < [5, 8, 0]:
2564
2564
  rcc_version = 1
2565
2565
  qt_resource_struct = qt_resource_struct_v1
@@ -2567,10 +2567,13 @@ else:
2567
2567
  rcc_version = 2
2568
2568
  qt_resource_struct = qt_resource_struct_v2
2569
2569
 
2570
+
2570
2571
  def qInitResources():
2571
2572
  QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
2572
2573
 
2574
+
2573
2575
  def qCleanupResources():
2574
2576
  QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
2575
2577
 
2578
+
2576
2579
  qInitResources()
boris/select_modifiers.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
 
@@ -22,17 +22,32 @@ This file is part of BORIS.
22
22
 
23
23
  import re
24
24
 
25
- from PyQt5.QtCore import *
26
- from PyQt5.QtGui import *
27
- from PyQt5.QtWidgets import *
25
+ from PySide6.QtCore import Qt, QEvent
26
+ from PySide6.QtWidgets import (
27
+ QDialog,
28
+ QVBoxLayout,
29
+ QLabel,
30
+ QListWidget,
31
+ QHBoxLayout,
32
+ QLineEdit,
33
+ QPushButton,
34
+ QListWidgetItem,
35
+ QSizePolicy,
36
+ QSpacerItem,
37
+ QAbstractItemView,
38
+ QMessageBox,
39
+ )
28
40
 
29
41
  from . import config as cfg
30
42
  from . import utilities as util
31
43
 
32
44
 
33
45
  class ModifiersList(QDialog):
34
- def __init__(self, code: str, modifiers_dict: dict, currentModifier: str):
46
+ """
47
+ class for selection the modifier(s)
48
+ """
35
49
 
50
+ def __init__(self, code: str, modifiers_dict: dict, currentModifier: str):
36
51
  super().__init__()
37
52
  self.setWindowTitle(cfg.programName)
38
53
  self.setWindowFlags(Qt.WindowStaysOnTopHint)
@@ -49,23 +64,29 @@ class ModifiersList(QDialog):
49
64
  self.modifiersSetNumber = 0
50
65
 
51
66
  for idx in util.sorted_keys(modifiers_dict):
52
-
53
- if self.modifiers_dict[idx]["type"] not in [
67
+ if self.modifiers_dict[idx]["type"] not in (
54
68
  cfg.SINGLE_SELECTION,
55
69
  cfg.MULTI_SELECTION,
56
70
  cfg.NUMERIC_MODIFIER,
57
- ]:
71
+ ):
58
72
  continue
59
73
 
60
74
  V2layout = QVBoxLayout()
61
75
 
62
76
  self.modifiersSetNumber += 1
63
77
 
64
- lb = QLabel()
65
- lb.setText(f"Modifier <b>{self.modifiers_dict[idx]['name']}</b>")
66
- V2layout.addWidget(lb)
78
+ if self.modifiers_dict[idx].get("name", ""):
79
+ lb1 = QLabel(f"Modifier <b>{self.modifiers_dict[idx].get('name', '')}</b>")
80
+ V2layout.addWidget(lb1)
81
+
82
+ if self.modifiers_dict[idx].get("description", ""):
83
+ lb2 = QLabel(f"<small>{self.modifiers_dict[idx].get('description', '')[:50]}</small>")
84
+ V2layout.addWidget(lb2)
67
85
 
68
- if self.modifiers_dict[idx]["type"] in [cfg.SINGLE_SELECTION, cfg.MULTI_SELECTION]:
86
+ if self.modifiers_dict[idx]["type"] in (
87
+ cfg.SINGLE_SELECTION,
88
+ cfg.MULTI_SELECTION,
89
+ ):
69
90
  lw = QListWidget()
70
91
  self.modifiers_dict[idx]["widget"] = lw
71
92
  lw.setObjectName(f"lw_modifiers_({self.modifiers_dict[idx]['type']})")
@@ -83,9 +104,7 @@ class ModifiersList(QDialog):
83
104
 
84
105
  # previously selected
85
106
  try:
86
- if currentModifierList != [""] and re.sub(" \(.\)", "", modifier) in currentModifierList[
87
- int(idx)
88
- ].split(","):
107
+ if currentModifierList != [""] and re.sub(r" \(.\)", "", modifier) in currentModifierList[int(idx)].split(","):
89
108
  item.setCheckState(Qt.Checked)
90
109
  except Exception: # for old projects due to a fixed bug
91
110
  pass
@@ -94,10 +113,7 @@ class ModifiersList(QDialog):
94
113
 
95
114
  if self.modifiers_dict[idx]["type"] == cfg.SINGLE_SELECTION:
96
115
  try:
97
- if (
98
- currentModifierList != [""]
99
- and re.sub(" \(.\)", "", modifier) == currentModifierList[int(idx)]
100
- ):
116
+ if currentModifierList != [""] and re.sub(r" \(.\)", "", modifier) == currentModifierList[int(idx)]:
101
117
  item.setSelected(True)
102
118
  except Exception: # for old projects due to a fixed bug
103
119
  pass
@@ -106,6 +122,7 @@ class ModifiersList(QDialog):
106
122
  if self.modifiers_dict[idx]["type"] in [cfg.NUMERIC_MODIFIER]:
107
123
  le = QLineEdit()
108
124
  self.modifiers_dict[idx]["widget"] = le
125
+ le.setObjectName(f"le_modifiers_({self.modifiers_dict[idx]['type']})")
109
126
 
110
127
  if currentModifierList != [""] and currentModifierList[int(idx)] != "None":
111
128
  le.setText(currentModifierList[int(idx)])
@@ -152,12 +169,15 @@ class ModifiersList(QDialog):
152
169
 
153
170
  # accept and close dialog if enter pressed
154
171
  if ek == Qt.Key_Enter or ek == Qt.Key_Return: # enter or enter from numeric pad
155
- self.accept()
172
+ if not self.pbOK_clicked():
173
+ return False
156
174
  return True
157
175
 
176
+ modifiersSetIndex = 0
158
177
  for widget in self.children():
178
+ if "_modifiers" in widget.objectName():
179
+ modifiersSetIndex = modifiersSetIndex + 1
159
180
  if "lw_modifiers" in widget.objectName():
160
-
161
181
  if self.modifiersSetNumber == 1:
162
182
  # check if modifiers have code
163
183
  for index in range(widget.count()):
@@ -165,11 +185,8 @@ class ModifiersList(QDialog):
165
185
  break
166
186
  else:
167
187
  # modifiers have no associated code: the modifier starting with hit key will be selected
168
- if ek not in [Qt.Key_Down, Qt.Key_Up]:
169
-
170
- if (
171
- ek == Qt.Key_Space and f"({cfg.MULTI_SELECTION})" in widget.objectName()
172
- ): # checking using SPACE bar
188
+ if ek not in (Qt.Key_Down, Qt.Key_Up):
189
+ if ek == Qt.Key_Space and f"({cfg.MULTI_SELECTION})" in widget.objectName(): # checking using SPACE bar
173
190
  if widget.item(widget.currentRow()).checkState() == Qt.Checked:
174
191
  widget.item(widget.currentRow()).setCheckState(Qt.Unchecked)
175
192
  else:
@@ -179,7 +196,10 @@ class ModifiersList(QDialog):
179
196
  for index in range(widget.count()):
180
197
  if widget.item(index).text().upper().startswith(ek_text.upper()):
181
198
  widget.setCurrentRow(index)
182
- widget.scrollToItem(widget.item(index), QAbstractItemView.EnsureVisible)
199
+ widget.scrollToItem(
200
+ widget.item(index),
201
+ QAbstractItemView.EnsureVisible,
202
+ )
183
203
  return True
184
204
  else: # up / down keys
185
205
  try:
@@ -191,7 +211,7 @@ class ModifiersList(QDialog):
191
211
  return
192
212
 
193
213
  for index in range(widget.count()):
194
-
214
+ # check function kesy (F1, F2...)
195
215
  if ek in cfg.function_keys:
196
216
  if f"({cfg.function_keys[ek]})" in widget.item(index).text().upper():
197
217
  if f"({cfg.SINGLE_SELECTION})" in widget.objectName():
@@ -200,6 +220,10 @@ class ModifiersList(QDialog):
200
220
  if self.modifiersSetNumber == 1:
201
221
  self.accept()
202
222
  return True
223
+ # else move to next set of mofifiers
224
+ elif modifiersSetIndex != self.modifiersSetNumber:
225
+ widget.parent().focusNextChild()
226
+ return True
203
227
 
204
228
  if f"({cfg.MULTI_SELECTION})" in widget.objectName():
205
229
  if widget.item(index).checkState() == Qt.Checked:
@@ -208,13 +232,16 @@ class ModifiersList(QDialog):
208
232
  widget.item(index).setCheckState(Qt.Checked)
209
233
 
210
234
  if ek < 1114112 and f"({ek_text})" in widget.item(index).text():
211
-
212
235
  if f"({cfg.SINGLE_SELECTION})" in widget.objectName():
213
236
  widget.item(index).setSelected(True)
214
237
  # close dialog if one set of modifiers
215
238
  if self.modifiersSetNumber == 1:
216
239
  self.accept()
217
240
  return True
241
+ # else move to next set of mofifiers
242
+ elif modifiersSetIndex != self.modifiersSetNumber:
243
+ widget.parent().focusNextChild()
244
+ return True
218
245
 
219
246
  if f"({cfg.MULTI_SELECTION})" in widget.objectName():
220
247
  if widget.item(index).checkState() == Qt.Checked:
@@ -231,17 +258,24 @@ class ModifiersList(QDialog):
231
258
  get modifiers
232
259
  returns list of selected modifiers
233
260
  """
234
- modifiers = []
235
- for idx in util.sorted_keys(self.modifiers_dict):
236
261
 
237
- if self.modifiers_dict[idx]["type"] in [cfg.SINGLE_SELECTION, cfg.MULTI_SELECTION, cfg.NUMERIC_MODIFIER]:
262
+ for idx in util.sorted_keys(self.modifiers_dict):
263
+ if self.modifiers_dict[idx]["type"] in [
264
+ cfg.SINGLE_SELECTION,
265
+ cfg.MULTI_SELECTION,
266
+ cfg.NUMERIC_MODIFIER,
267
+ ]:
238
268
  self.modifiers_dict[idx]["selected"] = []
239
269
 
240
270
  if self.modifiers_dict[idx]["type"] == cfg.MULTI_SELECTION:
241
271
  for j in range(self.modifiers_dict[idx]["widget"].count()):
242
272
  if self.modifiers_dict[idx]["widget"].item(j).checkState() == Qt.Checked:
243
273
  self.modifiers_dict[idx]["selected"].append(
244
- re.sub(" \(.*\)", "", self.modifiers_dict[idx]["widget"].item(j).text())
274
+ re.sub(
275
+ r" \(.*\)",
276
+ "",
277
+ self.modifiers_dict[idx]["widget"].item(j).text(),
278
+ )
245
279
  )
246
280
 
247
281
  if not self.modifiers_dict[idx]["selected"]:
@@ -249,38 +283,30 @@ class ModifiersList(QDialog):
249
283
 
250
284
  if self.modifiers_dict[idx]["type"] == cfg.SINGLE_SELECTION:
251
285
  for item in self.modifiers_dict[idx]["widget"].selectedItems():
252
- self.modifiers_dict[idx]["selected"].append(re.sub(" \(.*\)", "", item.text()))
286
+ self.modifiers_dict[idx]["selected"].append(re.sub(r" \(.*\)", "", item.text()))
253
287
 
254
288
  if self.modifiers_dict[idx]["type"] == cfg.NUMERIC_MODIFIER:
255
289
  self.modifiers_dict[idx]["selected"] = (
256
290
  self.modifiers_dict[idx]["widget"].text() if self.modifiers_dict[idx]["widget"].text() else "None"
257
291
  )
258
- """
259
- for widget in self.children():
260
- if widget.objectName() == "lw_modifiers_classic":
261
- for item in widget.selectedItems():
262
- modifiers.append(re.sub(" \(.*\)", "", item.text()))
263
- if widget.objectName() == "lw_modifiers_from_set":
264
- for idx in range(widget.count()):
265
- if widget.item(idx).checkState() == Qt.Checked:
266
- modifiers.append(widget.item(idx).text())
267
- """
268
292
 
269
293
  return self.modifiers_dict
270
294
 
271
295
  def pbOK_clicked(self):
272
-
296
+ """
297
+ OK button is clicked
298
+ """
273
299
  for idx in util.sorted_keys(self.modifiers_dict):
274
300
  if self.modifiers_dict[idx]["type"] == cfg.NUMERIC_MODIFIER:
275
301
  if self.modifiers_dict[idx]["widget"].text():
276
302
  try:
277
- val = float(self.modifiers_dict[idx]["widget"].text())
303
+ _ = float(self.modifiers_dict[idx]["widget"].text())
278
304
  except Exception:
279
305
  QMessageBox.warning(
280
306
  self,
281
307
  cfg.programName,
282
- "<b>{}</b> is not a numeric value".format(self.modifiers_dict[idx]["widget"].text()),
308
+ f"<b>{self.modifiers_dict[idx]['widget'].text()}</b> is not a numeric value",
283
309
  )
284
- return
310
+ return False
285
311
 
286
312
  self.accept()
@@ -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
@@ -20,9 +20,12 @@ Copyright 2012-2023 Olivier Friard
20
20
  MA 02110-1301, USA.
21
21
 
22
22
  """
23
- from typing import List, Tuple, Dict
24
- from PyQt5.QtCore import Qt
25
- from PyQt5.QtWidgets import QAbstractItemView
23
+
24
+ import logging
25
+ from typing import Tuple
26
+
27
+ from PySide6.QtCore import Qt
28
+ from PySide6.QtWidgets import QAbstractItemView
26
29
 
27
30
  from . import config as cfg
28
31
  from . import gui_utilities, observations_list, project_functions
@@ -54,38 +57,32 @@ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str,
54
57
  indep_var_header.append(pj[cfg.INDEPENDENT_VARIABLES][idx]["label"])
55
58
  column_type.append(pj[cfg.INDEPENDENT_VARIABLES][idx]["type"])
56
59
 
57
- state_events_list = [
58
- pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE]
59
- for x in pj[cfg.ETHOGRAM]
60
- if cfg.STATE in pj[cfg.ETHOGRAM][x][cfg.TYPE].upper()
61
- ]
60
+ state_events_list = util.state_behavior_codes(pj[cfg.ETHOGRAM])
62
61
 
63
62
  data: list = []
64
63
  not_paired: list = []
65
64
 
66
- """print(f"{hash(str(self.pj[cfg.OBSERVATIONS]))=}")"""
67
-
65
+ # check if observations changed
68
66
  if hash(str(self.pj[cfg.OBSERVATIONS])) != self.mem_hash_obs:
67
+ logging.debug("observations changed")
69
68
 
70
69
  for obs in sorted(list(pj[cfg.OBSERVATIONS].keys())):
71
70
  date = pj[cfg.OBSERVATIONS][obs]["date"].replace("T", " ")
72
71
  descr = util.eol2space(pj[cfg.OBSERVATIONS][obs][cfg.DESCRIPTION])
73
72
 
74
73
  # subjects
75
- observed_subjects = [
76
- cfg.NO_FOCAL_SUBJECT if x == "" else x for x in project_functions.extract_observed_subjects(pj, [obs])
77
- ]
74
+ observed_subjects = [cfg.NO_FOCAL_SUBJECT if x == "" else x for x in project_functions.extract_observed_subjects(pj, [obs])]
78
75
 
79
76
  subjectsList = ", ".join(observed_subjects)
80
77
 
81
78
  # observed time interval
82
79
  interval = project_functions.observed_interval(pj[cfg.OBSERVATIONS][obs])
83
- observed_interval_str = str(interval[1] - interval[0])
80
+ observed_interval_str = str(round(interval[1] - interval[0], 3))
84
81
 
85
82
  # media
86
- media = ""
83
+ media: str = ""
87
84
  if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.MEDIA:
88
- media_list = []
85
+ media_list: list = []
89
86
  if pj[cfg.OBSERVATIONS][obs][cfg.FILE]:
90
87
  for player in sorted(pj[cfg.OBSERVATIONS][obs][cfg.FILE].keys()):
91
88
  for media in pj[cfg.OBSERVATIONS][obs][cfg.FILE][player]:
@@ -100,13 +97,13 @@ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str,
100
97
  media = cfg.LIVE
101
98
 
102
99
  if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.IMAGES:
103
- dir_list = []
100
+ dir_list: list = []
104
101
  for dir_path in pj[cfg.OBSERVATIONS][obs].get(cfg.DIRECTORIES_LIST, []):
105
102
  dir_list.append(dir_path)
106
103
  media = "; ".join(dir_list)
107
104
 
108
105
  # independent variables
109
- indepvar = []
106
+ indepvar: list = []
110
107
  if cfg.INDEPENDENT_VARIABLES in pj[cfg.OBSERVATIONS][obs]:
111
108
  for var_label in indep_var_header:
112
109
  if var_label in pj[cfg.OBSERVATIONS][obs][cfg.INDEPENDENT_VARIABLES]:
@@ -115,20 +112,15 @@ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str,
115
112
  indepvar.append("")
116
113
 
117
114
  # check unpaired events
118
- ok, _ = project_functions.check_state_events_obs(
119
- obs, pj[cfg.ETHOGRAM], pj[cfg.OBSERVATIONS][obs], cfg.HHMMSS
120
- )
115
+ ok, _ = project_functions.check_state_events_obs(obs, pj[cfg.ETHOGRAM], pj[cfg.OBSERVATIONS][obs], cfg.HHMMSS)
121
116
  if not ok:
122
117
  not_paired.append(obs)
123
118
 
124
119
  # exhaustivity
125
120
  if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] in (cfg.MEDIA, cfg.LIVE):
126
121
  # check exhaustivity of observation
127
- exhaustivity = project_functions.check_observation_exhaustivity(
128
- pj[cfg.OBSERVATIONS][obs][cfg.EVENTS], state_events_list
129
- )
122
+ exhaustivity = project_functions.check_observation_exhaustivity(pj[cfg.OBSERVATIONS][obs][cfg.EVENTS], state_events_list)
130
123
  elif pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.IMAGES:
131
- # TODO: add exhaustivity for images observation (number of coded images?)
132
124
  exhaustivity = project_functions.check_observation_exhaustivity_pictures(pj[cfg.OBSERVATIONS][obs])
133
125
 
134
126
  data.append([obs, date, descr, subjectsList, observed_interval_str, str(exhaustivity), media] + indepvar)
@@ -137,11 +129,12 @@ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str,
137
129
  data, header=fields_list + indep_var_header, column_type=column_type, not_paired=not_paired
138
130
  )
139
131
  self.data = data
132
+ self.not_paired = not_paired
140
133
  self.mem_hash_obs = hash(str(self.pj[cfg.OBSERVATIONS]))
141
134
 
142
135
  else:
143
136
  obsList = observations_list.observationsList_widget(
144
- self.data, header=fields_list + indep_var_header, column_type=column_type, not_paired=not_paired
137
+ self.data, header=fields_list + indep_var_header, column_type=column_type, not_paired=self.not_paired
145
138
  )
146
139
 
147
140
  if windows_title:
@@ -215,175 +208,3 @@ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str,
215
208
  resultStr = cfg.VIEW
216
209
 
217
210
  return resultStr, selected_observations
218
-
219
-
220
- def select_observations(pj: dict, mode: str, windows_title: str = "") -> Tuple[str, list]:
221
- """
222
- allow user to select observations
223
- mode: accepted values: OPEN, EDIT, SINGLE, MULTIPLE, SELECT1
224
-
225
- Args:
226
- pj (dict): BORIS project dictionary
227
- mode (str): mode for selection: OPEN, EDIT, SINGLE, MULTIPLE, SELECT1
228
- windows_title (str): title for windows
229
-
230
- Returns:
231
- str: selected mode: OPEN, EDIT, VIEW
232
- list: list of selected observations
233
- """
234
-
235
- fields_list = ["id", "date", "description", "subjects", "observation duration", "exhaustivity %", "media"]
236
- indep_var_header, column_type = [], [cfg.TEXT, cfg.TEXT, cfg.TEXT, cfg.TEXT, cfg.NUMERIC, cfg.NUMERIC, cfg.TEXT]
237
-
238
- if cfg.INDEPENDENT_VARIABLES in pj:
239
- for idx in util.sorted_keys(pj[cfg.INDEPENDENT_VARIABLES]):
240
- indep_var_header.append(pj[cfg.INDEPENDENT_VARIABLES][idx]["label"])
241
- column_type.append(pj[cfg.INDEPENDENT_VARIABLES][idx]["type"])
242
-
243
- state_events_list = [
244
- pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE]
245
- for x in pj[cfg.ETHOGRAM]
246
- if cfg.STATE in pj[cfg.ETHOGRAM][x][cfg.TYPE].upper()
247
- ]
248
-
249
- data: list = []
250
- not_paired: list = []
251
-
252
- for obs in sorted(list(pj[cfg.OBSERVATIONS].keys())):
253
- date = pj[cfg.OBSERVATIONS][obs]["date"].replace("T", " ")
254
- descr = util.eol2space(pj[cfg.OBSERVATIONS][obs][cfg.DESCRIPTION])
255
-
256
- # subjects
257
- observed_subjects = [
258
- cfg.NO_FOCAL_SUBJECT if x == "" else x for x in project_functions.extract_observed_subjects(pj, [obs])
259
- ]
260
-
261
- subjectsList = ", ".join(observed_subjects)
262
-
263
- # observed time interval
264
- interval = project_functions.observed_interval(pj[cfg.OBSERVATIONS][obs])
265
- observed_interval_str = str(interval[1] - interval[0])
266
-
267
- # media
268
- media = ""
269
- if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.MEDIA:
270
- media_list = []
271
- if pj[cfg.OBSERVATIONS][obs][cfg.FILE]:
272
- for player in sorted(pj[cfg.OBSERVATIONS][obs][cfg.FILE].keys()):
273
- for media in pj[cfg.OBSERVATIONS][obs][cfg.FILE][player]:
274
- media_list.append(f"#{player}: {media}")
275
-
276
- if len(media_list) > 8:
277
- media = " ".join(media_list)
278
- else:
279
- media = "\n".join(media_list)
280
-
281
- if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.LIVE:
282
- media = cfg.LIVE
283
-
284
- if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.IMAGES:
285
- dir_list = []
286
- for dir_path in pj[cfg.OBSERVATIONS][obs].get(cfg.DIRECTORIES_LIST, []):
287
- dir_list.append(dir_path)
288
- media = "; ".join(dir_list)
289
-
290
- # independent variables
291
- indepvar = []
292
- if cfg.INDEPENDENT_VARIABLES in pj[cfg.OBSERVATIONS][obs]:
293
- for var_label in indep_var_header:
294
- if var_label in pj[cfg.OBSERVATIONS][obs][cfg.INDEPENDENT_VARIABLES]:
295
- indepvar.append(pj[cfg.OBSERVATIONS][obs][cfg.INDEPENDENT_VARIABLES][var_label])
296
- else:
297
- indepvar.append("")
298
-
299
- # check unpaired events
300
- ok, _ = project_functions.check_state_events_obs(obs, pj[cfg.ETHOGRAM], pj[cfg.OBSERVATIONS][obs], cfg.HHMMSS)
301
- if not ok:
302
- not_paired.append(obs)
303
-
304
- # exhaustivity
305
- if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] in (cfg.MEDIA, cfg.LIVE):
306
- # check exhaustivity of observation
307
- exhaustivity = project_functions.check_observation_exhaustivity(
308
- pj[cfg.OBSERVATIONS][obs][cfg.EVENTS], state_events_list
309
- )
310
- elif pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.IMAGES:
311
- # TODO: add exhaustivity for images observation (number of coded images?)
312
- exhaustivity = project_functions.check_observation_exhaustivity_pictures(pj[cfg.OBSERVATIONS][obs])
313
-
314
- data.append([obs, date, descr, subjectsList, observed_interval_str, str(exhaustivity), media] + indepvar)
315
-
316
- obsList = observations_list.observationsList_widget(
317
- data, header=fields_list + indep_var_header, column_type=column_type, not_paired=not_paired
318
- )
319
- if windows_title:
320
- obsList.setWindowTitle(windows_title)
321
-
322
- obsList.pbOpen.setVisible(False)
323
- obsList.pbView.setVisible(False)
324
- obsList.pbEdit.setVisible(False)
325
- obsList.pbOk.setVisible(False)
326
- obsList.pbSelectAll.setVisible(False)
327
- obsList.pbUnSelectAll.setVisible(False)
328
- obsList.mode = mode
329
-
330
- if mode == cfg.OPEN:
331
- obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
332
- obsList.pbOpen.setVisible(True)
333
-
334
- if mode == cfg.VIEW:
335
- obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
336
- obsList.pbView.setVisible(True)
337
-
338
- if mode == cfg.EDIT:
339
- obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
340
- obsList.pbEdit.setVisible(True)
341
-
342
- if mode == cfg.SINGLE:
343
- obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
344
- obsList.pbOpen.setVisible(True)
345
- obsList.pbView.setVisible(True)
346
- obsList.pbEdit.setVisible(True)
347
-
348
- if mode == cfg.MULTIPLE:
349
- obsList.view.setSelectionMode(QAbstractItemView.MultiSelection)
350
- obsList.pbOk.setVisible(True)
351
- obsList.pbSelectAll.setVisible(True)
352
- obsList.pbUnSelectAll.setVisible(True)
353
-
354
- if mode == cfg.SELECT1:
355
- obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
356
- obsList.pbOk.setVisible(True)
357
-
358
- # restore window geometry
359
- gui_utilities.restore_geometry(obsList, "observations list", (900, 600))
360
-
361
- obsList.view.sortItems(0, Qt.AscendingOrder)
362
- for row in range(obsList.view.rowCount()):
363
- obsList.view.resizeRowToContents(row)
364
-
365
- selected_observations = []
366
-
367
- result = obsList.exec_()
368
-
369
- # saving window geometry in ini file
370
- gui_utilities.save_geometry(obsList, "observations list")
371
-
372
- if result:
373
- if obsList.view.selectedIndexes():
374
- for idx in obsList.view.selectedIndexes():
375
- if idx.column() == 0: # first column
376
- selected_observations.append(idx.data())
377
-
378
- if result == 0: # cancel
379
- resultStr = ""
380
- if result == 1: # select
381
- resultStr = "ok"
382
- if result == 2: # open
383
- resultStr = cfg.OPEN
384
- if result == 3: # edit
385
- resultStr = cfg.EDIT
386
- if result == 4: # view
387
- resultStr = cfg.VIEW
388
-
389
- return resultStr, selected_observations