boris-behav-obs 8.16.5__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 -36
  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 +102 -50
  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 +27 -7
  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 +128 -35
  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 +42 -26
  92. boris/time_budget_functions.py +169 -169
  93. boris/time_budget_widget.py +77 -89
  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.5.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.5.dist-info/LICENSE.TXT +0 -674
  121. boris_behav_obs-8.16.5.dist-info/METADATA +0 -134
  122. boris_behav_obs-8.16.5.dist-info/RECORD +0 -107
  123. boris_behav_obs-8.16.5.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.5.dist-info → boris_behav_obs-9.7.1.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,8 +22,8 @@ This file is part of BORIS.
22
22
 
23
23
  import re
24
24
 
25
- from PyQt5.QtCore import Qt, QEvent
26
- from PyQt5.QtWidgets import (
25
+ from PySide6.QtCore import Qt, QEvent
26
+ from PySide6.QtWidgets import (
27
27
  QDialog,
28
28
  QVBoxLayout,
29
29
  QLabel,
@@ -32,14 +32,21 @@ from PyQt5.QtWidgets import (
32
32
  QLineEdit,
33
33
  QPushButton,
34
34
  QListWidgetItem,
35
-
36
- QSizePolicy,QSpacerItem, QAbstractItemView, QMessageBox)
35
+ QSizePolicy,
36
+ QSpacerItem,
37
+ QAbstractItemView,
38
+ QMessageBox,
39
+ )
37
40
 
38
41
  from . import config as cfg
39
42
  from . import utilities as util
40
43
 
41
44
 
42
45
  class ModifiersList(QDialog):
46
+ """
47
+ class for selection the modifier(s)
48
+ """
49
+
43
50
  def __init__(self, code: str, modifiers_dict: dict, currentModifier: str):
44
51
  super().__init__()
45
52
  self.setWindowTitle(cfg.programName)
@@ -50,34 +57,36 @@ class ModifiersList(QDialog):
50
57
 
51
58
  V1layout = QVBoxLayout()
52
59
  label = QLabel()
53
- label.setText(
54
- f"Choose the modifier{'s' * (len(self.modifiers_dict) > 1)} for <b>{code}</b> behavior"
55
- )
60
+ label.setText(f"Choose the modifier{'s' * (len(self.modifiers_dict) > 1)} for <b>{code}</b> behavior")
56
61
  V1layout.addWidget(label)
57
62
 
58
63
  Hlayout = QHBoxLayout()
59
64
  self.modifiersSetNumber = 0
60
65
 
61
66
  for idx in util.sorted_keys(modifiers_dict):
62
- if self.modifiers_dict[idx]["type"] not in [
67
+ if self.modifiers_dict[idx]["type"] not in (
63
68
  cfg.SINGLE_SELECTION,
64
69
  cfg.MULTI_SELECTION,
65
70
  cfg.NUMERIC_MODIFIER,
66
- ]:
71
+ ):
67
72
  continue
68
73
 
69
74
  V2layout = QVBoxLayout()
70
75
 
71
76
  self.modifiersSetNumber += 1
72
77
 
73
- lb = QLabel()
74
- lb.setText(f"Modifier <b>{self.modifiers_dict[idx]['name']}</b>")
75
- 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)
76
81
 
77
- if self.modifiers_dict[idx]["type"] in [
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)
85
+
86
+ if self.modifiers_dict[idx]["type"] in (
78
87
  cfg.SINGLE_SELECTION,
79
88
  cfg.MULTI_SELECTION,
80
- ]:
89
+ ):
81
90
  lw = QListWidget()
82
91
  self.modifiers_dict[idx]["widget"] = lw
83
92
  lw.setObjectName(f"lw_modifiers_({self.modifiers_dict[idx]['type']})")
@@ -95,9 +104,7 @@ class ModifiersList(QDialog):
95
104
 
96
105
  # previously selected
97
106
  try:
98
- if currentModifierList != [""] and re.sub(
99
- " \(.\)", "", modifier
100
- ) in currentModifierList[int(idx)].split(","):
107
+ if currentModifierList != [""] and re.sub(r" \(.\)", "", modifier) in currentModifierList[int(idx)].split(","):
101
108
  item.setCheckState(Qt.Checked)
102
109
  except Exception: # for old projects due to a fixed bug
103
110
  pass
@@ -106,11 +113,7 @@ class ModifiersList(QDialog):
106
113
 
107
114
  if self.modifiers_dict[idx]["type"] == cfg.SINGLE_SELECTION:
108
115
  try:
109
- if (
110
- currentModifierList != [""]
111
- and re.sub(" \(.\)", "", modifier)
112
- == currentModifierList[int(idx)]
113
- ):
116
+ if currentModifierList != [""] and re.sub(r" \(.\)", "", modifier) == currentModifierList[int(idx)]:
114
117
  item.setSelected(True)
