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
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,36 +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
 
65
+ # check if observations changed
66
66
  if hash(str(self.pj[cfg.OBSERVATIONS])) != self.mem_hash_obs:
67
+ logging.debug("observations changed")
67
68
 
68
69
  for obs in sorted(list(pj[cfg.OBSERVATIONS].keys())):
69
70
  date = pj[cfg.OBSERVATIONS][obs]["date"].replace("T", " ")
70
71
  descr = util.eol2space(pj[cfg.OBSERVATIONS][obs][cfg.DESCRIPTION])
71
72
 
72
73
  # subjects
73
- observed_subjects = [
74
- cfg.NO_FOCAL_SUBJECT if x == "" else x for x in project_functions.extract_observed_subjects(pj, [obs])
75
- ]
74
+ observed_subjects = [cfg.NO_FOCAL_SUBJECT if x == "" else x for x in project_functions.extract_observed_subjects(pj, [obs])]
76
75
 
77
76
  subjectsList = ", ".join(observed_subjects)
78
77
 
79
78
  # observed time interval
80
79
  interval = project_functions.observed_interval(pj[cfg.OBSERVATIONS][obs])
81
- observed_interval_str = str(interval[1] - interval[0])
80
+ observed_interval_str = str(round(interval[1] - interval[0], 3))
82
81
 
83
82
  # media
84
- media = ""
83
+ media: str = ""
85
84
  if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.MEDIA:
86
- media_list = []
85
+ media_list: list = []
87
86
  if pj[cfg.OBSERVATIONS][obs][cfg.FILE]:
88
87
  for player in sorted(pj[cfg.OBSERVATIONS][obs][cfg.FILE].keys()):
89
88
  for media in pj[cfg.OBSERVATIONS][obs][cfg.FILE][player]:
@@ -98,13 +97,13 @@ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str,
98
97
  media = cfg.LIVE
99
98
 
100
99
  if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.IMAGES:
101
- dir_list = []
100
+ dir_list: list = []
102
101
  for dir_path in pj[cfg.OBSERVATIONS][obs].get(cfg.DIRECTORIES_LIST, []):
103
102
  dir_list.append(dir_path)
104
103
  media = "; ".join(dir_list)
105
104
 
106
105
  # independent variables
107
- indepvar = []
106
+ indepvar: list = []
108
107
  if cfg.INDEPENDENT_VARIABLES in pj[cfg.OBSERVATIONS][obs]:
109
108
  for var_label in indep_var_header:
110
109
  if var_label in pj[cfg.OBSERVATIONS][obs][cfg.INDEPENDENT_VARIABLES]:
@@ -113,18 +112,14 @@ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str,
113
112
  indepvar.append("")
114
113
 
115
114
  # check unpaired events
116
- ok, _ = project_functions.check_state_events_obs(
117
- obs, pj[cfg.ETHOGRAM], pj[cfg.OBSERVATIONS][obs], cfg.HHMMSS
118
- )
115
+ ok, _ = project_functions.check_state_events_obs(obs, pj[cfg.ETHOGRAM], pj[cfg.OBSERVATIONS][obs], cfg.HHMMSS)
119
116
  if not ok:
120
117
  not_paired.append(obs)
121
118
 
122
119
  # exhaustivity
123
120
  if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] in (cfg.MEDIA, cfg.LIVE):
124
121
  # check exhaustivity of observation
125
- exhaustivity = project_functions.check_observation_exhaustivity(
126
- pj[cfg.OBSERVATIONS][obs][cfg.EVENTS], state_events_list
127
- )
122
+ exhaustivity = project_functions.check_observation_exhaustivity(pj[cfg.OBSERVATIONS][obs][cfg.EVENTS], state_events_list)
128
123
  elif pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.IMAGES:
129
124
  exhaustivity = project_functions.check_observation_exhaustivity_pictures(pj[cfg.OBSERVATIONS][obs])
130
125
 
@@ -134,11 +129,12 @@ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str,
134
129
  data, header=fields_list + indep_var_header, column_type=column_type, not_paired=not_paired
135
130
  )
136
131
  self.data = data
