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
@@ -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
@@ -22,9 +22,10 @@ Module containing functions for state events
22
22
 
23
23
  """
24
24
 
25
+ import time
25
26
  from decimal import Decimal as dec
26
27
 
27
- from PyQt5.QtWidgets import QMessageBox
28
+ from PySide6.QtWidgets import QMessageBox, QAbstractItemView
28
29
 
29
30
  from . import config as cfg
30
31
  from . import dialog, project_functions, select_observations
@@ -81,17 +82,16 @@ def check_state_events(self, mode: str = "all") -> None:
81
82
  results.exec_()
82
83
 
83
84
 
84
- def fix_unpaired_events(self):
85
+ def fix_unpaired_events(self, silent_mode: bool = False):
85
86
  """
86
87
  fix unpaired state events
87
88
  """
88
89
 
89
90
  if self.observationId:
90
-
91
91
  r, msg = project_functions.check_state_events_obs(
92
92
  self.observationId, self.pj[cfg.ETHOGRAM], self.pj[cfg.OBSERVATIONS][self.observationId]
93
93
  )
94
- if "not PAIRED" not in msg:
94
+ if not silent_mode and "not PAIRED" not in msg:
95
95
  QMessageBox.information(
96
96
  None,
97
97
  cfg.programName,
@@ -101,33 +101,53 @@ def fix_unpaired_events(self):
101
101
  )
102
102
  return
103
103
 
104
- w = dialog.Ask_time(self.timeFormat)
104
+ w = dialog.Ask_time(0)
105
105
  w.setWindowTitle("Fix UNPAIRED state events")
106
- w.label.setText("Fix UNPAIRED events at time")
106
+ w.label.setText("Fix UNPAIRED events at time:")
107
107
 
108
- if w.exec_():
109
- fix_at_time = w.time_widget.get_time()
108
+ if not w.exec_():
109
+ return
110
110
 
111
- events_to_add = project_functions.fix_unpaired_state_events(
112
- self.pj[cfg.ETHOGRAM],
113
- self.pj[cfg.OBSERVATIONS][self.observationId],
114
- 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"),
115
117
  )
118
+ return
116
119
 
117
- if events_to_add:
118
- self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS].extend(events_to_add)
119
- self.project_changed()
120
- self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS].sort()
121
- self.load_tw_events(self.observationId)
122
- item = self.twEvents.item(
123
- [
124
- event_idx
125
- for event_idx, event in enumerate(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS])
126
- if event[cfg.TIME] == fix_at_time
127
- ][0],
128
- 0,
129
- )
130
- 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)
131
151
 
132
152
  # selected observations
133
153
  else:
@@ -138,11 +158,11 @@ def fix_unpaired_events(self):
138
158
  # check if state events are paired
139
159
  out: str = ""
140
160
  for obs_id in selected_observations:
141
- r, msg = project_functions.check_state_events_obs(
142
- obs_id, self.pj[cfg.ETHOGRAM], self.pj[cfg.OBSERVATIONS][obs_id]
143
- )
161
+ r, msg = project_functions.check_state_events_obs(obs_id, self.pj[cfg.ETHOGRAM], self.pj[cfg.OBSERVATIONS][obs_id])
144
162
  if "NOT PAIRED" in msg.upper():
163
+ # determine max time of events
145
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
146
166
  events_to_add = project_functions.fix_unpaired_state_events(
147
167
  self.pj[cfg.ETHOGRAM], self.pj[cfg.OBSERVATIONS][obs_id], fix_at_time
148
168
  )
@@ -151,9 +171,7 @@ def fix_unpaired_events(self):
151
171
  self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS].extend(events_to_add)
152
172
 
153
173
  # check if modified obs if fixed
154
- r, msg = project_functions.check_state_events_obs(
155
- obs_id, self.pj[cfg.ETHOGRAM], self.pj[cfg.OBSERVATIONS][obs_id]
156
- )
174
+ r, msg = project_functions.check_state_events_obs(obs_id, self.pj[cfg.ETHOGRAM], self.pj[cfg.OBSERVATIONS][obs_id])
157
175
  if "NOT PAIRED" in msg.upper():
158
176
  out += f"The observation <b>{obs_id}</b> can not be automatically fixed.<br><br>"
159
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,18 +19,17 @@ Copyright 2012-2023 Olivier Friard
19
19
  MA 02110-1301, USA.
20
20
  """
