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
@@ -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
 
@@ -21,15 +21,16 @@ This file is part of BORIS.
21
21
  """
22
22
 
23
23
  import logging
24
-
25
- from PyQt5.QtCore import Qt
26
- from PyQt5.QtGui import QFont
27
- from PyQt5.QtWidgets import QCheckBox, QListWidgetItem, QMessageBox
28
24
  from decimal import Decimal as dec
25
+ from typing import Optional
26
+
27
+ from PySide6.QtCore import Qt
28
+ from PySide6.QtGui import QFont
29
+ from PySide6.QtWidgets import QCheckBox, QListWidgetItem, QMessageBox
30
+
29
31
  from . import config as cfg
30
32
  from . import gui_utilities, param_panel, project_functions
31
33
  from . import utilities as util
32
- from typing import Optional
33
34
 
34
35
 
35
36
  def choose_obs_subj_behav_category(
@@ -37,33 +38,37 @@ def choose_obs_subj_behav_category(
37
38
  selected_observations: list,
38
39
  start_coding: Optional[dec] = dec("NaN"), # Union[..., None]
39
40
  end_coding: Optional[dec] = dec("NaN"),
41
+ start_interval: Optional[dec] = dec("NaN"),
42
+ end_interval: Optional[dec] = dec("NaN"),
40
43
  maxTime: Optional[dec] = None,
41
- flagShowIncludeModifiers: bool = True,
42
- flagShowExcludeBehaviorsWoEvents: bool = True,
44
+ show_include_modifiers: bool = True,
45
+ show_exclude_non_coded_behaviors: bool = True,
43
46
  by_category: bool = False,
44
47
  n_observations: int = 1,
45
48
  show_time_bin_size: bool = False,
46
49
  window_title: str = "Select subjects and behaviors",
47
- ):
50
+ show_exclude_non_coded_modifiers: bool = False,
51
+ ) -> dict:
48
52
  """
49
53
  show window for:
50
54
  - selection of subjects
51
55
  - selection of behaviors (based on selected subjects)
52
56
  - selection of time interval
53
57
  - inclusion/exclusion of modifiers
54
- - inclusion/exclusion of behaviors without events (flagShowExcludeBehaviorsWoEvents == True)
58
+ - inclusion/exclusion of behaviors without events (show_exclude_non_coded_behaviors == True)
55
59
  - selection of time bin size (show_time_bin_size == True)
56
60
 
57
61
  Args:
58
62
  selected_observations (list): List of selected observations
59
63
  ...
64
+ show_exclude_non_coded_modifiers (bool): display the Exclude non coded modifiers checkbox
60
65
 
61
66
  Returns:
62
67
  dict: {"selected subjects": selectedSubjects,
63
68
  "selected behaviors": selectedBehaviors,
64
69
  "include modifiers": True/False,
65
70
  "exclude behaviors": True/False,
66
- "time": TIME_FULL_OBS / TIME_EVENTS / TIME_ARBITRARY_INTERVAL
71
+ "time": TIME_FULL_OBS / TIME_EVENTS / TIME_ARBITRARY_INTERVAL / TIME_OBS_INTERVAL
67
72
  "start time": startTime,
68
73
  "end time": endTime
69
74
  }
