boris-behav-obs 8.16.5__py3-none-any.whl → 9.7.12__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 (126) hide show
  1. boris/__init__.py +1 -1
  2. boris/__main__.py +1 -1
  3. boris/about.py +28 -40
  4. boris/add_modifier.py +88 -80
  5. boris/add_modifier_ui.py +266 -144
  6. boris/advanced_event_filtering.py +23 -29
  7. boris/analysis_plugins/__init__.py +0 -0
  8. boris/analysis_plugins/_export_to_feral.py +225 -0
  9. boris/analysis_plugins/_latency.py +59 -0
  10. boris/analysis_plugins/irr_cohen_kappa.py +109 -0
  11. boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
  12. boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
  13. boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
  14. boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
  15. boris/analysis_plugins/number_of_occurences.py +22 -0
  16. boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
  17. boris/analysis_plugins/time_budget.py +61 -0
  18. boris/behav_coding_map_creator.py +235 -236
  19. boris/behavior_binary_table.py +33 -50
  20. boris/behaviors_coding_map.py +17 -18
  21. boris/boris_cli.py +6 -25
  22. boris/cmd_arguments.py +12 -1
  23. boris/coding_pad.py +19 -36
  24. boris/config.py +109 -50
  25. boris/config_file.py +58 -67
  26. boris/connections.py +105 -58
  27. boris/converters.py +13 -37
  28. boris/converters_ui.py +187 -110
  29. boris/cooccurence.py +250 -0
  30. boris/core.py +2174 -1303
  31. boris/core_qrc.py +15892 -10829
  32. boris/core_ui.py +941 -806
  33. boris/db_functions.py +17 -42
  34. boris/dev.py +27 -7
  35. boris/dialog.py +461 -242
  36. boris/duration_widget.py +9 -14
  37. boris/edit_event.py +61 -31
  38. boris/edit_event_ui.py +208 -97
  39. boris/event_operations.py +405 -281
  40. boris/events_cursor.py +25 -17
  41. boris/events_snapshots.py +36 -82
  42. boris/exclusion_matrix.py +4 -9
  43. boris/export_events.py +180 -203
  44. boris/export_observation.py +60 -73
  45. boris/external_processes.py +123 -98
  46. boris/geometric_measurement.py +427 -218
  47. boris/gui_utilities.py +91 -14
  48. boris/image_overlay.py +4 -4
  49. boris/import_observations.py +190 -98
  50. boris/ipc_mpv.py +325 -0
  51. boris/irr.py +20 -57
  52. boris/latency.py +31 -24
  53. boris/measurement_widget.py +14 -18
  54. boris/media_file.py +17 -19
  55. boris/menu_options.py +16 -6
  56. boris/modifier_coding_map_creator.py +1013 -0
  57. boris/modifiers_coding_map.py +7 -9
  58. boris/mpv2.py +128 -35
  59. boris/observation.py +501 -211
  60. boris/observation_operations.py +1037 -393
  61. boris/observation_ui.py +573 -363
  62. boris/observations_list.py +51 -58
  63. boris/otx_parser.py +74 -68
  64. boris/param_panel.py +45 -59
  65. boris/param_panel_ui.py +254 -138
  66. boris/player_dock_widget.py +91 -56
  67. boris/plot_data_module.py +20 -53
  68. boris/plot_events.py +56 -153
  69. boris/plot_events_rt.py +16 -30
  70. boris/plot_spectrogram_rt.py +83 -56
  71. boris/plot_waveform_rt.py +27 -49
  72. boris/plugins.py +468 -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 +307 -123
  80. boris/preferences_ui.py +686 -227
  81. boris/project.py +294 -271
  82. boris/project_functions.py +626 -537
  83. boris/project_import_export.py +204 -213
  84. boris/project_ui.py +673 -441
  85. boris/qrc_boris.py +6 -3
  86. boris/qrc_boris5.py +6 -3
  87. boris/select_modifiers.py +62 -90
  88. boris/select_observations.py +19 -197
  89. boris/select_subj_behav.py +67 -39
  90. boris/state_events.py +51 -33
  91. boris/subjects_pad.py +7 -9
  92. boris/synthetic_time_budget.py +42 -26
  93. boris/time_budget_functions.py +169 -169
  94. boris/time_budget_widget.py +77 -89
  95. boris/transitions.py +41 -41
  96. boris/utilities.py +594 -226
  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 +86 -28
  101. boris/view_df.py +104 -0
  102. boris/view_df_ui.py +75 -0
  103. boris/write_event.py +240 -136
  104. boris_behav_obs-9.7.12.dist-info/METADATA +139 -0
  105. boris_behav_obs-9.7.12.dist-info/RECORD +110 -0
  106. {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.12.dist-info}/WHEEL +1 -1
  107. boris_behav_obs-9.7.12.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 -37
  112. boris/core.ui +0 -1571
  113. boris/edit_event.ui +0 -233
  114. boris/icons/logo_eye.ico +0 -0
  115. boris/map_creator.py +0 -982
  116. boris/observation.ui +0 -814
  117. boris/param_panel.ui +0 -379
  118. boris/preferences.ui +0 -537
  119. boris/project.ui +0 -1074
  120. boris/vlc_local.py +0 -90
  121. boris_behav_obs-8.16.5.dist-info/LICENSE.TXT +0 -674
  122. boris_behav_obs-8.16.5.dist-info/METADATA +0 -134
  123. boris_behav_obs-8.16.5.dist-info/RECORD +0 -107
  124. boris_behav_obs-8.16.5.dist-info/entry_points.txt +0 -2
  125. {boris → boris_behav_obs-9.7.12.dist-info/licenses}/LICENSE.TXT +0 -0
  126. {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.12.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 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
@@ -21,12 +21,10 @@ Copyright 2012-2023 Olivier Friard
21
21
 
22
22
  import os
23
23
  import pathlib
24
- import re
25
24
  from decimal import Decimal as dec
26
25
 
27
26
  import tablib
28
- from PyQt5.QtCore import Qt
29
- from PyQt5.QtWidgets import QFileDialog, QInputDialog, QMessageBox
27
+ from PySide6.QtWidgets import QFileDialog, QInputDialog, QMessageBox
30
28
 
31
29
  from . import observation_operations
32
30
 
@@ -38,9 +36,7 @@ from . import config as cfg
38
36
  from . import select_subj_behav
39
37
 
40
38
 
41
- def create_behavior_binary_table(
42
- pj: dict, selected_observations: list, parameters_obs: dict, time_interval: float
43
- ) -> dict:
39
+ def create_behavior_binary_table(pj: dict, selected_observations: list, parameters_obs: dict, time_interval: float) -> dict:
44
40
  """
45
41
  create behavior binary table
46
42
 
@@ -57,17 +53,12 @@ def create_behavior_binary_table(
57
53
 
58
54
  results_df = {}
59
55
 
60
- state_behavior_codes = [
61
- x for x in util.state_behavior_codes(pj[cfg.ETHOGRAM]) if x in parameters_obs[cfg.SELECTED_BEHAVIORS]
62
- ]
63
- point_behavior_codes = [
64
- x for x in util.point_behavior_codes(pj[cfg.ETHOGRAM]) if x in parameters_obs[cfg.SELECTED_BEHAVIORS]
65
- ]
56
+ state_behavior_codes = [x for x in util.state_behavior_codes(pj[cfg.ETHOGRAM]) if x in parameters_obs[cfg.SELECTED_BEHAVIORS]]
57
+ point_behavior_codes = [x for x in util.point_behavior_codes(pj[cfg.ETHOGRAM]) if x in parameters_obs[cfg.SELECTED_BEHAVIORS]]
66
58
  if not state_behavior_codes and not point_behavior_codes:
67
59
  return {"error": True, "msg": "No events selected"}
68
60
 
69
61
  for obs_id in selected_observations:
70
-
71
62
  start_time = parameters_obs[cfg.START_TIME]
72
63
  end_time = parameters_obs[cfg.END_TIME]
73
64
 
@@ -78,7 +69,6 @@ def create_behavior_binary_table(
78
69
  end_time = dec(max_obs_length)
79
70
 
80
71
  if parameters_obs["time"] == cfg.TIME_EVENTS:
81
-
82
72
  try:
83
73
  start_time = dec(pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS][0][0])
84
74
  except Exception:
@@ -89,23 +79,29 @@ def create_behavior_binary_table(
89
79
  max_obs_length, _ = observation_operations.observation_length(pj, [obs_id])
90
80
  end_time = dec(max_obs_length)
91
81
 
82
+ if parameters_obs["time"] == cfg.TIME_OBS_INTERVAL:
83
+ obs_interval = pj[cfg.OBSERVATIONS][obs_id].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0])
84
+ offset = pj[cfg.OBSERVATIONS][obs_id][cfg.TIME_OFFSET]
85
+ start_time = dec(obs_interval[0]) + offset
86
+ # Use max observation length for end time if no interval is defined (=0)
87
+ max_obs_length, _ = observation_operations.observation_length(pj, [obs_id])
88
+ end_time = dec(obs_interval[1]) + offset if obs_interval[1] not in (0, None) else dec(max_obs_length)
89
+
92
90
  if obs_id not in results_df:
93
91
  results_df[obs_id] = {}
94
92
 
95
93
  for subject in parameters_obs[cfg.SELECTED_SUBJECTS]:
96
-
97
94
  # extract tuple (behavior, modifier)
98
95
  behav_modif_list = [
99
96
  (idx[2], idx[3])
100
97
  for idx in pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]
101
- if idx[1] == (subject if subject != cfg.NO_FOCAL_SUBJECT else "")
102
- and idx[2] in parameters_obs[cfg.SELECTED_BEHAVIORS]
98
+ if idx[1] == (subject if subject != cfg.NO_FOCAL_SUBJECT else "") and idx[2] in parameters_obs[cfg.SELECTED_BEHAVIORS]
103
99
  ]
104
100
 
105
101
  # extract observed subjects NOT USED at the moment
106
- observed_subjects = [
102
+ """observed_subjects = [
107
103
  event[cfg.EVENT_SUBJECT_FIELD_IDX] for event in pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]
108
- ]
104
+ ]"""
109
105
 
110
106
  # add selected behavior if not found in (behavior, modifier)
111
107
  if not parameters_obs[cfg.EXCLUDE_BEHAVIORS]:
@@ -127,17 +123,12 @@ def create_behavior_binary_table(
127
123
  sel_subject_dict = {"": {cfg.SUBJECT_NAME: ""}}
128
124
  else:
129
125
  sel_subject_dict = dict(
130
- [
131
- (idx, pj[cfg.SUBJECTS][idx])
132
- for idx in pj[cfg.SUBJECTS]
133
- if pj[cfg.SUBJECTS][idx][cfg.SUBJECT_NAME] == subject
134
- ]
126
+ [(idx, pj[cfg.SUBJECTS][idx]) for idx in pj[cfg.SUBJECTS] if pj[cfg.SUBJECTS][idx][cfg.SUBJECT_NAME] == subject]
135
127
  )
136
128
 
137
129
  row_idx = 0
138
130
  t = start_time
139
131
  while t <= end_time:
140
-
141
132
  # state events
142
133
  current_states = util.get_current_states_modifiers_by_subject_2(
143
134
  state_behavior_codes, pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS], sel_subject_dict, t
@@ -175,8 +166,8 @@ def behavior_binary_table(self):
175
166
  None,
176
167
  cfg.programName,
177
168
  (
178
- "Depending of the length of your observations "
179
- "the execution of this function may be very long.<br>"
169
+ "Depending on the length of yours observations "
170
+ "the execution of this function may take a long time.<br>"
180
171
  "The program interface may freeze, be patient. <br>"
181
172
  ),
182
173
  )
@@ -214,24 +205,26 @@ def behavior_binary_table(self):
214
205
  return
215
206
  """