21
21
 
22
- from PyQt5.QtCore import pyqtSignal, QRect, QEvent, Qt
23
- from PyQt5.QtWidgets import QGridLayout, QPushButton, QHBoxLayout, QWidget
22
+ from PySide6.QtCore import Signal, QRect, QEvent, Qt
23
+ from PySide6.QtWidgets import QGridLayout, QPushButton, QHBoxLayout, QWidget
24
24
 
25
25
  from . import config as cfg
26
26
  from . import utilities as util
27
27
 
28
28
 
29
29
  class SubjectsPad(QWidget):
30
-
31
- clickSignal = pyqtSignal(str)
32
- sendEventSignal = pyqtSignal(QEvent)
33
- close_signal = pyqtSignal(QRect)
30
+ clickSignal = Signal(str)
31
+ sendEventSignal = Signal(QEvent)
32
+ close_signal = Signal(QRect)
34
33
 
35
34
  def __init__(self, pj, filtered_subjects, parent=None):
36
35
  super(SubjectsPad, self).__init__(parent)
@@ -62,7 +61,6 @@ class SubjectsPad(QWidget):
62
61
  c += 1
63
62
 
64
63
  def addWidget(self, subject, i, j):
65
-
66
64
  self.grid.addWidget(Button(), i, j)
67
65
  index = self.grid.count() - 1
68
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
 
@@ -23,8 +23,8 @@ This file is part of BORIS.
23
23
  import logging
24
24
  import pathlib as pl
25
25
 
26
- from PyQt5.QtWidgets import QFileDialog, QMessageBox
27
- from PyQt5.QtGui import QFont, QTextOption, QTextCursor
26
+ from PySide6.QtWidgets import QFileDialog, QMessageBox
27
+ from PySide6.QtGui import QFont, QTextOption, QTextCursor
28
28
 
29
29
  from . import config as cfg
30
30
  from . import (
@@ -60,21 +60,26 @@ def synthetic_time_budget(self) -> None:
60
60
  if not_ok or not selected_observations:
61
61
  return
62
62
 
63
- max_media_duration_all_obs, _ = observation_operations.media_duration(
64
- self.pj[cfg.OBSERVATIONS], selected_observations
65
- )
63
+ max_media_duration_all_obs, _ = observation_operations.media_duration(self.pj[cfg.OBSERVATIONS], selected_observations)
66
64
 
67
65
  start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
68
66
 
67
+ start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
68
+
69
69
  synth_tb_param = select_subj_behav.choose_obs_subj_behav_category(
70
70
  self,
71
71
  selected_observations,
72
72
  start_coding=start_coding,
73
73
  end_coding=end_coding,
74
+ # start_interval=start_interval,
75
+ # end_interval=end_interval,
76
+ start_interval=None,
77
+ end_interval=None,
74
78
  maxTime=max_media_duration_all_obs,
75
- flagShowExcludeBehaviorsWoEvents=False,
79
+ show_exclude_non_coded_behaviors=False,
76
80
  by_category=False,
77
81
  n_observations=len(selected_observations),
82
+ show_exclude_non_coded_modifiers=True,
78
83
  )
79
84
 
80
85
  if synth_tb_param == {}:
@@ -90,7 +95,7 @@ def synthetic_time_budget(self) -> None:
90
95
  title="Select behaviors to exclude from the total time",
91
96
  text="The duration of the selected behaviors will be subtracted from the total time",
92
97
  table="",
93
- behavior_type=[cfg.STATE_EVENT],
98
+ behavior_type=cfg.STATE_EVENT_TYPES,
94
99
  )
95
100
  if cancel_pressed:
96
101
  return
@@ -134,21 +139,22 @@ def synthetic_time_budget(self) -> None:
134
139
 
135
140
  output_format = cfg.FILE_NAME_SUFFIX[filter_]