132
+ self.not_paired = not_paired
137
133
  self.mem_hash_obs = hash(str(self.pj[cfg.OBSERVATIONS]))
138
134
 
139
135
  else:
140
136
  obsList = observations_list.observationsList_widget(
141
- 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
142
138
  )
143
139
 
144
140
  if windows_title:
@@ -212,177 +208,3 @@ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str,
212
208
  resultStr = cfg.VIEW
213
209
 
214
210
  return resultStr, selected_observations
215
-
216
-
217
- '''
218
- def select_observations(pj: dict, mode: str, windows_title: str = "") -> Tuple[str, list]:
219
- """
220
- allow user to select observations
221
- mode: accepted values: OPEN, EDIT, SINGLE, MULTIPLE, SELECT1
222
-
223
- Args:
224
- pj (dict): BORIS project dictionary
225
- mode (str): mode for selection: OPEN, EDIT, SINGLE, MULTIPLE, SELECT1
226
- windows_title (str): title for windows
227
-
228
- Returns:
229
- str: selected mode: OPEN, EDIT, VIEW
230
- list: list of selected observations
231
- """
232
-
233
- fields_list = ["id", "date", "description", "subjects", "observation duration", "exhaustivity %", "media"]
234
- indep_var_header, column_type = [], [cfg.TEXT, cfg.TEXT, cfg.TEXT, cfg.TEXT, cfg.NUMERIC, cfg.NUMERIC, cfg.TEXT]
235
-
236
- if cfg.INDEPENDENT_VARIABLES in pj:
237
- for idx in util.sorted_keys(pj[cfg.INDEPENDENT_VARIABLES]):
238
- indep_var_header.append(pj[cfg.INDEPENDENT_VARIABLES][idx]["label"])
239
- column_type.append(pj[cfg.INDEPENDENT_VARIABLES][idx]["type"])
240
-
241
- state_events_list = [
242
- pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE]
243
- for x in pj[cfg.ETHOGRAM]
244
- if cfg.STATE in pj[cfg.ETHOGRAM][x][cfg.TYPE].upper()
245
- ]
246
-
247
- data: list = []
248
- not_paired: list = []
249
-
250
- for obs in sorted(list(pj[cfg.OBSERVATIONS].keys())):
251
- date = pj[cfg.OBSERVATIONS][obs]["date"].replace("T", " ")
252
- descr = util.eol2space(pj[cfg.OBSERVATIONS][obs][cfg.DESCRIPTION])
253
-
254
- # subjects
255
- observed_subjects = [
256
- cfg.NO_FOCAL_SUBJECT if x == "" else x for x in project_functions.extract_observed_subjects(pj, [obs])
257
- ]
258
-
259
- subjectsList = ", ".join(observed_subjects)
260
-
261
- # observed time interval
262
- interval = project_functions.observed_interval(pj[cfg.OBSERVATIONS][obs])
263
- observed_interval_str = str(interval[1] - interval[0])
264
-
265
- # media
266
- media = ""
267
- if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.MEDIA:
268
- media_list = []
269
- if pj[cfg.OBSERVATIONS][obs][cfg.FILE]:
270
- for player in sorted(pj[cfg.OBSERVATIONS][obs][cfg.FILE].keys()):
271
- for media in pj[cfg.OBSERVATIONS][obs][cfg.FILE][player]:
272
- media_list.append(f"#{player}: {media}")
273
-
274
- if len(media_list) > 8:
275
- media = " ".join(media_list)
276
- else:
277
- media = "\n".join(media_list)
278
-
279
- if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.LIVE:
280
- media = cfg.LIVE
281
-
282
- if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.IMAGES:
283
- dir_list = []
284
- for dir_path in pj[cfg.OBSERVATIONS][obs].get(cfg.DIRECTORIES_LIST, []):
285
- dir_list.append(dir_path)
286
- media = "; ".join(dir_list)
287
-
288
- # independent variables
289
- indepvar = []
290
- if cfg.INDEPENDENT_VARIABLES in pj[cfg.OBSERVATIONS][obs]:
291
- for var_label in indep_var_header:
292
- if var_label in pj[cfg.OBSERVATIONS][obs][cfg.INDEPENDENT_VARIABLES]:
293
- indepvar.append(pj[cfg.OBSERVATIONS][obs][cfg.INDEPENDENT_VARIABLES][var_label])
294
- else:
295
- indepvar.append("")
296
-
297
- # check unpaired events
298
- ok, _ = project_functions.check_state_events_obs(obs, pj[cfg.ETHOGRAM], pj[cfg.OBSERVATIONS][obs], cfg.HHMMSS)
299
- if not ok:
300
- not_paired.append(obs)
301
-
302
- # exhaustivity
303
- if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] in (cfg.MEDIA, cfg.LIVE):
304
- # check exhaustivity of observation
305
- exhaustivity = project_functions.check_observation_exhaustivity(
306
- pj[cfg.OBSERVATIONS][obs][cfg.EVENTS], state_events_list
307
- )
308
- elif pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.IMAGES:
309
- # TODO: add exhaustivity for images observation (number of coded images?)
310
- exhaustivity = project_functions.check_observation_exhaustivity_pictures(pj[cfg.OBSERVATIONS][obs])
311
-
312
- data.append([obs, date, descr, subjectsList, observed_interval_str, str(exhaustivity), media] + indepvar)
313
-
314
- obsList = observations_list.observationsList_widget(
315
- data, header=fields_list + indep_var_header, column_type=column_type, not_paired=not_paired
316
- )
317
- if windows_title:
318
- obsList.setWindowTitle(windows_title)
319
-
320
- obsList.pbOpen.setVisible(False)
321
- obsList.pbView.setVisible(False)
322
- obsList.pbEdit.setVisible(False)
323
- obsList.pbOk.setVisible(False)
324
- obsList.pbSelectAll.setVisible(False)
325
- obsList.pbUnSelectAll.setVisible(False)
326
- obsList.mode = mode
327
-
328
- if mode == cfg.OPEN:
329
- obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
330
- obsList.pbOpen.setVisible(True)
331
-
332
- if mode == cfg.VIEW:
333
- obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
334
- obsList.pbView.setVisible(True)
335
-
336
- if mode == cfg.EDIT:
337
- obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
338
- obsList.pbEdit.setVisible(True)
339
-
340
- if mode == cfg.SINGLE:
341
- obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
342
- obsList.pbOpen.setVisible(True)
343
- obsList.pbView.setVisible(True)
344
- obsList.pbEdit.setVisible(True)
345
-
346
- if mode == cfg.MULTIPLE:
347
- obsList.view.setSelectionMode(QAbstractItemView.MultiSelection)
348
- obsList.pbOk.setVisible(True)
349
- obsList.pbSelectAll.setVisible(True)
350
- obsList.pbUnSelectAll.setVisible(True)
351
-
352
- if mode == cfg.SELECT1:
353
- obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
354
- obsList.pbOk.setVisible(True)
355
-
356
- # restore window geometry
357
- gui_utilities.restore_geometry(obsList, "observations list", (900, 600))
358
-
359
- obsList.view.sortItems(0, Qt.AscendingOrder)
360
- for row in range(obsList.view.rowCount()):
361
- obsList.view.resizeRowToContents(row)
362
-
363
- selected_observations = []
364
-
365
- result = obsList.exec_()
366
-
367
- # saving window geometry in ini file
368
- gui_utilities.save_geometry(obsList, "observations list")
369
-
370
- if result:
371
- if obsList.view.selectedIndexes():
372
- for idx in obsList.view.selectedIndexes():
373
- if idx.column() == 0: # first column
374
- selected_observations.append(idx.data())
375
-
376
- if result == 0: # cancel
377
- resultStr = ""
378
- if result == 1: # select
379
- resultStr = "ok"
380
- if result == 2: # open
381
- resultStr = cfg.OPEN
382
- if result == 3: # edit
383
- resultStr = cfg.EDIT
384
- if result == 4: # view
385
- resultStr = cfg.VIEW
386
-
387
- return resultStr, selected_observations
388
- '''