216
207
 
217
- max_media_duration_all_obs, _ = observation_operations.media_duration(
218
- self.pj[cfg.OBSERVATIONS], selected_observations
219
- )
208
+ max_media_duration_all_obs, _ = observation_operations.media_duration(self.pj[cfg.OBSERVATIONS], selected_observations)
220
209
 
221
210
  start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
222
211
 
212
+ start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
213
+
223
214
  parameters = select_subj_behav.choose_obs_subj_behav_category(
224
215
  self,
225
216
  selected_observations,
226
217
  start_coding=start_coding,
227
218
  end_coding=end_coding,
219
+ start_interval=start_interval,
220
+ end_interval=end_interval,
228
221
  maxTime=max_media_duration_all_obs,
229
- flagShowIncludeModifiers=True,
230
- flagShowExcludeBehaviorsWoEvents=True,
222
+ show_include_modifiers=True,
223
+ show_exclude_non_coded_behaviors=True,
231
224
  by_category=False,
232
225
  n_observations=len(selected_observations),
233
226
  )
234
- if parameters == {}:
227
+ if not parameters:
235
228
  return
236
229
  if not parameters[cfg.SELECTED_SUBJECTS] or not parameters[cfg.SELECTED_BEHAVIORS]:
237
230
  QMessageBox.warning(None, cfg.programName, "Select subject(s) and behavior(s) to analyze")
