boris-behav-obs 8.16.6__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 -40
  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 +101 -49
  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 +134 -0
  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 +127 -36
  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 +25 -17
  92. boris/time_budget_functions.py +169 -169
  93. boris/time_budget_widget.py +71 -86
  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.6.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.6.dist-info/LICENSE.TXT +0 -674
  121. boris_behav_obs-8.16.6.dist-info/METADATA +0 -134
  122. boris_behav_obs-8.16.6.dist-info/RECORD +0 -106
  123. boris_behav_obs-8.16.6.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.6.dist-info → boris_behav_obs-9.7.1.dist-info}/top_level.txt +0 -0
boris/ipc_mpv.py ADDED
@@ -0,0 +1,304 @@
1
+ import socket
2
+ import json
3
+ import subprocess
4
+
5
+ # from PySide6.QtCore import QTimer
6
+ import logging
7
+ import config as cfg
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class IPC_MPV:
13
+ """
14
+ class for managing mpv through Inter Process Communication (IPC)
15
+ """
16
+
17
+ media_durations: list = []
18
+ cumul_media_durations: list = []
19
+ fps: list = []
20
+ _pause: bool = False
21
+
22
+ def __init__(self, socket_path: str = cfg.MPV_SOCKET, parent=None):
23
+ # print(f"{parent=}")
24
+ self.socket_path = socket_path
25
+ self.process = None
26
+ # self.sock = None
27
+ self.init_mpv()
28
+ # self.init_socket()
29
+
30
+ def init_mpv(self):
31
+ """
32
+ Start mpv process and embed it in the PySide6 application.
33
+ """
34
+ logger.info("Start mpv ipc process")
35
+ # print(f"{self.winId()=}")
36
+ self.process = subprocess.Popen(
37
+ [
38
+ "mpv",
39
+ "--no-border",
40
+ "--osc=no", # no on screen commands
41
+ "--input-ipc-server=" + self.socket_path,
42
+ # "--wid=" + str(int(self.winId())), # Embed in the widget
43
+ "--idle", # Keeps mpv running with no video
44
+ "--input-default-bindings=no",
45
+ "--input-vo-keyboard=no",
46
+ ],
47
+ stdout=subprocess.PIPE,
48
+ stderr=subprocess.PIPE,
49
+ )
50
+
51
+ '''
52
+ def init_socket(self):
53
+ """
54
+ Initialize the JSON IPC socket.
55
+ """
56
+ print("init socket")
57
+ self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
58
+ QTimer.singleShot(5000, self.connect_socket) # Allow time for mpv to initialize
59
+ '''
60
+
61
+ '''
62
+ def connect_socket(self):
63
+ """
64
+ Connect to the mpv IPC socket.
65
+ """
66
+ print("connect socket")
67
+ try:
68
+ self.sock.connect(self.socket_path)
69
+ print("Connected to mpv IPC server.")
70
+ except socket.error as e:
71
+ print(f"Failed to connect to mpv IPC server: {e}")
72
+ print("end of connect_socket fucntion")
73
+ '''
74
+
75
+ def send_command(self, command):
76
+ """
77
+ Send a JSON command to the mpv IPC server.
78
+ """
79
+ # print(f"send command: {command}")
80
+ try:
81
+ # Create a Unix socket
82
+ with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as client:
83
+ # Connect to the MPV IPC server
84
+ client.connect(self.socket_path)
85
+ # Send the JSON command
86
+ # print(f"{json.dumps(command).encode('utf-8')=}")
87
+ client.sendall(json.dumps(command).encode("utf-8") + b"\n")
88
+ # Receive the response
89
+ response = client.recv(2000)
90
+
91
+ # print(f"{response=}")
92
+ # Parse the response as JSON
93
+ response_data = json.loads(response.decode("utf-8"))
94
+ if response_data["error"] != "success":
95
+ print(f"send command: {command}")
96
+ print(f"{response_data=}")
97
+ print()
98
+ # Return the 'data' field which contains the playback position
99
+ return response_data.get("data")
100
+ except FileNotFoundError:
101
+ logger.critical("Error: Socket file not found.")
102
+ except Exception as e:
103
+ logger.critical(f"An error occurred: {e}")
104
+ return None
105
+
106
+ @property
107
+ def time_pos(self):
108
+ time_pos = self.send_command({"command": ["get_property", "time-pos"]})
109
+ return time_pos
110
+
111
+ @property
112
+ def duration(self):
113
+ duration_ = self.send_command({"command": ["get_property", "duration"]})
114
+ return duration_
115
+
116
+ @property
117
+ def video_zoom(self):
118
+ return self.send_command({"command": ["get_property", "video-zoom"]})
119
+
120
+ @video_zoom.setter
121
+ def video_zoom(self, value):
122
+ self.send_command({"command": ["set_property", "video-zoom", value]})
123
+ return
124
+
125
+ @property
126
+ def pause(self):
127
+ return self.send_command({"command": ["get_property", "pause"]})
128
+
129
+ @pause.setter
130
+ def pause(self, value):
131
+ print(f"set pause to {value}")
132
+ return self.send_command({"command": ["set_property", "pause", value]})
133
+
134
+ @property
135
+ def estimated_frame_number(self):
136
+ return self.send_command({"command": ["get_property", "estimated-frame-number"]})
137
+
138
+ def stop(self):
139
+ self.send_command({"command": ["stop"]})
140
+ return
141
+
142
+ @property
143
+ def playlist(self):
144
+ return self.send_command({"command": ["get_property", "playlist"]})
145
+
146
+ @property
147
+ def playlist_pos(self):
148
+ return self.send_command({"command": ["get_property", "playlist-pos"]})
149
+
150
+ @playlist_pos.setter
151
+ def playlist_pos(self, value):
152
+ return self.send_command({"command": ["set_property", "playlist-pos", value]})
153
+
154
+ @property
155
+ def playlist_count(self):
156
+ return self.send_command({"command": ["get_property", "playlist-count"]})
157
+
158
+ def playlist_append(self, media):
159
+ return self.send_command({"command": ["loadfile", media, "append"]})
160
+
161
+ def wait_until_playing(self):
162
+ return
163
+
164
+ def seek(self, value, mode: str):
165
+ self.send_command({"command": ["seek", value, mode]})
166
+ return
167
+
168
+ @property
169
+ def playback_time(self):
170
+ playback_time_ = self.send_command({"command": ["get_property", "playback-time"]})
171
+ print(f"playback_time: {playback_time_}")
172
+ return playback_time_
173
+
174
+ def frame_step(self):
175
+ self.send_command({"command": ["frame-step"]})
176
+ return
177
+
178
+ def frame_back_step(self):
179
+ self.send_command({"command": ["frame-back-step"]})
180
+ return
181
+
182
+ def screenshot_to_file(self, value):
183
+ self.send_command({"command": ["screenshot-to-file", value, "video"]})
184
+ return
185
+
186
+ @property
187
+ def speed(self):
188
+ return self.send_command({"command": ["get_property", "speed"]})
189
+
190
+ @speed.setter
191
+ def speed(self, value):
192
+ self.send_command({"command": ["set_property", "speed", value]})
193
+ return
194
+
195
+ @property
196
+ def video_rotate(self):
197
+ return self.send_command({"command": ["get_property", "video-rotate"]})
198
+
199
+ @video_rotate.setter
200
+ def video_rotate(self, value):
201
+ self.send_command({"command": ["set_property", "video-rotate", value]})
202
+ return
203
+
204
+ @property
205
+ def sub_visibility(self):
206
+ return self.send_command({"command": ["get_property", "sub-visibility"]})
207
+
208
+ @sub_visibility.setter
209
+ def sub_visibility(self, value):
210
+ self.send_command({"command": ["set_property", "sub-visibility", value]})
211
+ return
212
+
213
+ @property
214
+ def brightness(self):
215
+ return self.send_command({"command": ["get_property", "brightness"]})
216
+
217
+ @brightness.setter
218
+ def brightness(self, value):
219
+ self.send_command({"command": ["set_property", "brightness", value]})
220
+ return
221
+
222
+ @property
223
+ def contrast(self):
224
+ return self.send_command({"command": ["get_property", "contrast"]})
225
+
226
+ @contrast.setter
227
+ def contrast(self, value):
228
+ self.send_command({"command": ["set_property", "contrast", value]})
229
+ return
230
+
231
+ @property
232
+ def saturation(self):
233
+ return self.send_command({"command": ["get_property", "saturation"]})
234
+
235
+ @saturation.setter
236
+ def saturation(self, value):
237
+ self.send_command({"command": ["set_property", "saturation", value]})
238
+ return
239
+
240
+ @property
241
+ def gamma(self):
242
+ return self.send_command({"command": ["get_property", "gamma"]})
243
+
244
+ @gamma.setter
245
+ def gamma(self, value):
246
+ self.send_command({"command": ["set_property", "gamma", value]})
247
+ return
248
+
249
+ @property
250
+ def hue(self):
251
+ return self.send_command({"command": ["get_property", "hue"]})
252
+
253
+ @hue.setter
254
+ def hue(self, value):
255
+ self.send_command({"command": ["set_property", "hue", value]})
256
+ return
257
+
258
+ @property
259
+ def container_fps(self):
260
+ return self.send_command({"command": ["get_property", "container-fps"]})
261
+
262
+ @property
263
+ def width(self):
264
+ return self.send_command({"command": ["get_property", "width"]})
265
+
266
+ @property
267
+ def height(self):
268
+ return self.send_command({"command": ["get_property", "height"]})
269
+
270
+ @property
271
+ def video_format(self):
272
+ return self.send_command({"command": ["get_property", "video-format"]})
273
+
274
+ @property
275
+ def deinterlace(self):
276
+ return self.send_command({"command": ["get_property", "deinterlace"]})
277
+
278
+ @deinterlace.setter
279
+ def deinterlace(self, value):
280
+ self.send_command({"command": ["set_property", "deinterlace", value]})
281
+ return
282
+
283
+ @property
284
+ def audio_bitrate(self):
285
+ return self.send_command({"command": ["get_property", "audio-bitrate"]})
286
+
287
+ @property
288
+ def eof_reached(self):
289
+ return self.send_command({"command": ["get_property", "eof-reached"]})
290
+
291
+ @property
292
+ def core_idle(self):
293
+ return self.send_command({"command": ["get_property", "core-idle"]})
294
+
295
+ """
296
+ @property
297
+ def xxx(self):
298
+ return self.send_command({"command": ["get_property", "xxx"]})
299
+
300
+ @xxx.setter
301
+ def xxx(self, value):
302
+ self.send_command({"command": ["set_property", "xxx", value]})
303
+ return
304
+ """
boris/irr.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
@@ -25,7 +25,7 @@ import logging
25
25
  from decimal import Decimal as dec
