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 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
+ clickSignal = 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,16 +55,13 @@ 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
 
65
62
  self.grid = QGridLayout(self)
66
63
 
67
64
  self.installEventFilter(self)
68
- # self.compose()
69
65
 
70
66
  def config(self):
71
67
  """
@@ -96,6 +92,7 @@ class CodingPad(QWidget):
96
92
  # combobox for coding pad configuration
97
93
  vlayout = QHBoxLayout()
98
94
  self.cb_config = QComboBox()
95
+ self.cb_config.setFocusPolicy(Qt.NoFocus)
99
96
  self.cb_config.addItems(
100
97
  [
101
98
  "Choose an option to configure",
@@ -110,9 +107,7 @@ class CodingPad(QWidget):
110
107
  vlayout.addWidget(self.cb_config)
111
108
  self.grid.addLayout(vlayout, 0, 1, 1, 1)
112
109
 
113
- self.all_behaviors = [
114
- self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] for x in util.sorted_keys(self.pj[cfg.ETHOGRAM])
115
- ]
110
+ self.all_behaviors = [self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] for x in util.sorted_keys(self.pj[cfg.ETHOGRAM])]
116
111
 
117
112
  # behavioral category colors
118
113
  self.unique_behavioral_categories = sorted(
@@ -127,8 +122,7 @@ class CodingPad(QWidget):
127
122
  behaviorsList = [
128
123
  [self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CATEGORY], self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE]]
129
124
  for x in util.sorted_keys(self.pj[cfg.ETHOGRAM])
130
- if cfg.BEHAVIOR_CATEGORY in self.pj[cfg.ETHOGRAM][x]
131
- 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
132
126
  ]
133
127
 
134
128
  # square grid dimension
@@ -144,15 +138,13 @@ class CodingPad(QWidget):
144
138
 
145
139
  self.button_configuration()
146
140
 
147
- def addWidget(self, behavior_code, i, j):
148
-
141
+ def addWidget(self, behavior_code: str, i: int, j: int) -> None:
149
142
  self.grid.addWidget(Button(), i, j)
150
143
  index = self.grid.count() - 1
151
144
  widget = self.grid.itemAt(index).widget()
152
145
 
153
146
  if widget is not None:
154
147
  widget.pushButton.setText(behavior_code)
155
-
156
148
  widget.pushButton.clicked.connect(lambda: self.click(behavior_code))
157
149
 
158
150
  def button_configuration(self):
@@ -168,36 +160,40 @@ class CodingPad(QWidget):
168
160
  behavior_code = self.grid.itemAt(index).widget().pushButton.text()
169
161
 
170
162
  if self.preferences["button color"] == cfg.BEHAVIOR_CATEGORY:
171
- color = self.behavioral_category_colors[
172
- [
173
- self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CATEGORY]
174
- for x in self.pj[cfg.ETHOGRAM]
175
- if self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] == behavior_code
176
- ][0]
177
- ]
163
+ behav_cat = [
164
+ self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CATEGORY]
165
+ for x in self.pj[cfg.ETHOGRAM]
166
+ if self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] == behavior_code
167
+ ][0]
168
+ if cfg.BEHAVIORAL_CATEGORIES_CONF in self.pj:
169
+ behav_cat_color = util.behav_category_user_color(self.pj[cfg.BEHAVIORAL_CATEGORIES_CONF], behav_cat)
170
+ else:
171
+ behav_cat_color = None
172
+ if behav_cat_color is None:
173
+ color = self.behavioral_category_colors[behav_cat]
174
+ else:
175
+ color = behav_cat_color
178
176
 
179
177
  if self.preferences["button color"] == "behavior":
180
178
  # behavioral categories are not defined
181
179
  behavior_position = int(
182
- [
183
- x
184
- for x in util.sorted_keys(self.pj[cfg.ETHOGRAM])
185
- if self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] == behavior_code
186
- ][0]
187
- )
188
- color = self.behavior_colors_list[behavior_position % len(self.behavior_colors_list)].replace(
189
- "tab:", ""
180
+ [x for x in util.sorted_keys(self.pj[cfg.ETHOGRAM]) if self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] == behavior_code][0]
190
181
  )
191
182
 
183
+ # behavior button color
184
+ behav_color = util.behavior_user_color(self.pj[cfg.ETHOGRAM], behavior_code)
185
+
186
+ if behav_color is not None:
187
+ color = behav_color
188
+ else:
189
+ color = self.behavior_colors_list[behavior_position % len(self.behavior_colors_list)].replace("tab:", "")
190
+
192
191
  if self.preferences["button color"] == "no color":
193
- # color = cfg.NO_COLOR_CODING_PAD
194
192
  color = ""
195
193
 
196
194
  # set checkable if state behavior
197
195
  self.grid.itemAt(index).widget().pushButton.setCheckable(behavior_code in state_behaviors_list)
198
- self.grid.itemAt(index).widget().pushButton.setStyleSheet(
199
- self.button_css + (f"background-color: {color};" if color else "")
200
- )
196
+ self.grid.itemAt(index).widget().pushButton.setStyleSheet(self.button_css + (f"background-color: {color};" if color else ""))
201
197
  font = QFont("Arial", self.preferences["button font size"])
202
198
  self.grid.itemAt(index).widget().pushButton.setFont(font)
203
199
 
@@ -208,13 +204,13 @@ class CodingPad(QWidget):
208
204
  """