115
118
  except Exception: # for old projects due to a fixed bug
116
119
  pass
@@ -119,19 +122,15 @@ class ModifiersList(QDialog):
119
122
  if self.modifiers_dict[idx]["type"] in [cfg.NUMERIC_MODIFIER]:
120
123
  le = QLineEdit()
121
124
  self.modifiers_dict[idx]["widget"] = le
125
+ le.setObjectName(f"le_modifiers_({self.modifiers_dict[idx]['type']})")
122
126
 
123
- if (
124
- currentModifierList != [""]
125
- and currentModifierList[int(idx)] != "None"
126
- ):
127
+ if currentModifierList != [""] and currentModifierList[int(idx)] != "None":
127
128
  le.setText(currentModifierList[int(idx)])
128
129
 
129
130
  V2layout.addWidget(le)
130
131
 
131
132
  # vertical spacer
132
- spacerItem = QSpacerItem(
133
- 20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding
134
- )
133
+ spacerItem = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
135
134
  V2layout.addItem(spacerItem)
136
135
 
137
136
  Hlayout.addLayout(V2layout)
@@ -169,13 +168,15 @@ class ModifiersList(QDialog):
169
168
  return False
170
169
 
171
170
  # accept and close dialog if enter pressed
172
- if (
173
- ek == Qt.Key_Enter or ek == Qt.Key_Return
174
- ): # enter or enter from numeric pad
175
- self.accept()
171
+ if ek == Qt.Key_Enter or ek == Qt.Key_Return: # enter or enter from numeric pad
172
+ if not self.pbOK_clicked():
173
+ return False
176
174
  return True
177
175
 
176
+ modifiersSetIndex = 0
178
177
  for widget in self.children():
178
+ if "_modifiers" in widget.objectName():
179
+ modifiersSetIndex = modifiersSetIndex + 1
179
180
  if "lw_modifiers" in widget.objectName():
180
181
  if self.modifiersSetNumber == 1:
181
182
  # check if modifiers have code
@@ -184,32 +185,16 @@ class ModifiersList(QDialog):
184
185
  break
185
186
  else:
186
187
  # modifiers have no associated code: the modifier starting with hit key will be selected
187
- if ek not in [Qt.Key_Down, Qt.Key_Up]:
188
- if (
189
- ek == Qt.Key_Space
190
- and f"({cfg.MULTI_SELECTION})"
191
- in widget.objectName()
192
- ): # checking using SPACE bar
193
- if (
194
- widget.item(widget.currentRow()).checkState()
195
- == Qt.Checked
196
- ):
197
- widget.item(widget.currentRow()).setCheckState(
198
- Qt.Unchecked
199
- )
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
190
+ if widget.item(widget.currentRow()).checkState() == Qt.Checked:
191
+ widget.item(widget.currentRow()).setCheckState(Qt.Unchecked)
200
192
  else:
201
- widget.item(widget.currentRow()).setCheckState(
202
- Qt.Checked
203
- )
193
+ widget.item(widget.currentRow()).setCheckState(Qt.Checked)
204
194
 
205
195
  else:
206
196
  for index in range(widget.count()):
207
- if (
208
- widget.item(index)
209
- .text()
210
- .upper()
211
- .startswith(ek_text.upper())
212
- ):
197
+ if widget.item(index).text().upper().startswith(ek_text.upper()):
213
198
  widget.setCurrentRow(index)