26
26
 
27
27
  import numpy as np
28
- from PyQt5.QtWidgets import QInputDialog, QMessageBox
28
+ from PySide6.QtWidgets import QInputDialog, QMessageBox
29
29
 
30
30
  from . import config as cfg
31
31
  from . import db_functions, dialog, project_functions, select_subj_behav
@@ -126,7 +126,7 @@ def cohen_kappa(cursor, obsid1: str, obsid2: str, interval: dec, selected_subjec
126
126
  first_event = cursor.execute(
127
127
  (
128
128
  "SELECT min(start) FROM aggregated_events "
129
- f"WHERE observation in (?, ?) AND subject in ({','.join('?'*len(selected_subjects))}) "
129
+ f"WHERE observation in (?, ?) AND subject in ({','.join('?' * len(selected_subjects))}) "
130
130
  ),
131
131
  (obsid1, obsid2) + tuple(selected_subjects),
132
132
  ).fetchone()[0]
@@ -134,27 +134,18 @@ def cohen_kappa(cursor, obsid1: str, obsid2: str, interval: dec, selected_subjec
134
134
  logging.debug(f"first_event: {first_event}")
135
135
 
136
136
  last_event = cursor.execute(
137
- (
138
- "SELECT max(stop) FROM aggregated_events "
139
- f"WHERE observation in (?, ?) AND subject in ({','.join('?'*len(selected_subjects))}) "
140
- ),
137
+ (f"SELECT max(stop) FROM aggregated_events WHERE observation in (?, ?) AND subject in ({','.join('?' * len(selected_subjects))}) "),
141
138
  (obsid1, obsid2) + tuple(selected_subjects),
142
139
  ).fetchone()[0]
143
140
 
144
141
  logging.debug(f"last_event: {last_event}")
145
142
 
146
143
  nb_events1 = cursor.execute(
147
- (
148
- "SELECT COUNT(*) FROM aggregated_events "
149
- f"WHERE observation = ? AND subject in ({','.join('?'*len(selected_subjects))}) "
150
- ),
144
+ (f"SELECT COUNT(*) FROM aggregated_events WHERE observation = ? AND subject in ({','.join('?' * len(selected_subjects))}) "),
151
145
  (obsid1,) + tuple(selected_subjects),
152
146
  ).fetchone()[0]
153
147
  nb_events2 = cursor.execute(
154
- (
155
- "SELECT COUNT(*) FROM aggregated_events "
156
- f"WHERE observation = ? AND subject in ({','.join('?'*len(selected_subjects))}) "
157
- ),
148
+ (f"SELECT COUNT(*) FROM aggregated_events WHERE observation = ? AND subject in ({','.join('?' * len(selected_subjects))}) "),
158
149
  (obsid2,) + tuple(selected_subjects),
159
150
  ).fetchone()[0]
160
151
 
@@ -162,10 +153,8 @@ def cohen_kappa(cursor, obsid1: str, obsid2: str, interval: dec, selected_subjec
162
153
 
163
154
  currentTime = dec(str(first_event))
164
155
  while currentTime <= last_event:
165
-
166
156
  for obsid in [obsid1, obsid2]:
167
157
  for subject in selected_subjects:
168
-
169
158
  s = subj_behav_modif(cursor, obsid, subject, currentTime, interval, include_modifiers)
170
159
 
171
160
  if s not in total_states:
@@ -185,11 +174,9 @@ def cohen_kappa(cursor, obsid1: str, obsid2: str, interval: dec, selected_subjec
185
174
  seq2 = {}
186
175
  currentTime = dec(str(first_event))
187
176
  while currentTime <= last_event:
188
-
189
177
  seq1[currentTime] = []
190
178
  seq2[currentTime] = []
191
179
  for subject in selected_subjects:
192
-
193
180
  s1 = subj_behav_modif(cursor, obsid1, subject, currentTime, interval, include_modifiers)
194
181
  s2 = subj_behav_modif(cursor, obsid2, subject, currentTime, interval, include_modifiers)
195
182
 
@@ -211,11 +198,7 @@ def cohen_kappa(cursor, obsid1: str, obsid2: str, interval: dec, selected_subjec
211
198
  logging.debug(f"contingency_table:\n {contingency_table}")
212
199
 
213
200
  template = (
214
- "Observation: {obsid1}\n"
215
- "number of events: {nb_events1}\n\n"
216
- "Observation: {obsid2}\n"
217
- "number of events: {nb_events2:.0f}\n\n"
218
- "K = {K:.3f}"
201
+ "Observation: {obsid1}\nnumber of events: {nb_events1}\n\nObservation: {obsid2}\nnumber of events: {nb_events2:.0f}\n\nK = {K:.3f}"
219
202
  )
220
203
 
221
204
  # out += "Observation length: <b>{:.3f} s</b><br>".format(self.observationTotalMediaLength(obsid1))
@@ -298,8 +281,8 @@ def irr_cohen_kappa(self):
298
281
  selected_observations,
299
282
  start_coding=dec("NaN"),
300
283
  end_coding=dec("NaN"),
301
- flagShowIncludeModifiers=True,
302
- flagShowExcludeBehaviorsWoEvents=False,
284
+ show_include_modifiers=True,
285
+ show_exclude_non_coded_behaviors=False,
303
286
  n_observations=len(selected_observations),
304
287
  )
305
288
  if parameters == {}:
@@ -309,9 +292,7 @@ def irr_cohen_kappa(self):
309
292
  return
310
293
 
311
294
  # ask for time slice
312
- i, ok = QInputDialog.getDouble(
313
- self, "IRR - Cohen's Kappa (time-unit)", "Time unit (in seconds):", 1.0, 0.001, 86400, 3
314
- )
295
+ i, ok = QInputDialog.getDouble(self, "IRR - Cohen's Kappa (time-unit)", "Time unit (in seconds):", 1.0, 0.001, 86400, 3)
315
296
  if not ok:
316
297
  return
317
298
  interval = util.float2decimal(i)
@@ -354,7 +335,7 @@ def irr_cohen_kappa(self):
354
335
  out2 += "\t".join(["%8.6f" % x for x in irr_results[r, :]]) + "\n"
355
336
 
356
337
  self.results = dialog.Results_dialog()
357
- self.results.setWindowTitle(f"BORIS - IRR - Cohen's Kappa (time-unit) analysis results")
338
+ self.results.setWindowTitle("BORIS - IRR - Cohen's Kappa (time-unit) analysis results")
358
339
  self.results.ptText.setReadOnly(True)
359
340
  if len(selected_observations) == 2:
360
341
  self.results.ptText.appendPlainText(out)
@@ -363,9 +344,7 @@ def irr_cohen_kappa(self):
363
344
  self.results.show()
364
345
 
365
346
 
366
- def needleman_wunsch_identity(
367
- cursor, obsid1: str, obsid2: str, interval, selected_subjects: list, include_modifiers: bool
368
- ):
347
+ def needleman_wunsch_identity(cursor, obsid1: str, obsid2: str, interval, selected_subjects: list, include_modifiers: bool):
369
348
  """
370
349
  Needleman - Wunsch identity between 2 observations
371
350
 
@@ -484,13 +463,12 @@ def needleman_wunsch_identity(
484
463
  first_event = cursor.execute(
485
464
  (
486
465
  "SELECT min(start) FROM aggregated_events "
487
- f"WHERE observation in (?, ?) AND subject in ({','.join('?'*len(selected_subjects))}) "
466
+ f"WHERE observation in (?, ?) AND subject in ({','.join('?' * len(selected_subjects))}) "
488
467
  ),
489
468
  (obsid1, obsid2) + tuple(selected_subjects),
490
469
  ).fetchone()[0]
491
470
 
492
471
  if first_event is None:
493
-
494
472
  logging.debug(f"An observation has no recorded events: {obsid1} or {obsid2}")
495
473
 
496
474
  return -100, f"An observation has no recorded events: {obsid1} {obsid2}"
@@ -498,28 +476,19 @@ def needleman_wunsch_identity(
498
476
  logging.debug(f"first_event: {first_event}")
499
477
 
500
478
  last_event = cursor.execute(
501
- (
502
- "SELECT max(stop) FROM aggregated_events "
503
- f"WHERE observation in (?, ?) AND subject in ({','.join('?'*len(selected_subjects))}) "
504
- ),
479
+ (f"SELECT max(stop) FROM aggregated_events WHERE observation in (?, ?) AND subject in ({','.join('?' * len(selected_subjects))}) "),
505
480
  (obsid1, obsid2) + tuple(selected_subjects),
506
481
  ).fetchone()[0]
507
482
 
508
483
  logging.debug(f"last_event: {last_event}")
509
484
 
510
485
  nb_events1 = cursor.execute(
511
- (
512
- "SELECT COUNT(*) FROM aggregated_events "
513
- f"WHERE observation = ? AND subject in ({','.join('?'*len(selected_subjects))}) "
514
- ),
486
+ (f"SELECT COUNT(*) FROM aggregated_events WHERE observation = ? AND subject in ({','.join('?' * len(selected_subjects))}) "),
515
487
  (obsid1,) + tuple(selected_subjects),
516
488
  ).fetchone()[0]
517
489
 
518
490
  nb_events2 = cursor.execute(
519
- (
520
- "SELECT COUNT(*) FROM aggregated_events "
521
- f"WHERE observation = ? AND subject in ({','.join('?'*len(selected_subjects))}) "
522
- ),
491
+ (f"SELECT COUNT(*) FROM aggregated_events WHERE observation = ? AND subject in ({','.join('?' * len(selected_subjects))}) "),
523
492
  (obsid2,) + tuple(selected_subjects),
524
493
  ).fetchone()[0]
525
494
 
@@ -528,11 +497,9 @@ def needleman_wunsch_identity(
528
497
 
529
498
  currentTime = dec(str(first_event))
530
499
  while currentTime <= last_event:
531
-
532
500
  seq1[currentTime], seq2[currentTime] = [], []
533
501
 
534
502
  for subject in selected_subjects:
535
-
536
503
  s1 = subj_behav_modif(cursor, obsid1, subject, currentTime, interval, include_modifiers)
537
504
  s2 = subj_behav_modif(cursor, obsid2, subject, currentTime, interval, include_modifiers)
538
505
 
@@ -574,9 +541,7 @@ def needleman_wunch(self):
574
541
  if not selected_observations:
575
542
  return
576
543
  if len(selected_observations) < 2:
577
- QMessageBox.information(
578
- self, cfg.programName, "You have to select at least 2 observations for Needleman-Wunsch similarity"
579
- )
544
+ QMessageBox.information(self, cfg.programName, "You have to select at least 2 observations for Needleman-Wunsch similarity")
580
545
  return
581
546
 
582
547
  # check if coded behaviors are defined in ethogram
@@ -606,8 +571,8 @@ def needleman_wunch(self):
606
571
  selected_observations,
607
572
  start_coding=dec("NaN"),
608
573
  end_coding=dec("NaN"),
609
- flagShowIncludeModifiers=True,
610
- flagShowExcludeBehaviorsWoEvents=False,
574
+ show_include_modifiers=True,
575
+ show_exclude_non_coded_behaviors=False,
611
576
  n_observations=len(selected_observations),
612
577
  )
613
578
 
@@ -631,9 +596,7 @@ def needleman_wunch(self):
631
596
 
632
597
  cursor = db_connector.cursor()
633
598
  out = (
634
- "Needleman-Wunsch similarity\n\n"
635
- f"Time unit: {interval:.3f} s\n"
636
- f"Selected subjects: {', '.join(parameters[cfg.SELECTED_SUBJECTS])}\n\n"
599
+ f"Needleman-Wunsch similarity\n\nTime unit: {interval:.3f} s\nSelected subjects: {', '.join(parameters[cfg.SELECTED_SUBJECTS])}\n\n"
637
600
  )
638
601
  mem_done = []
639
602
  nws_results = np.ones((len(selected_observations), len(selected_observations)))
boris/latency.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
@@ -18,9 +18,7 @@ Copyright 2012-2023 Olivier Friard
18
18
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
19
  MA 02110-1301, USA.
20
20
 
21
- """
22
21
 
23
- """
24
22
  Module for analyzing the latency of behaviors after another behavior(s) (marker)
25
23
 
26
24
  """
@@ -31,7 +29,7 @@ from . import dialog
31
29
  from . import select_observations
32
30
  from . import project_functions, observation_operations
33
31
 
34
- from PyQt5.QtWidgets import QMessageBox
32
+ from PySide6.QtWidgets import QMessageBox
35
33
 
36
34
 
37
35
  def get_latency(self):
@@ -43,10 +41,9 @@ def get_latency(self):
43
41
  None,
44
42
  cfg.programName,
45
43
  (
46
- f"This function is experimental. Please test it and report any bug at <br>"
44
+ "This function is experimental. Please test it and report any bug at <br>"
47
45
  '<a href="https://github.com/olivierfriard/BORIS/issues">'
48
46
  "https://github.com/olivierfriard/BORIS/issues</a><br>"
49
- "or by email (See the About page on the BORIS web site.<br><br>"
50
47
  "Thank you for your collaboration!"
51
48
  ),
52
49
  QMessageBox.Ok | QMessageBox.Default,
@@ -83,10 +80,10 @@ def get_latency(self):
83
80
  )
84
81
  return
85
82
 
86
- parameters = select_subj_behav.choose_obs_subj_behav_category(
83
+ parameters: dict = select_subj_behav.choose_obs_subj_behav_category(
87
84
  self,
88
85
  selected_observations,
89
- flagShowExcludeBehaviorsWoEvents=False,
86
+ show_exclude_non_coded_behaviors=False,
90
87
  window_title="Select the marker behaviors (stimulus)",
91
88
  n_observations=len(selected_observations),
92
89
  )
@@ -102,10 +99,10 @@ def get_latency(self):
102
99
  marker_subjects = parameters[cfg.SELECTED_SUBJECTS]
103
100
  include_marker_modifiers = parameters[cfg.INCLUDE_MODIFIERS]
104
101
 
105
- # print(f"{marker_behaviors=} {marker_subjects=} {include_marker_modifiers=}")
102
+ print(f"{marker_behaviors=} {marker_subjects=} {include_marker_modifiers=}")
106
103
 
107
- parameters = select_subj_behav.choose_obs_subj_behav_category(
108
- self, selected_observations, flagShowExcludeBehaviorsWoEvents=False, window_title="Select the latency behaviors"
104
+ parameters: dict = select_subj_behav.choose_obs_subj_behav_category(
105
+ self, selected_observations, show_exclude_non_coded_behaviors=False, window_title="Select the latency behaviors"
109
106
  )
110
107
  if not parameters[cfg.SELECTED_SUBJECTS] or not parameters[cfg.SELECTED_BEHAVIORS]:
111
108
  return
@@ -113,11 +110,11 @@ def get_latency(self):
113
110
  latency_subjects = parameters[cfg.SELECTED_SUBJECTS]
114
111
  include_latency_modifiers = parameters[cfg.INCLUDE_MODIFIERS]
115
112
 
116
- # print(f"{latency_behaviors=} {latency_subjects=} {include_latency_modifiers=}")
113
+ print(f"{latency_behaviors=} {latency_subjects=} {include_latency_modifiers=}")
117
114
 
118
- results = {}
115
+ results: dict = {}
119
116
  for obs_id in selected_observations:
120
- # print(f"{obs_id=}")
117
+ print(f"{obs_id=}")
121
118
 
122
119
  events_with_status = project_functions.events_start_stop(
123
120
  self.pj[cfg.ETHOGRAM],
@@ -125,7 +122,17 @@ def get_latency(self):
125
122
  self.pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE],
126
123
  )
127
124
 
125
+ print(f"{events_with_status=}")
126
+
128
127
  for idx, event in enumerate(events_with_status):
128
+ print(f"{event=}")
129
+
130
+ print(f"{event[cfg.EVENT_STATUS_FIELD_IDX]=}")
131
+
132
+ print(f"{event[cfg.EVENT_BEHAVIOR_FIELD_IDX]=}")
133
+
134
+ print(f"{event[cfg.EVENT_SUBJECT_FIELD_IDX]=}")
135
+
129
136
  if all(
130
137
  (
131
138
  event[cfg.EVENT_STATUS_FIELD_IDX] in (cfg.START, cfg.POINT),
@@ -138,11 +145,13 @@ def get_latency(self):
138
145
  ),
139
146
  )
140
147
  ):
141
-
142
148
  if include_marker_modifiers:
143
149
  marker = event[cfg.EVENT_TIME_FIELD_IDX : cfg.EVENT_MODIFIER_FIELD_IDX + 1]
144
150
  else:
145
151
  marker = event[cfg.EVENT_TIME_FIELD_IDX : cfg.EVENT_BEHAVIOR_FIELD_IDX + 1]
152
+
153
+ print(f"{marker=}")
154
+
146
155
  if marker not in results:
147
156
  results[marker] = {}
148
157
 
@@ -164,20 +173,19 @@ def get_latency(self):
164
173
  ),
165
174
  )
166
175
  ):