209
205
  self.button_configuration()
210
206
 
211
- def click(self, behaviorCode):
207
+ def click(self, behavior_code: str) -> None:
212
208
  """
213
209
  Button clicked
214
210
  """
215
- self.clickSignal.emit(behaviorCode)
211
+ self.clickSignal.emit(behavior_code)
216
212
 
217
- def eventFilter(self, receiver, event):
213
+ def eventFilter(self, receiver, event) -> bool:
218
214
  """
219
215
  send event (if keypress) to main window
220
216
  """
@@ -224,7 +220,7 @@ class CodingPad(QWidget):
224
220
  else:
225
221
  return False
226
222
 
227
- def closeEvent(self, event):
223
+ def closeEvent(self, event) -> None:
228
224
  """
229
225
  send event for widget geometry memory
230
226
  """
@@ -240,10 +236,7 @@ def show_coding_pad(self):
240
236
  return
241
237
 
242
238
  if hasattr(self, "codingpad"):
243
-
244
- self.codingpad.filtered_behaviors = [
245
- self.twEthogram.item(i, 1).text() for i in range(self.twEthogram.rowCount())
246
- ]
239
+ self.codingpad.filtered_behaviors = [self.twEthogram.item(i, 1).text() for i in range(self.twEthogram.rowCount())]
247
240
  if not self.codingpad.filtered_behaviors:
248
241
  QMessageBox.warning(self, cfg.programName, "No behaviors to show!")
249
242
  return
@@ -254,7 +247,6 @@ def show_coding_pad(self):
254
247
  self.codingpad.behavioral_category_colors_list = self.behav_category_colors
255
248
 
256
249
  else: # coding pad does not exist
257
-
258
250
  filtered_behaviors = [self.twEthogram.item(i, 1).text() for i in range(self.twEthogram.rowCount())]
259
251
  if not filtered_behaviors:
260
252
  QMessageBox.warning(self, cfg.programName, "No behaviors to show!")
@@ -268,6 +260,7 @@ def show_coding_pad(self):
268
260
 
269
261
  self.codingpad.setWindowFlags(Qt.WindowStaysOnTopHint)
270
262
  self.codingpad.sendEventSignal.connect(self.signal_from_widget)
263
+
271
264
  self.codingpad.clickSignal.connect(self.click_signal_from_coding_pad)
272
265
  self.codingpad.close_signal.connect(self.close_signal_from_coding_pad)
273
266
  self.codingpad.show()
@@ -280,7 +273,7 @@ def show_coding_pad(self):
280
273
  self.config_param[cfg.CODING_PAD_GEOMETRY].height(),
281
274
  )
282
275
  else:
283
- self.codingpad.setGeometry(200, 200, 660, 500)
276
+ self.codingpad.setGeometry(100, 100, 660, 500)
284
277
 
285
278
  if self.config_param.get(cfg.CODING_PAD_CONFIG, {}):
286
279
  self.codingpad.preferences = self.config_param[cfg.CODING_PAD_CONFIG]