@@ -253,7 +246,6 @@ def behavior_binary_table(self):
253
246
  file_formats = [cfg.TSV, cfg.CSV, cfg.ODS, cfg.XLSX, cfg.XLS, cfg.HTML]
254
247
 
255
248
  if len(selected_observations) == 1:
256
-
257
249
  file_name, filter_ = QFileDialog().getSaveFileName(None, "Save results", "", ";;".join(file_formats))
258
250
  if not file_name:
259
251
  return
@@ -265,18 +257,15 @@ def behavior_binary_table(self):
265
257
  # check if file with new extension already exists
266
258
  if pathlib.Path(file_name).is_file():
267
259
  if (
268
- dialog.MessageDialog(
269
- cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE]
270
- )
260
+ dialog.MessageDialog(cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE])
271
261
  == cfg.CANCEL
272
262
  ):
273
263
  return
274
264
  else:
275
-
276
265
  item, ok = QInputDialog.getItem(None, "Save results", "Available formats", file_formats, 0, False)
277
266
  if not ok:
278
267
  return
279
- """output_format = re.sub(".* \(\*\.", "", item)[:-1]"""
268
+
280
269
  output_format = cfg.FILE_NAME_SUFFIX[item]
281
270
 
282
271
  export_dir = QFileDialog().getExistingDirectory(
@@ -287,17 +276,11 @@ def behavior_binary_table(self):
287
276
 
288
277
  mem_command = ""
289
278
  for obs_id in results_df:
290
-
291
279
  for subject in results_df[obs_id]:
292
-
293
280
  if len(selected_observations) > 1:
294
- file_name_with_subject = (
295
- str(pathlib.Path(export_dir) / util.safeFileName(obs_id + "_" + subject)) + "." + output_format
296
- )
281
+ file_name_with_subject = str(pathlib.Path(export_dir) / util.safeFileName(obs_id + "_" + subject)) + "." + output_format
297
282
  else:
298
- file_name_with_subject = (
299
- str(os.path.splitext(file_name)[0] + util.safeFileName("_" + subject)) + "." + output_format
300
- )
283
+ file_name_with_subject = str(os.path.splitext(file_name)[0] + util.safeFileName("_" + subject)) + "." + output_format
301
284
 
302
285
  # check if file with new extension already exists
303
286
  if mem_command != cfg.OVERWRITE_ALL and pathlib.Path(file_name_with_subject).is_file():
@@ -313,10 +296,10 @@ def behavior_binary_table(self):
313
296
  if mem_command in ["Skip", "Skip all"]:
314
297
  continue
315
298
 
316
- if output_format in ["csv", "tsv", "html"]:
299
+ if output_format in [cfg.CSV_EXT, cfg.TSV_EXT, cfg.HTML]:
317
300
  with open(file_name_with_subject, "wb") as f:
318
301
  f.write(str.encode(results_df[obs_id][subject].export(output_format)))
319
302
 
320
- if output_format in ["ods", "xlsx", "xls"]:
303
+ if output_format in [cfg.ODS_EXT, cfg.XLSX_EXT, cfg.XLS_EXT]:
321
304
  with open(file_name_with_subject, "wb") as f:
322
305
  f.write(results_df[obs_id][subject].export(output_format))
@@ -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
 
@@ -20,9 +20,12 @@ This file is part of BORIS.
20
20
 
21
21
  """
22
22
 
23
- from PyQt5.QtGui import *
24
- from PyQt5.QtCore import *
25
- from PyQt5.QtWidgets import (
23
+ import json
24
+ import binascii
25
+
26
+ from PySide6.QtGui import QMouseEvent, QPixmap, QPolygonF, QColor, QBrush, QPen
27
+ from PySide6.QtCore import Qt, Signal, QEvent, QPoint
28
+ from PySide6.QtWidgets import (
26
29
  QLabel,
27
30
  QHBoxLayout,
28
31
  QGraphicsView,
@@ -38,20 +41,17 @@ from PyQt5.QtWidgets import (
38
41
  QApplication,
39
42
  )
40
43
 
41
- import json
42
- import binascii
43
44
  from . import config as cfg
44
45
 
45
- codeSeparator = ","
46
- penWidth = 0
46
+ codeSeparator: str = ","
47
+ penWidth: int = 0
47
48
  penStyle = Qt.NoPen
48
49
 
49
50
 
50
51
  class BehaviorsCodingMapWindowClass(QWidget):
51
52
  class View(QGraphicsView):
52
-
53
- mousePress = pyqtSignal(QMouseEvent)
54
- mouseMove = pyqtSignal(QMouseEvent)
53
+ mousePress = Signal(QMouseEvent)
54
+ mouseMove = Signal(QMouseEvent)
55
55
 
56
56
  def eventFilter(self, source, event):
57
57
  if event.type() == QEvent.MouseMove:
@@ -72,9 +72,9 @@ class BehaviorsCodingMapWindowClass(QWidget):
72
72
  self.viewport().installEventFilter(self)
73
73
  self.setMouseTracking(True)
74
74
 
75
- clickSignal = pyqtSignal(str, list) # click signal to be sent to mainwindow
76
- keypressSignal = pyqtSignal(QEvent)
77
- close_signal = pyqtSignal(str)
75
+ clickSignal = Signal(str, list) # click signal to be sent to mainwindow
76
+ keypressSignal = Signal(QEvent)
77
+ close_signal = Signal(str)
78
78
 
79
79
  def __init__(self, behaviors_coding_map, idx=0):
80
80
  super(BehaviorsCodingMapWindowClass, self).__init__()
@@ -103,7 +103,7 @@ class BehaviorsCodingMapWindowClass(QWidget):
103
103
  self.leareaCode = QLineEdit(self)
104
104
  hBoxLayout1.addWidget(self.leareaCode)
105
105
 
106
- self.btClose = QPushButton("Close")
106
+ self.btClose = QPushButton(cfg.CLOSE)
107
107
  self.btClose.clicked.connect(self.close)
108
108
  hBoxLayout1.addWidget(self.btClose)
109
109
 
@@ -140,13 +140,13 @@ class BehaviorsCodingMapWindowClass(QWidget):
140
140
  codes.append(areaCode)
141
141
  self.leareaCode.setText(", ".join(codes))
142
142
 
143
- def viewMousePressEvent(self, event):
143
+ def viewMousePressEvent(self, event) -> None:
144
144
  """
145
145
  insert clicked areas codes
146
146
  """
147
147
 
148
148
  test = self.view.mapToScene(event.pos()).toPoint()
149
- to_be_sent = []
149
+ to_be_sent: list = []
150
150
 
151
151
  for areaCode, pg in self.polygonsList2:
152
152
  if pg.contains(test):
@@ -227,7 +227,6 @@ def show_behaviors_coding_map(self):
227
227
 
228
228
 
229
229
  if __name__ == "__main__":
230
-
231
230
  import sys
232
231
 
233
232
  app = QApplication(sys.argv)
boris/boris_cli.py CHANGED
@@ -3,7 +3,7 @@ BORIS CLI
3
3
 
4
4
  Behavioral Observation Research Interactive Software Command Line Interface
5
5
 
6
- Copyright 2012-2023 Olivier Friard
6
+ Copyright 2012-2025 Olivier Friard
7
7
 
8
8
  This program is free software; you can redistribute it and/or modify
9
9
  it under the terms of the GNU General Public License as published by
@@ -91,9 +91,7 @@ commands_usage = {
91
91
  parser = argparse.ArgumentParser(description="BORIS CLI")
92
92
  parser.add_argument("-v", "--version", action="store_true", dest="version", help="BORIS version")
93
93
  parser.add_argument("-p", "--project", action="store", dest="project_file", help="Project file path")
94
- parser.add_argument(
95
- "-o", "--observation", nargs="*", action="store", default=[], dest="observation_id", help="Observation id"
96
- )
94
+ parser.add_argument("-o", "--observation", nargs="*", action="store", default=[], dest="observation_id", help="Observation id")
97
95
  parser.add_argument("-i", "--info", action="store_true", dest="project_info", help="Project information")
98
96
  parser.add_argument("-c", "--command", nargs="*", action="store", dest="command", help="Command to execute")
99
97
 
@@ -117,7 +115,6 @@ if args.command:
117
115
  sys.exit()
118
116
 
119
117
  if args.project_file:
120
-
121
118
  if not args.command:
122
119
  print("Project path: {}".format(args.project_file))
123
120
 
@@ -164,9 +161,7 @@ if args.project_info:
164
161
  print("Subjects\n========")
165
162
  print("Number of subjects: {}".format(len(pj[SUBJECTS])))
166
163
  for idx in utilities.sorted_keys(pj[SUBJECTS]):
167
- print(
168
- "Name: {}\tDescription: {}".format(pj[SUBJECTS][idx]["name"], pj[SUBJECTS][idx]["description"])
169
- )
164
+ print("Name: {}\tDescription: {}".format(pj[SUBJECTS][idx]["name"], pj[SUBJECTS][idx]["description"]))
170
165
  print()
171
166
 
172
167
  print("Observations\n============")
@@ -179,7 +174,6 @@ if args.project_info:
179
174
  for observation_id in observations_id_list:
180
175
  print("Observation id: {}".format(observation_id))
181
176
  if pj[OBSERVATIONS][observation_id][EVENTS]:
182
-
183
177
  for event in pj[OBSERVATIONS][observation_id][EVENTS]:
184
178
  print("\t".join([str(x) for x in event]))
185
179
  else:
@@ -190,7 +184,6 @@ if args.project_info:
190
184
  sys.exit()
191
185
 
192
186
  if args.command:
193
-
194
187
  print("Command: {}\n".format(" ".join(args.command)))
195
188
 
196
189
  if not pj:
@@ -198,20 +191,16 @@ if args.command:
198
191
  sys.exit()
199
192
 
200
193
  if "check_state_events" in args.command:
201
-
202
194
  if not observations_id_list:
203
195
  print("No observation selected. Command applied on all observations found in project\n")
204
196
  observations_id_list = all_observations(pj)
205
197
 
206
198
  for observation_id in observations_id_list:
207
- ret, msg = project_functions.check_state_events_obs(
208
- observation_id, pj[ETHOGRAM], pj[OBSERVATIONS][observation_id], HHMMSS
209
- )
199
+ ret, msg = project_functions.check_state_events_obs(observation_id, pj[ETHOGRAM], pj[OBSERVATIONS][observation_id], HHMMSS)
210
200
  print("{}: {}".format(observation_id, cleanhtml(msg)))
211
201
  sys.exit()
212
202
 
213
203
  if "export_events" in args.command:
214
-
215
204
  if not observations_id_list:
216
205
  print("No observation selected. Command applied on all observations found in project\n")
217
206
  observations_id_list = [idx for idx in pj[OBSERVATIONS]]
@@ -261,21 +250,14 @@ if args.command:
261
250
  if len(args.command) > 2:
262
251
  include_modifiers = "TRUE" in args.command[2].upper()
263
252
 
264
- K, out = irr.cohen_kappa(
265
- cursor, observations_id_list[0], observations_id_list[1], interval, subjects, include_modifiers
266
- )
253
+ K, out = irr.cohen_kappa(cursor, observations_id_list[0], observations_id_list[1], interval, subjects, include_modifiers)
267
254
 
268
- print(
269
- ("Cohen's Kappa - Index of Inter-Rater Reliability\n\n" "Interval time: {interval:.3f} s\n").format(
270
- interval=interval
271
- )
272
- )
255
+ print(("Cohen's Kappa - Index of Inter-Rater Reliability\n\nInterval time: {interval:.3f} s\n").format(interval=interval))
273
256
 
274
257
  print(out)
275
258
  sys.exit()
276
259
 
277
260
  if "subtitles" in args.command:
278
-
279
261
  if not observations_id_list:
280
262
  print("No observation selected. Command applied on all observations found in project\n")
281
263
  observations_id_list = all_observations(pj)
@@ -309,7 +291,6 @@ if args.command:
309
291
  sys.exit()
310
292
 
311
293
  if "plot_events" in args.command[0]:
312
-
313
294
  if not observations_id_list:
314
295
  print("No observation selected. Command applied on all observations found in project\n")
315
296
  observations_id_list = all_observations(pj)
boris/cmd_arguments.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
 
7
7
  This program is free software; you can redistribute it and/or modify
@@ -30,9 +30,20 @@ def parse_arguments():
30
30
  parser = OptionParser(usage=usage)
31
31
 
32
32
  parser.add_option("-d", "--debug", action="store_true", default=False, dest="debug", help="Use debugging mode")
33
+ parser.add_option("-q", "--quit", action="store_true", default=False, dest="quit", help="Quit after launch")
33
34
  parser.add_option("-v", "--version", action="store_true", default=False, dest="version", help="Print version")
34
35
  parser.add_option("-n", "--nosplashscreen", action="store_true", default=False, help="No splash screen")
35
36
  parser.add_option("-p", "--project", action="store", default="", dest="project", help="Project file")
36
37
  parser.add_option("-o", "--observation", action="store", default="", dest="observation", help="Observation id")
38
+ parser.add_option("-i", "--ipc", action="store_true", default="", dest="ipc", help="MPV IPC mode")
39
+
40
+ parser.add_option(
41
+ "-f",
42
+ "--no-first-launch-dialog",
43
+ action="store_true",
44
+ default=False,
45
+ dest="no_first_launch_dialog",
46
+ help="No first launch dialog (for new version automatic check)",
47
+ )
37
48
 
38
49
  return parser.parse_args()
boris/coding_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,9 +19,9 @@ Copyright 2012-2023 Olivier Friard
19
19
  MA 02110-1301, USA.
20
20
  """
21
21
 
22
- from PyQt5.QtCore import Qt, pyqtSignal, QEvent, QRect
23
- from PyQt5.QtGui import QFont
24
- from PyQt5.QtWidgets import QWidget, QPushButton, QHBoxLayout, QGridLayout, QComboBox, QMessageBox
22
+ from PySide6.QtCore import Qt, Signal, QEvent, QRect
23
+ from PySide6.QtGui import QFont
24
+ from PySide6.QtWidgets import QWidget, QPushButton, QHBoxLayout, QGridLayout, QComboBox, QMessageBox
25
25
 
26
26
  from . import config as cfg
27
27
  from . import utilities as util
@@ -38,10 +38,9 @@ class Button(QWidget):
38
38
 
39
39
 
40
40
  class CodingPad(QWidget):
41
-
42
- clickSignal = pyqtSignal(str)
43
- sendEventSignal = pyqtSignal(QEvent)
44
- close_signal = pyqtSignal(QRect, dict)
41
+ click_signal = Signal(str)
42
+ sendEventSignal = Signal(QEvent)
43
+ close_signal = Signal(QRect, dict)
45
44
 
46
45
  def __init__(self, pj: dict, filtered_behaviors, parent=None):
47
46
  super().__init__(parent)
@@ -56,9 +55,7 @@ class CodingPad(QWidget):
56
55
 
57
56
  self.preferences: dict = {"button font size": 20, "button color": cfg.BEHAVIOR_CATEGORY}
58
57
 
59
- self.button_css: str = (
60
- "min-width: 50px; min-height:50px; font-weight: bold; max-height:5000px; max-width: 5000px;"
61
- )
58
+ self.button_css: str = "min-width: 50px; min-height:50px; font-weight: bold; max-height:5000px; max-width: 5000px;"
62
59
 
63
60
  self.setWindowTitle("Coding pad")
64
61
 
@@ -95,6 +92,7 @@ class CodingPad(QWidget):
95
92
  # combobox for coding pad configuration
96
93
  vlayout = QHBoxLayout()
97
94
  self.cb_config = QComboBox()
95
+ self.cb_config.setFocusPolicy(Qt.NoFocus)
98
96
  self.cb_config.addItems(
99
97
  [
100
98
  "Choose an option to configure",
@@ -109,9 +107,7 @@ class CodingPad(QWidget):
109
107
  vlayout.addWidget(self.cb_config)
110
108
  self.grid.addLayout(vlayout, 0, 1, 1, 1)
111
109
 
112
- self.all_behaviors = [
113
- self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] for x in util.sorted_keys(self.pj[cfg.ETHOGRAM])
114
- ]
110
+ self.all_behaviors = [self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] for x in util.sorted_keys(self.pj[cfg.ETHOGRAM])]
115
111
 
116
112
  # behavioral category colors
117
113
  self.unique_behavioral_categories = sorted(
@@ -126,8 +122,7 @@ class CodingPad(QWidget):
126
122
  behaviorsList = [
127
123
  [self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CATEGORY], self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE]]
128
124
  for x in util.sorted_keys(self.pj[cfg.ETHOGRAM])
129
- if cfg.BEHAVIOR_CATEGORY in self.pj[cfg.ETHOGRAM][x]
130
- and self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] in self.filtered_behaviors
125
+ if cfg.BEHAVIOR_CATEGORY in self.pj[cfg.ETHOGRAM][x] and self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] in self.filtered_behaviors
131
126
  ]