167
-
168
- # print(event, event2)
176
+ print(event, event2)
169
177
  if include_latency_modifiers:
170
178
  latency = event2[cfg.EVENT_SUBJECT_FIELD_IDX : cfg.EVENT_MODIFIER_FIELD_IDX + 1]
171
179
  else:
172
180
  latency = event2[cfg.EVENT_SUBJECT_FIELD_IDX : cfg.EVENT_BEHAVIOR_FIELD_IDX + 1]
173
181
 
174
182
  # print(f"{marker=}")
175
- # print(f"{latency=}")
176
- if not latency in results[marker]:
183
+ print(f"{latency=}")
184
+ if latency not in results[marker]:
177
185
  results[marker][latency] = []
178
- results[marker][latency].append(
179
- event2[cfg.EVENT_TIME_FIELD_IDX] - event[cfg.EVENT_TIME_FIELD_IDX]
180
- )
186
+ results[marker][latency].append(event2[cfg.EVENT_TIME_FIELD_IDX] - event[cfg.EVENT_TIME_FIELD_IDX])
187
+
188
+ print(f"{results[marker][latency]=}")
181
189
 
182
190
  # check if new marker
183
191
  if all(
@@ -208,7 +216,6 @@ def get_latency(self):
208
216
  out = ""
209
217
 
210
218
  for marker in sorted(results.keys()):
211
-
212
219
  subject = cfg.NO_FOCAL_SUBJECT if marker[cfg.EVENT_SUBJECT_FIELD_IDX] == "" else marker[1]
213
220
  if include_marker_modifiers:
214
221
  out += f"Marker: <b>{marker[cfg.EVENT_BEHAVIOR_FIELD_IDX]}</b> at {marker[cfg.EVENT_TIME_FIELD_IDX]} s (subject: {subject} - modifiers: {marker[cfg.EVENT_MODIFIER_FIELD_IDX]})<br><br>"
@@ -223,7 +230,7 @@ def get_latency(self):
223
230
 
224
231
  out += "first occurrence: "
225
232
  out += f"{sorted(results[marker][behav])[0]} s<br>"
226
- out += f"all occurrences: "
233
+ out += "all occurrences: "
227
234
 
228
235
  out += ", ".join([f"{x} s" for x in sorted(results[marker][behav])])
229
236
  out += "<br><br>"