136
141
 
137
- if filter_ != cfg.TEXT_FILE and pl.Path(file_name).suffix != "." + output_format:
138
- 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))
139
147
  if pl.Path(file_name).is_file():
140
148
  if (
141
- dialog.MessageDialog(
142
- cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE]
143
- )
149
+ dialog.MessageDialog(cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE])
144
150
  == cfg.CANCEL
145
151
  ):
146
152
  return
147
153
 
148
154
  with open(file_name, "wb") as f:
149
- 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):
150
156
  f.write(str.encode(data_report.export(output_format)))
151
- if output_format in [cfg.ODS, cfg.XLSX, cfg.XLS]:
157
+ if filter_ in (cfg.ODS, cfg.XLSX, cfg.XLS):
152
158
  f.write(data_report.export(output_format))
153
159
 
154
160
 
@@ -179,6 +185,8 @@ def synthetic_binned_time_budget(self) -> None:
179
185
 
180
186
  start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
181
187
 
188
+ start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
189
+
182
190
  # exit with message if events do not have timestamp
183
191
  if start_coding.is_nan():
184
192
  QMessageBox.critical(
@@ -195,11 +203,16 @@ def synthetic_binned_time_budget(self) -> None:
195
203
  selected_observations,
196
204
  start_coding=start_coding,
197
205
  end_coding=end_coding,
206
+ # start_interval=start_interval,
207
+ # end_interval=end_interval,
208
+ start_interval=None,
209
+ end_interval=None,
198
210
  maxTime=max_media_duration_all_obs,
199
- flagShowExcludeBehaviorsWoEvents=False,
211
+ show_exclude_non_coded_behaviors=False,
200
212
  by_category=False,
201
213
  n_observations=len(selected_observations),
202
214
  show_time_bin_size=True,
215
+ show_exclude_non_coded_modifiers=True,
203
216
  )
204
217
 
205
218
  if synth_tb_param == {}:
@@ -212,9 +225,9 @@ def synthetic_binned_time_budget(self) -> None:
212
225
  # ask for excluding behaviors durations from total time
213
226
  cancel_pressed, synth_tb_param[cfg.EXCLUDED_BEHAVIORS] = self.filter_behaviors(
214
227
  title="Select behaviors to exclude",
215
- 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"),
216
229
  table="",
217
- behavior_type=[cfg.STATE_EVENT],
230
+ behavior_type=cfg.STATE_EVENT_TYPES,
218
231
  )
219
232
  if cancel_pressed:
220
233
  return
@@ -251,24 +264,27 @@ def synthetic_binned_time_budget(self) -> None:
251
264
  ]
252
265
 
253
266
  file_name, filter_ = QFileDialog().getSaveFileName(
254
- self, "Synthetic time budget with time bin", "", ";;".join(file_formats)
267
+ self, "Save the Synthetic time budget with time bin", "", ";;".join(file_formats)
255
268
  )
256
269
  if not file_name:
257
270
  return
258
271
 
259
- if filter_ != cfg.TEXT_FILE and pl.Path(file_name).suffix != "." + cfg.FILE_NAME_SUFFIX[filter_]:
260
- 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))
261
279
  if pl.Path(file_name).is_file():
262
280
  if (
263
- dialog.MessageDialog(
264
- cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE]
265
- )
281
+ dialog.MessageDialog(cfg.programName, f"The file {file_name} already exists.", (cfg.CANCEL, cfg.OVERWRITE))
266
282
  == cfg.CANCEL
267
283
  ):
268
284
  return
269
285
 
270
286
  with open(file_name, "wb") as f:
271
287
  if filter_ in (cfg.TSV, cfg.CSV, cfg.HTML, cfg.TEXT_FILE):
272
- f.write(str.encode(data_report.export(cfg.FILE_NAME_SUFFIX[filter_])))
288
+ f.write(str.encode(data_report.export(output_format)))
273
289
  if filter_ in (cfg.ODS, cfg.XLSX, cfg.XLS):
274
- f.write(data_report.export(cfg.FILE_NAME_SUFFIX[filter_]))
290
+ f.write(data_report.export(output_format))