132
127
 
133
128
  # square grid dimension
@@ -144,7 +139,6 @@ class CodingPad(QWidget):
144
139
  self.button_configuration()
145
140
 
146
141
  def addWidget(self, behavior_code: str, i: int, j: int) -> None:
147
-
148
142
  self.grid.addWidget(Button(), i, j)
149
143
  index = self.grid.count() - 1
150
144
  widget = self.grid.itemAt(index).widget()
@@ -166,7 +160,6 @@ class CodingPad(QWidget):
166
160
  behavior_code = self.grid.itemAt(index).widget().pushButton.text()
167
161
 
168
162
  if self.preferences["button color"] == cfg.BEHAVIOR_CATEGORY:
169
-
170
163
  behav_cat = [
171
164
  self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CATEGORY]
172
165
  for x in self.pj[cfg.ETHOGRAM]
@@ -184,11 +177,7 @@ class CodingPad(QWidget):
184
177
  if self.preferences["button color"] == "behavior":
185
178
  # behavioral categories are not defined
186
179
  behavior_position = int(
187
- [
188
- x
189
- for x in util.sorted_keys(self.pj[cfg.ETHOGRAM])
190
- if self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] == behavior_code
191
- ][0]
180
+ [x for x in util.sorted_keys(self.pj[cfg.ETHOGRAM]) if self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] == behavior_code][0]
192
181
  )