@@ -77,34 +82,51 @@ def choose_obs_subj_behav_category(
77
82
  paramPanelWindow.pj = self.pj
78
83
  paramPanelWindow.extract_observed_behaviors = self.extract_observed_behaviors
79
84
 
80
- paramPanelWindow.cbIncludeModifiers.setVisible(flagShowIncludeModifiers)
81
- paramPanelWindow.cbExcludeBehaviors.setVisible(flagShowExcludeBehaviorsWoEvents)
85
+ paramPanelWindow.cbIncludeModifiers.setVisible(show_include_modifiers)
86
+ paramPanelWindow.cb_exclude_non_coded_modifiers.setVisible(show_exclude_non_coded_modifiers)
87
+ paramPanelWindow.cbExcludeBehaviors.setVisible(show_exclude_non_coded_behaviors)
82
88
  # show_time_bin_size:
83
89
  paramPanelWindow.frm_time_bin_size.setVisible(show_time_bin_size)
84
90
 
85
91
  if by_category:
86
92
  paramPanelWindow.cbIncludeModifiers.setVisible(False)
87
93
  paramPanelWindow.cbExcludeBehaviors.setVisible(False)
94
+ paramPanelWindow.cb_exclude_non_coded_modifiers.setVisible(False)
95
+
96
+ # set state of cb_exclude_non_coded_modifiers
97
+ paramPanelWindow.cb_exclude_non_coded_modifiers.setEnabled(paramPanelWindow.cbIncludeModifiers.isChecked())
88
98
 
89
99
  paramPanelWindow.media_duration = maxTime
90
100
  paramPanelWindow.start_coding = start_coding
91
101
  paramPanelWindow.end_coding = end_coding
102
+ paramPanelWindow.start_interval = start_interval
103
+ paramPanelWindow.end_interval = end_interval
92
104
 
93
- paramPanelWindow.start_time.set_format(self.timeFormat)
94
- paramPanelWindow.end_time.set_format(self.timeFormat)
105
+ if self.timeFormat == cfg.S:
106
+ paramPanelWindow.start_time.rb_seconds.setChecked(True)
107
+ paramPanelWindow.end_time.rb_seconds.setChecked(True)
108
+ if self.timeFormat == cfg.HHMMSS:
109
+ paramPanelWindow.start_time.rb_time.setChecked(True)
110
+ paramPanelWindow.end_time.rb_time.setChecked(True)
95
111
 
96
112
  if n_observations > 1:
97
113
  paramPanelWindow.frm_time_interval.setVisible(False)
98
114
  else:
99
115
  if (start_coding is None) or (start_coding.is_nan()):
116
+ paramPanelWindow.rb_observed_events.setEnabled(False)
100
117
  paramPanelWindow.frm_time_interval.setVisible(False)
101
118
  paramPanelWindow.rb_user_defined.setVisible(False)
119
+ paramPanelWindow.rb_obs_interval.setVisible(False)
102
120
  paramPanelWindow.rb_media_duration.setVisible(False)
103
121
  else:
104
122
  paramPanelWindow.frm_time_interval.setEnabled(False)
105
123
  paramPanelWindow.start_time.set_time(start_coding)
106
124
  paramPanelWindow.end_time.set_time(end_coding)
107
125
 
126
+ # check observation time interval
127
+ if start_interval is None or start_interval.is_nan() or end_interval is None or end_interval.is_nan():
128
+ paramPanelWindow.rb_obs_interval.setEnabled(False)
129
+
108
130
  if selected_observations:
109
131
  observedSubjects = project_functions.extract_observed_subjects(self.pj, selected_observations)
110
132
  else:
@@ -162,7 +184,6 @@ def choose_obs_subj_behav_category(
162
184
  categories = ["###no category###"]
163
185
 
164
186
  for category in categories:
165
-
166
187
  if category != "###no category###":
167
188
  if category == "":
168
189
  paramPanelWindow.item = QListWidgetItem("No category")
@@ -180,17 +201,14 @@ def choose_obs_subj_behav_category(
180
201
  paramPanelWindow.lwBehaviors.addItem(paramPanelWindow.item)
181
202
 
182
203
  for behavior in [self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] for x in util.sorted_keys(self.pj[cfg.ETHOGRAM])]:
183
-
184
204
  if (categories == ["###no category###"]) or (
185
205
  behavior
186
206
  in [
187
207
  self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE]
188
208
  for x in self.pj[cfg.ETHOGRAM]
189
- if cfg.BEHAVIOR_CATEGORY in self.pj[cfg.ETHOGRAM][x]
190
- and self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CATEGORY] == category
209
+ if cfg.BEHAVIOR_CATEGORY in self.pj[cfg.ETHOGRAM][x] and self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CATEGORY] == category
191
210
  ]
192
211
  ):
193
-
194
212
  paramPanelWindow.item = QListWidgetItem(behavior)
195
213
  if behavior in observedBehaviors:
196
214
  paramPanelWindow.item.setCheckState(Qt.Checked)
@@ -219,30 +237,39 @@ def choose_obs_subj_behav_category(
219
237
  logging.debug(f"selected subjects: {selectedSubjects}")
220
238
  logging.debug(f"selected behaviors: {selectedBehaviors}")
221
239
 
222
- startTime = paramPanelWindow.start_time.get_time()
223
- endTime = paramPanelWindow.end_time.get_time()
224
- """
225
- if self.timeFormat == HHMMSS:
226
- startTime = time2seconds(paramPanelWindow.teStartTime.time().toString(HHMMSSZZZ))
227
- endTime = time2seconds(paramPanelWindow.teEndTime.time().toString(HHMMSSZZZ))
228
- if self.timeFormat == S:
229
- startTime = Decimal(paramPanelWindow.dsbStartTime.value())
230
- endTime = Decimal(paramPanelWindow.dsbEndTime.value())
231
- """
232
- if startTime > endTime:
233
- QMessageBox.warning(
234
- None,
235
- cfg.programName,
236
- "The start time is after the end time",
237
- QMessageBox.Ok | QMessageBox.Default,
238
- QMessageBox.NoButton,
239
- )
240
- return {cfg.SELECTED_SUBJECTS: [], cfg.SELECTED_BEHAVIORS: []}
240
+ if paramPanelWindow.rb_user_defined.isChecked():
241
+ startTime = paramPanelWindow.start_time.get_time()
242
+ endTime = paramPanelWindow.end_time.get_time()
243
+
244
+ if startTime > endTime:
245
+ QMessageBox.warning(
246
+ None,
247
+ cfg.programName,
248
+ "The start time is after the end time",
249
+ QMessageBox.Ok | QMessageBox.Default,
250
+ QMessageBox.NoButton,
251
+ )
252
+ return {cfg.SELECTED_SUBJECTS: [], cfg.SELECTED_BEHAVIORS: []}
253
+
254
+ elif paramPanelWindow.rb_obs_interval.isChecked() and not ((start_interval is None) or start_interval.is_nan()):
255
+ startTime = paramPanelWindow.start_time.get_time()
256
+ endTime = paramPanelWindow.end_time.get_time()
257
+
258
+ else:
259
+ startTime = None
260
+ endTime = None
261
+
262
+ # if startTime is None:
263
+ # startTime = dec("NaN")
264
+ # if endTime is None:
265
+ # endTime = dec("NaN")
241
266
 
242
267
  if paramPanelWindow.rb_media_duration.isChecked():
243
268
  time_param = cfg.TIME_FULL_OBS
244
269
  if paramPanelWindow.rb_observed_events.isChecked():
245
270
  time_param = cfg.TIME_EVENTS
271
+ if paramPanelWindow.rb_obs_interval.isChecked():
272
+ time_param = cfg.TIME_OBS_INTERVAL
246
273
  if paramPanelWindow.rb_user_defined.isChecked():
247
274
  time_param = cfg.TIME_ARBITRARY_INTERVAL
248
275
 
@@ -251,6 +278,7 @@ def choose_obs_subj_behav_category(
251
278
  cfg.SELECTED_BEHAVIORS: selectedBehaviors,
252
279
  cfg.INCLUDE_MODIFIERS: paramPanelWindow.cbIncludeModifiers.isChecked(),
253
280
  cfg.EXCLUDE_BEHAVIORS: paramPanelWindow.cbExcludeBehaviors.isChecked(),
281
+ cfg.EXCLUDE_NON_CODED_MODIFIERS: paramPanelWindow.cb_exclude_non_coded_modifiers.isChecked(),
254
282
  "time": time_param,
255
283
  cfg.START_TIME: startTime,
256
284
  cfg.END_TIME: endTime,
boris/state_events.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 program is free software; you can redistribute it and/or modify
7
7
  it under the terms of the GNU General Public License as published by
@@ -17,15 +17,15 @@ Copyright 2012-2023 Olivier Friard
17
17
  along with this program; if not, write to the Free Software
18
18
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
19
  MA 02110-1301, USA.
20
- """
21
- """
20
+
22
21
  Module containing functions for state events
23
22
 
24
23
  """
25
24
 
25
+ import time
26
26
  from decimal import Decimal as dec
27
27
 
28
- from PyQt5.QtWidgets import QMessageBox
28
+ from PySide6.QtWidgets import QMessageBox, QAbstractItemView
29
29
 
30
30
  from . import config as cfg
31
31
  from . import dialog, project_functions, select_observations
@@ -82,17 +82,16 @@ def check_state_events(self, mode: str = "all") -> None:
82
82
  results.exec_()
83
83
 
84
84
 
85
- def fix_unpaired_events(self):
85
+ def fix_unpaired_events(self, silent_mode: bool = False):
86
86
  """
87
87
  fix unpaired state events
88
88
  """
89
89
 
90
90
  if self.observationId:
91
-
92
91
  r, msg = project_functions.check_state_events_obs(
93
92
  self.observationId, self.pj[cfg.ETHOGRAM], self.pj[cfg.OBSERVATIONS][self.observationId]
94
93
  )
95
- if "not PAIRED" not in msg:
94
+ if not silent_mode and "not PAIRED" not in msg:
96
95
  QMessageBox.information(
97
96
  None,
98
97
  cfg.programName,
@@ -102,33 +101,53 @@ def fix_unpaired_events(self):
102
101
  )
103
102
  return
104
103
 
105
- w = dialog.Ask_time(self.timeFormat)
104
+ w = dialog.Ask_time(0)
106
105
  w.setWindowTitle("Fix UNPAIRED state events")
107
- w.label.setText("Fix UNPAIRED events at time")
106
+ w.label.setText("Fix UNPAIRED events at time:")
108
107
 
109
- if w.exec_():
110
- fix_at_time = w.time_widget.get_time()
108
+ if not w.exec_():
109
+ return
111
110
 
112
- events_to_add = project_functions.fix_unpaired_state_events(
113
- self.pj[cfg.ETHOGRAM],
114
- self.pj[cfg.OBSERVATIONS][self.observationId],
115
- fix_at_time - dec("0.001"),
111
+ fix_at_time = w.time_widget.get_time()
112
+ if fix_at_time.is_nan():
113
+ QMessageBox.warning(
114
+ self,
115
+ cfg.programName,
116
+ ("Select a time format"),
116
117
  )
118
+ return
117
119
 
118
- if events_to_add:
119
- self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS].extend(events_to_add)
120
- self.project_changed()
121
- self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS].sort()
122
- self.load_tw_events(self.observationId)
123
- item = self.twEvents.item(
124
- [
125
- event_idx
126
- for event_idx, event in enumerate(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS])
127
- if event[cfg.TIME] == fix_at_time
128
- ][0],
129
- 0,
130
- )
131
- self.twEvents.scrollToItem(item)
120
+ events_to_add = project_functions.fix_unpaired_state_events(
121
+ self.pj[cfg.ETHOGRAM],
122
+ self.pj[cfg.OBSERVATIONS][self.observationId],
123
+ fix_at_time - dec("0.001"),
124
+ )
125
+
126
+ if events_to_add:
127
+ # determine the new frame index
128
+ if (self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.MEDIA) and self.playerType == cfg.MEDIA:
129
+ mem_time = self.getLaps()
130
+ for event in events_to_add:
131
+ if not self.seek_mediaplayer(event[0]):
132
+ time.sleep(0.1)
133
+ frame_idx = self.get_frame_index()
134
+ event[cfg.PJ_OBS_FIELDS[cfg.MEDIA][cfg.FRAME_INDEX]] = frame_idx
135
+ self.seek_mediaplayer(mem_time)
136
+
137
+ self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS].extend(events_to_add)
138
+ self.project_changed()
139
+ self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS].sort(key=lambda x: x[:3])
140
+ self.load_tw_events(self.observationId)
141
+
142
+ index = self.tv_events.model().index(
143
+ [
144
+ event_idx
145
+ for event_idx, event in enumerate(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS])
146
+ if event[cfg.PJ_OBS_FIELDS[self.playerType][cfg.TIME]] == fix_at_time
147
+ ][0],
148
+ 0,
149
+ )
150
+ self.tv_events.scrollTo(index, QAbstractItemView.EnsureVisible)
132
151
 
133
152
  # selected observations
134
153
  else:
@@ -139,11 +158,11 @@ def fix_unpaired_events(self):
139
158
  # check if state events are paired
140
159
  out: str = ""
141
160
  for obs_id in selected_observations:
142
- r, msg = project_functions.check_state_events_obs(
143
- obs_id, self.pj[cfg.ETHOGRAM], self.pj[cfg.OBSERVATIONS][obs_id]
144
- )
161
+ r, msg = project_functions.check_state_events_obs(obs_id, self.pj[cfg.ETHOGRAM], self.pj[cfg.OBSERVATIONS][obs_id])
145
162
  if "NOT PAIRED" in msg.upper():
163
+ # determine max time of events
146
164
  fix_at_time = max(x[0] for x in self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS])
165
+ # list of events to add to fix unpaired events
147
166
  events_to_add = project_functions.fix_unpaired_state_events(
148
167
  self.pj[cfg.ETHOGRAM], self.pj[cfg.OBSERVATIONS][obs_id], fix_at_time
149
168
  )
@@ -152,9 +171,7 @@ def fix_unpaired_events(self):
152
171
  self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS].extend(events_to_add)
153
172
 
154
173
  # check if modified obs if fixed
155
- r, msg = project_functions.check_state_events_obs(
156
- obs_id, self.pj[cfg.ETHOGRAM], self.pj[cfg.OBSERVATIONS][obs_id]
157
- )
174
+ r, msg = project_functions.check_state_events_obs(obs_id, self.pj[cfg.ETHOGRAM], self.pj[cfg.OBSERVATIONS][obs_id])
158
175
  if "NOT PAIRED" in msg.upper():
159
176
  out += f"The observation <b>{obs_id}</b> can not be automatically fixed.<br><br>"
160
177
  self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS] = events_backup
boris/subjects_pad.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 program is free software; you can redistribute it and/or modify
7
7
  it under the terms of the GNU General Public License as published by
@@ -19,19 +19,17 @@ Copyright 2012-2023 Olivier Friard
19
19
  MA 02110-1301, USA.
20
20
  """
21
21
 
22
- from PyQt5.QtCore import *
23
- from PyQt5.QtGui import *
24
- from PyQt5.QtWidgets import *
22
+ from PySide6.QtCore import Signal, QRect, QEvent, Qt
23
+ from PySide6.QtWidgets import QGridLayout, QPushButton, QHBoxLayout, QWidget
25
24
 
26
25
  from . import config as cfg
27
26
  from . import utilities as util
28
27
 
29
28
 
30
29
  class SubjectsPad(QWidget):
31
-
32
- clickSignal = pyqtSignal(str)
33
- sendEventSignal = pyqtSignal(QEvent)
34
- close_signal = pyqtSignal(QRect)
30
+ clickSignal = Signal(str)
31
+ sendEventSignal = Signal(QEvent)
32
+ close_signal = Signal(QRect)
35
33
 
36
34
  def __init__(self, pj, filtered_subjects, parent=None):
37
35
  super(SubjectsPad, self).__init__(parent)
@@ -63,7 +61,6 @@ class SubjectsPad(QWidget):
63
61
  c += 1
64
62
 
65
63
  def addWidget(self, subject, i, j):
66
-
67
64
  self.grid.addWidget(Button(), i, j)
68
65
  index = self.grid.count() - 1
69
66
  widget = self.grid.itemAt(index).widget()
@@ -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,10 +22,9 @@ This file is part of BORIS.
22
22
 
23
23
  import logging
24
24
  import pathlib as pl
25
- from decimal import Decimal as dec
26
25
 
27
- from PyQt5.QtWidgets import QFileDialog, QMessageBox
28
- from PyQt5.QtGui import QFont, QTextOption, QTextCursor
26
+ from PySide6.QtWidgets import QFileDialog, QMessageBox
27
+ from PySide6.QtGui import QFont, QTextOption, QTextCursor
29
28
 
30
29
  from . import config as cfg
31
30
  from . import (
@@ -43,6 +42,8 @@ def synthetic_time_budget(self) -> None:
43
42
  Synthetic time budget
44
43
  """
45
44
 
45
+ logging.debug("synthetic time budget function")
46
+
46
47
  _, selected_observations = select_observations.select_observations2(
47
48
  self, mode=cfg.MULTIPLE, windows_title="Select observations for synthetic time budget"
48
49
  )
@@ -59,21 +60,26 @@ def synthetic_time_budget(self) -> None:
59
60
  if not_ok or not selected_observations:
60
61
  return
61
62
 
62
- max_media_duration_all_obs, _ = observation_operations.media_duration(
63
- self.pj[cfg.OBSERVATIONS], selected_observations
64
- )
63
+ max_media_duration_all_obs, _ = observation_operations.media_duration(self.pj[cfg.OBSERVATIONS], selected_observations)
65
64
 
66
65
  start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
67
66
 
67
+ start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
68
+
68
69
  synth_tb_param = select_subj_behav.choose_obs_subj_behav_category(
69
70
  self,
70
71
  selected_observations,
71
72
  start_coding=start_coding,
72
73
  end_coding=end_coding,
74
+ # start_interval=start_interval,
75
+ # end_interval=end_interval,
76
+ start_interval=None,
77
+ end_interval=None,
73
78
  maxTime=max_media_duration_all_obs,
74
- flagShowExcludeBehaviorsWoEvents=False,
79
+ show_exclude_non_coded_behaviors=False,
75
80
  by_category=False,
76
81
  n_observations=len(selected_observations),
82
+ show_exclude_non_coded_modifiers=True,
77
83
  )
78
84
 
79
85
  if synth_tb_param == {}:
@@ -87,9 +93,9 @@ def synthetic_time_budget(self) -> None:
87
93
  if not start_coding.is_nan():
88
94
  cancel_pressed, synth_tb_param[cfg.EXCLUDED_BEHAVIORS] = self.filter_behaviors(
89
95
  title="Select behaviors to exclude from the total time",
90
- text=("The duration of the selected behaviors will " "be subtracted from the total time"),
96
+ text="The duration of the selected behaviors will be subtracted from the total time",
91
97
  table="",
92
- behavior_type=[cfg.STATE_EVENT],
98
+ behavior_type=cfg.STATE_EVENT_TYPES,
93
99
  )
94
100
  if cancel_pressed:
95
101
  return
@@ -133,21 +139,22 @@ def synthetic_time_budget(self) -> None:
133
139
 
134
140
  output_format = cfg.FILE_NAME_SUFFIX[filter_]
135
141
 
136
- if filter_ != cfg.TEXT_FILE and pl.Path(file_name).suffix != "." + output_format:
137
- file_name = str(pl.Path(file_name)) + "." + output_format
142
+ if pl.Path(file_name).suffix != "." + output_format:
143
+ if filter_ != cfg.TEXT_FILE:
144
+ file_name = str(pl.Path(file_name)) + "." + output_format
145
+ else:
146
+ file_name = str(pl.Path(file_name))
138
147
  if pl.Path(file_name).is_file():
139
148
  if (
140
- dialog.MessageDialog(
141
- cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE]
142
- )
149
+ dialog.MessageDialog(cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE])
143
150
  == cfg.CANCEL
144
151
  ):
145
152
  return
146
153
 
147
154
  with open(file_name, "wb") as f:
148
- if filter_ in [cfg.TSV, cfg.CSV, cfg.HTML, cfg.TEXT_FILE]:
155
+ if filter_ in (cfg.TSV, cfg.CSV, cfg.HTML, cfg.TEXT_FILE):
149
156
  f.write(str.encode(data_report.export(output_format)))
150
- if output_format in [cfg.ODS, cfg.XLSX, cfg.XLS]:
157
+ if filter_ in (cfg.ODS, cfg.XLSX, cfg.XLS):
151
158
  f.write(data_report.export(output_format))
152
159
 
153
160
 
@@ -178,6 +185,8 @@ def synthetic_binned_time_budget(self) -> None:
178
185
 
179
186
  start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
180
187
 
188
+ start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
189
+
181
190
  # exit with message if events do not have timestamp
182
191
  if start_coding.is_nan():
183
192
  QMessageBox.critical(
@@ -194,11 +203,16 @@ def synthetic_binned_time_budget(self) -> None:
194
203
  selected_observations,
195
204
  start_coding=start_coding,
196
205
  end_coding=end_coding,
206
+ # start_interval=start_interval,
207
+ # end_interval=end_interval,
208
+ start_interval=None,
209
+ end_interval=None,
197
210
  maxTime=max_media_duration_all_obs,
198
- flagShowExcludeBehaviorsWoEvents=False,
211
+ show_exclude_non_coded_behaviors=False,
199
212
  by_category=False,
200
213
  n_observations=len(selected_observations),
201
214
  show_time_bin_size=True,
215
+ show_exclude_non_coded_modifiers=True,
202
216
  )
203
217
 
204
218
  if synth_tb_param == {}:
@@ -211,9 +225,9 @@ def synthetic_binned_time_budget(self) -> None:
211
225
  # ask for excluding behaviors durations from total time
212
226
  cancel_pressed, synth_tb_param[cfg.EXCLUDED_BEHAVIORS] = self.filter_behaviors(
213
227
  title="Select behaviors to exclude",
214
- text=("The duration of the selected behaviors will " "be subtracted from the total time"),
228
+ text=("The duration of the selected behaviors will be subtracted from the total time"),
215
229
  table="",
216
- behavior_type=[cfg.STATE_EVENT],
230
+ behavior_type=cfg.STATE_EVENT_TYPES,
217
231
  )
218
232
  if cancel_pressed:
219
233
  return
@@ -250,24 +264,27 @@ def synthetic_binned_time_budget(self) -> None:
250
264
  ]
251
265
 
252
266
  file_name, filter_ = QFileDialog().getSaveFileName(
253
- self, "Synthetic time budget with time bin", "", ";;".join(file_formats)
267
+ self, "Save the Synthetic time budget with time bin", "", ";;".join(file_formats)
254
268
  )
255
269
  if not file_name:
256
270
  return
257
271
 
258
- if filter_ != cfg.TEXT_FILE and pl.Path(file_name).suffix != "." + cfg.FILE_NAME_SUFFIX[filter_]:
259
- file_name = str(pl.Path(file_name)) + "." + cfg.FILE_NAME_SUFFIX[filter_]
272
+ output_format = cfg.FILE_NAME_SUFFIX[filter_]
273
+
274
+ if pl.Path(file_name).suffix != "." + output_format:
275
+ if filter_ != cfg.TEXT_FILE:
276
+ file_name = str(pl.Path(file_name)) + "." + output_format
277
+ else:
278
+ file_name = str(pl.Path(file_name))
260
279
  if pl.Path(file_name).is_file():
261
280
  if (
262
- dialog.MessageDialog(
263
- cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE]
264
- )
281
+ dialog.MessageDialog(cfg.programName, f"The file {file_name} already exists.", (cfg.CANCEL, cfg.OVERWRITE))
265
282
  == cfg.CANCEL
266
283
  ):
267
284
  return
268
285
 
269
286
  with open(file_name, "wb") as f:
270
287
  if filter_ in (cfg.TSV, cfg.CSV, cfg.HTML, cfg.TEXT_FILE):
271
- f.write(str.encode(data_report.export(cfg.FILE_NAME_SUFFIX[filter_])))
288
+ f.write(str.encode(data_report.export(output_format)))
272
289
  if filter_ in (cfg.ODS, cfg.XLSX, cfg.XLS):
273
- f.write(data_report.export(cfg.FILE_NAME_SUFFIX[filter_]))
290
+ f.write(data_report.export(output_format))