214
199
  widget.scrollToItem(
215
200
  widget.item(index),
@@ -218,10 +203,7 @@ class ModifiersList(QDialog):
218
203
  return True
219
204
  else: # up / down keys
220
205
  try:
221
- if (
222
- ek == Qt.Key_Down
223
- and widget.currentRow() < widget.count() - 1
224
- ):
206
+ if ek == Qt.Key_Down and widget.currentRow() < widget.count() - 1:
225
207
  widget.setCurrentRow(widget.currentRow() + 1)
226
208
  if ek == Qt.Key_Up and widget.currentRow() > 0:
227
209
  widget.setCurrentRow(widget.currentRow() - 1)
@@ -229,17 +211,19 @@ class ModifiersList(QDialog):
229
211
  return
230
212
 
231
213
  for index in range(widget.count()):
214
+ # check function kesy (F1, F2...)
232
215
  if ek in cfg.function_keys:
233
- if (
234
- f"({cfg.function_keys[ek]})"
235
- in widget.item(index).text().upper()
236
- ):
216
+ if f"({cfg.function_keys[ek]})" in widget.item(index).text().upper():
237
217
  if f"({cfg.SINGLE_SELECTION})" in widget.objectName():
238
218
  widget.item(index).setSelected(True)
239
219
  # close dialog if one set of modifiers
240
220
  if self.modifiersSetNumber == 1:
241
221
  self.accept()
242
222
  return True
223
+ # else move to next set of mofifiers
224
+ elif modifiersSetIndex != self.modifiersSetNumber:
225
+ widget.parent().focusNextChild()
226
+ return True
243
227
 
244
228
  if f"({cfg.MULTI_SELECTION})" in widget.objectName():
245
229
  if widget.item(index).checkState() == Qt.Checked:
@@ -254,6 +238,10 @@ class ModifiersList(QDialog):
254
238
  if self.modifiersSetNumber == 1:
255
239
  self.accept()
256
240
  return True
241
+ # else move to next set of mofifiers
242
+ elif modifiersSetIndex != self.modifiersSetNumber:
243
+ widget.parent().focusNextChild()
244
+ return True
257
245
 
258
246
  if f"({cfg.MULTI_SELECTION})" in widget.objectName():
259
247
  if widget.item(index).checkState() == Qt.Checked:
@@ -270,7 +258,7 @@ class ModifiersList(QDialog):
270
258
  get modifiers
271
259
  returns list of selected modifiers
272
260
  """
273
-
261
+
274
262
  for idx in util.sorted_keys(self.modifiers_dict):
275
263
  if self.modifiers_dict[idx]["type"] in [
276
264
  cfg.SINGLE_SELECTION,
@@ -281,13 +269,10 @@ class ModifiersList(QDialog):
281
269
 
282
270
  if self.modifiers_dict[idx]["type"] == cfg.MULTI_SELECTION:
283
271
  for j in range(self.modifiers_dict[idx]["widget"].count()):
284
- if (
285
- self.modifiers_dict[idx]["widget"].item(j).checkState()
286
- == Qt.Checked
287
- ):
272
+ if self.modifiers_dict[idx]["widget"].item(j).checkState() == Qt.Checked:
288
273
  self.modifiers_dict[idx]["selected"].append(
289
274
  re.sub(
290
- " \(.*\)",
275
+ r" \(.*\)",
291
276
  "",
292
277
  self.modifiers_dict[idx]["widget"].item(j).text(),
293
278
  )
@@ -298,30 +283,19 @@ class ModifiersList(QDialog):
298
283
 
299
284
  if self.modifiers_dict[idx]["type"] == cfg.SINGLE_SELECTION:
300
285
  for item in self.modifiers_dict[idx]["widget"].selectedItems():
301
- self.modifiers_dict[idx]["selected"].append(
302
- re.sub(" \(.*\)", "", item.text())
303
- )
286
+ self.modifiers_dict[idx]["selected"].append(re.sub(r" \(.*\)", "", item.text()))
304
287
 
305
288
  if self.modifiers_dict[idx]["type"] == cfg.NUMERIC_MODIFIER:
306
289
  self.modifiers_dict[idx]["selected"] = (
307
- self.modifiers_dict[idx]["widget"].text()
308
- if self.modifiers_dict[idx]["widget"].text()
309
- else "None"
290
+ self.modifiers_dict[idx]["widget"].text() if self.modifiers_dict[idx]["widget"].text() else "None"
310
291
  )
311
- """
312
- for widget in self.children():
313
- if widget.objectName() == "lw_modifiers_classic":
314
- for item in widget.selectedItems():
315
- modifiers.append(re.sub(" \(.*\)", "", item.text()))
316
- if widget.objectName() == "lw_modifiers_from_set":
317
- for idx in range(widget.count()):
318
- if widget.item(idx).checkState() == Qt.Checked:
319
- modifiers.append(widget.item(idx).text())
320
- """
321
292
 
322
293
  return self.modifiers_dict
323
294
 
324
295
  def pbOK_clicked(self):
296
+ """
297
+ OK button is clicked
298
+ """
325
299
  for idx in util.sorted_keys(self.modifiers_dict):
326
300
  if self.modifiers_dict[idx]["type"] == cfg.NUMERIC_MODIFIER:
327
301
  if self.modifiers_dict[idx]["widget"].text():
@@ -331,10 +305,8 @@ class ModifiersList(QDialog):
331
305
  QMessageBox.warning(
332
306
  self,
333
307
  cfg.programName,
334
- "<b>{}</b> is not a numeric value".format(
335
- self.modifiers_dict[idx]["widget"].text()
336
- ),
308
+ f"<b>{self.modifiers_dict[idx]['widget'].text()}</b> is not a numeric value",
337
309
  )
338
- return
310
+ return False
339
311
 
340
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
+
24
+ import logging
23
25
  from typing import Tuple
24
- from PyQt5.QtCore import Qt
25
- from PyQt5.QtWidgets import QAbstractItemView
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
- '''