193
182
 
194
183
  # behavior button color
@@ -197,18 +186,14 @@ class CodingPad(QWidget):
197
186
  if behav_color is not None:
198
187
  color = behav_color
199
188
  else:
200
- color = self.behavior_colors_list[behavior_position % len(self.behavior_colors_list)].replace(
201
- "tab:", ""
202
- )
189
+ color = self.behavior_colors_list[behavior_position % len(self.behavior_colors_list)].replace("tab:", "")
203
190
 
204
191
  if self.preferences["button color"] == "no color":
205
192
  color = ""
206
193
 
207
194
  # set checkable if state behavior
208
195
  self.grid.itemAt(index).widget().pushButton.setCheckable(behavior_code in state_behaviors_list)
209
- self.grid.itemAt(index).widget().pushButton.setStyleSheet(
210
- self.button_css + (f"background-color: {color};" if color else "")
211
- )
196
+ self.grid.itemAt(index).widget().pushButton.setStyleSheet(self.button_css + (f"background-color: {color};" if color else ""))
212
197
  font = QFont("Arial", self.preferences["button font size"])
213
198
  self.grid.itemAt(index).widget().pushButton.setFont(font)
214
199
 
@@ -223,7 +208,8 @@ class CodingPad(QWidget):
223
208
  """
224
209
  Button clicked
225
210
  """
226
- self.clickSignal.emit(behavior_code)
211
+ print(f"{behavior_code=}")
212
+ self.click_signal.emit(behavior_code)
227
213
 
228
214
  def eventFilter(self, receiver, event) -> bool:
229
215
  """
@@ -251,10 +237,7 @@ def show_coding_pad(self):
251
237
  return
252
238
 
253
239
  if hasattr(self, "codingpad"):
254
-
255
- self.codingpad.filtered_behaviors = [
256
- self.twEthogram.item(i, 1).text() for i in range(self.twEthogram.rowCount())
257
- ]
240
+ self.codingpad.filtered_behaviors = [self.twEthogram.item(i, 1).text() for i in range(self.twEthogram.rowCount())]
258
241
  if not self.codingpad.filtered_behaviors:
259
242
  QMessageBox.warning(self, cfg.programName, "No behaviors to show!")
260
243
  return
@@ -265,7 +248,6 @@ def show_coding_pad(self):
265
248
  self.codingpad.behavioral_category_colors_list = self.behav_category_colors
266
249
 
267
250
  else: # coding pad does not exist
268
-
269
251
  filtered_behaviors = [self.twEthogram.item(i, 1).text() for i in range(self.twEthogram.rowCount())]
270
252
  if not filtered_behaviors:
271
253
  QMessageBox.warning(self, cfg.programName, "No behaviors to show!")
@@ -279,7 +261,8 @@ def show_coding_pad(self):
279
261
 
280
262
  self.codingpad.setWindowFlags(Qt.WindowStaysOnTopHint)
281
263
  self.codingpad.sendEventSignal.connect(self.signal_from_widget)
282
- self.codingpad.clickSignal.connect(self.click_signal_from_coding_pad)
264
+
265
+ self.codingpad.click_signal.connect(self.click_signal_from_coding_pad)
283
266
  self.codingpad.close_signal.connect(self.close_signal_from_coding_pad)
284
267
  self.codingpad.show()
285
268