boris-behav-obs 8.16.5__py3-none-any.whl → 9.7.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- boris/__init__.py +1 -1
- boris/__main__.py +1 -1
- boris/about.py +24 -36
- boris/add_modifier.py +88 -80
- boris/add_modifier_ui.py +235 -131
- boris/advanced_event_filtering.py +23 -29
- boris/analysis_plugins/__init__.py +0 -0
- boris/analysis_plugins/_latency.py +59 -0
- boris/analysis_plugins/irr_cohen_kappa.py +109 -0
- boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
- boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
- boris/analysis_plugins/number_of_occurences.py +22 -0
- boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
- boris/analysis_plugins/time_budget.py +61 -0
- boris/behav_coding_map_creator.py +228 -229
- boris/behavior_binary_table.py +33 -50
- boris/behaviors_coding_map.py +17 -18
- boris/boris_cli.py +6 -25
- boris/cmd_arguments.py +12 -1
- boris/coding_pad.py +16 -34
- boris/config.py +102 -50
- boris/config_file.py +55 -64
- boris/connections.py +105 -58
- boris/converters.py +13 -37
- boris/converters_ui.py +187 -110
- boris/cooccurence.py +250 -0
- boris/core.py +2108 -1275
- boris/core_qrc.py +15892 -10829
- boris/core_ui.py +941 -806
- boris/db_functions.py +17 -42
- boris/dev.py +27 -7
- boris/dialog.py +461 -242
- boris/duration_widget.py +9 -14
- boris/edit_event.py +61 -31
- boris/edit_event_ui.py +208 -97
- boris/event_operations.py +405 -281
- boris/events_cursor.py +25 -17
- boris/events_snapshots.py +36 -82
- boris/exclusion_matrix.py +4 -9
- boris/export_events.py +180 -203
- boris/export_observation.py +60 -73
- boris/external_processes.py +123 -98
- boris/geometric_measurement.py +427 -218
- boris/gui_utilities.py +91 -14
- boris/image_overlay.py +4 -4
- boris/import_observations.py +190 -98
- boris/ipc_mpv.py +304 -0
- boris/irr.py +20 -57
- boris/latency.py +31 -24
- boris/measurement_widget.py +14 -18
- boris/media_file.py +17 -19
- boris/menu_options.py +16 -6
- boris/modifier_coding_map_creator.py +1013 -0
- boris/modifiers_coding_map.py +7 -9
- boris/mpv2.py +128 -35
- boris/observation.py +493 -210
- boris/observation_operations.py +1010 -391
- boris/observation_ui.py +573 -363
- boris/observations_list.py +51 -58
- boris/otx_parser.py +74 -68
- boris/param_panel.py +45 -59
- boris/param_panel_ui.py +254 -138
- boris/player_dock_widget.py +91 -56
- boris/plot_data_module.py +18 -53
- boris/plot_events.py +56 -153
- boris/plot_events_rt.py +16 -30
- boris/plot_spectrogram_rt.py +80 -56
- boris/plot_waveform_rt.py +23 -48
- boris/plugins.py +431 -0
- boris/portion/__init__.py +18 -8
- boris/portion/const.py +35 -18
- boris/portion/dict.py +5 -5
- boris/portion/func.py +2 -2
- boris/portion/interval.py +21 -41
- boris/portion/io.py +41 -32
- boris/preferences.py +298 -123
- boris/preferences_ui.py +664 -225
- boris/project.py +293 -270
- boris/project_functions.py +610 -537
- boris/project_import_export.py +204 -213
- boris/project_ui.py +673 -441
- boris/qrc_boris.py +6 -3
- boris/qrc_boris5.py +6 -3
- boris/select_modifiers.py +62 -90
- boris/select_observations.py +19 -197
- boris/select_subj_behav.py +67 -39
- boris/state_events.py +51 -33
- boris/subjects_pad.py +6 -8
- boris/synthetic_time_budget.py +42 -26
- boris/time_budget_functions.py +169 -169
- boris/time_budget_widget.py +77 -89
- boris/transitions.py +41 -41
- boris/utilities.py +562 -222
- boris/version.py +3 -3
- boris/video_equalizer.py +16 -14
- boris/video_equalizer_ui.py +199 -130
- boris/video_operations.py +78 -28
- boris/view_df.py +104 -0
- boris/view_df_ui.py +75 -0
- boris/write_event.py +240 -136
- boris_behav_obs-9.7.1.dist-info/METADATA +140 -0
- boris_behav_obs-9.7.1.dist-info/RECORD +109 -0
- {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.1.dist-info}/WHEEL +1 -1
- boris_behav_obs-9.7.1.dist-info/entry_points.txt +2 -0
- boris/README.TXT +0 -22
- boris/add_modifier.ui +0 -323
- boris/converters.ui +0 -289
- boris/core.qrc +0 -37
- boris/core.ui +0 -1571
- boris/edit_event.ui +0 -233
- boris/icons/logo_eye.ico +0 -0
- boris/map_creator.py +0 -982
- boris/observation.ui +0 -814
- boris/param_panel.ui +0 -379
- boris/preferences.ui +0 -537
- boris/project.ui +0 -1074
- boris/vlc_local.py +0 -90
- boris_behav_obs-8.16.5.dist-info/LICENSE.TXT +0 -674
- boris_behav_obs-8.16.5.dist-info/METADATA +0 -134
- boris_behav_obs-8.16.5.dist-info/RECORD +0 -107
- boris_behav_obs-8.16.5.dist-info/entry_points.txt +0 -2
- {boris → boris_behav_obs-9.7.1.dist-info/licenses}/LICENSE.TXT +0 -0
- {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.1.dist-info}/top_level.txt +0 -0
boris/plot_events.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
BORIS
|
|
3
3
|
Behavioral Observation Research Interactive Software
|
|
4
|
-
Copyright 2012-
|
|
4
|
+
Copyright 2012-2025 Olivier Friard
|
|
5
5
|
|
|
6
6
|
This file is part of BORIS.
|
|
7
7
|
|
|
@@ -25,8 +25,10 @@ import pathlib as pl
|
|
|
25
25
|
|
|
26
26
|
import matplotlib
|
|
27
27
|
|
|
28
|
-
matplotlib.use("
|
|
28
|
+
matplotlib.use("QtAgg")
|
|
29
|
+
|
|
29
30
|
import matplotlib.dates
|
|
31
|
+
|
|
30
32
|
import matplotlib.pyplot as plt
|
|
31
33
|
import numpy as np
|
|
32
34
|
from matplotlib.dates import (
|
|
@@ -37,15 +39,15 @@ from . import config as cfg
|
|
|
37
39
|
from . import db_functions, project_functions, observation_operations
|
|
38
40
|
from . import utilities as util
|
|
39
41
|
|
|
42
|
+
# matplotlib.pyplot.switch_backend("Qt5Agg")
|
|
43
|
+
|
|
40
44
|
|
|
41
45
|
def default_value(ethogram, behavior, parameter):
|
|
42
46
|
"""
|
|
43
47
|
return value for duration in case of point event
|
|
44
48
|
"""
|
|
45
49
|
default_value_ = 0
|
|
46
|
-
if project_functions.event_type(
|
|
47
|
-
behavior, ethogram
|
|
48
|
-
) == "POINT EVENT" and parameter in ["duration"]:
|
|
50
|
+
if project_functions.event_type(behavior, ethogram) in cfg.POINT_EVENT_TYPES and parameter in ["duration"]:
|
|
49
51
|
default_value_ = "NA"
|
|
50
52
|
return default_value_
|
|
51
53
|
|
|
@@ -65,7 +67,6 @@ def init_behav_modif(
|
|
|
65
67
|
for subj in selected_subjects:
|
|
66
68
|
behaviors[subj] = {}
|
|
67
69
|
for behav_modif in distinct_behav_modif:
|
|
68
|
-
|
|
69
70
|
behav, modif = behav_modif
|
|
70
71
|
behav_modif_str = "|".join(behav_modif) if modif else behav
|
|
71
72
|
|
|
@@ -73,16 +74,12 @@ def init_behav_modif(
|
|
|
73
74
|
behaviors[subj][behav_modif_str] = {}
|
|
74
75
|
|
|
75
76
|
for parameter in parameters:
|
|
76
|
-
behaviors[subj][behav_modif_str][parameter[0]] = default_value(
|
|
77
|
-
ethogram, behav_modif_str, parameter[0]
|
|
78
|
-
)
|
|
77
|
+
behaviors[subj][behav_modif_str][parameter[0]] = default_value(ethogram, behav_modif_str, parameter[0])
|
|
79
78
|
|
|
80
79
|
return behaviors
|
|
81
80
|
|
|
82
81
|
|
|
83
|
-
def init_behav(
|
|
84
|
-
ethogram: dict, selected_subjects: list, distinct_behaviors, parameters
|
|
85
|
-
) -> dict:
|
|
82
|
+
def init_behav(ethogram: dict, selected_subjects: list, distinct_behaviors, parameters) -> dict:
|
|
86
83
|
"""
|
|
87
84
|
initialize dictionary with subject, behaviors and modifiers
|
|
88
85
|
"""
|
|
@@ -94,9 +91,7 @@ def init_behav(
|
|
|
94
91
|
if behavior not in behaviors[subj]:
|
|
95
92
|
behaviors[subj][behavior] = {}
|
|
96
93
|
for parameter in parameters:
|
|
97
|
-
behaviors[subj][behavior][parameter] = default_value(
|
|
98
|
-
ethogram, behavior, parameter
|
|
99
|
-
)
|
|
94
|
+
behaviors[subj][behavior][parameter] = default_value(ethogram, behavior, parameter)
|
|
100
95
|
return behaviors
|
|
101
96
|
|
|
102
97
|
|
|
@@ -128,9 +123,7 @@ def create_behaviors_bar_plot(
|
|
|
128
123
|
|
|
129
124
|
parameters = ["duration", "number of occurences"]
|
|
130
125
|
|
|
131
|
-
ok, msg, db_connector = db_functions.load_aggregated_events_in_db(
|
|
132
|
-
pj, selected_subjects, selected_observations, selected_behaviors
|
|
133
|
-
)
|
|
126
|
+
ok, msg, db_connector = db_functions.load_aggregated_events_in_db(pj, selected_subjects, selected_observations, selected_behaviors)
|
|
134
127
|
|
|
135
128
|
if not ok:
|
|
136
129
|
return {"error": True, "message": msg}
|
|
@@ -139,7 +132,6 @@ def create_behaviors_bar_plot(
|
|
|
139
132
|
all_behaviors = util.all_behaviors(pj[cfg.ETHOGRAM])
|
|
140
133
|
|
|
141
134
|
for obs_id in selected_observations:
|
|
142
|
-
|
|
143
135
|
cursor = db_connector.cursor()
|
|
144
136
|
# distinct behaviors
|
|
145
137
|
cursor.execute(
|
|
@@ -163,16 +155,12 @@ def create_behaviors_bar_plot(
|
|
|
163
155
|
)
|
|
164
156
|
distinct_subjects = [rows["subject"] for rows in cursor.fetchall()]
|
|
165
157
|
|
|
166
|
-
behaviors = init_behav(
|
|
167
|
-
pj[cfg.ETHOGRAM], distinct_subjects, distinct_behav, parameters
|
|
168
|
-
)
|
|
158
|
+
behaviors = init_behav(pj[cfg.ETHOGRAM], distinct_subjects, distinct_behav, parameters)
|
|
169
159
|
|
|
170
160
|
# plot creation
|
|
171
161
|
if len(distinct_subjects) > 1:
|
|
172
162
|
fig, axs = plt.subplots(nrows=1, ncols=len(distinct_subjects), sharey=True)
|
|
173
|
-
fig2, axs2 = plt.subplots(
|
|
174
|
-
nrows=1, ncols=len(distinct_subjects), sharey=True
|
|
175
|
-
)
|
|
163
|
+
fig2, axs2 = plt.subplots(nrows=1, ncols=len(distinct_subjects), sharey=True)
|
|
176
164
|
|
|
177
165
|
else:
|
|
178
166
|
fig, ax = plt.subplots(nrows=1, ncols=len(distinct_subjects), sharey=True)
|
|
@@ -190,9 +178,7 @@ def create_behaviors_bar_plot(
|
|
|
190
178
|
cursor.execute("UPDATE aggregated_events SET modifiers = ''")
|
|
191
179
|
|
|
192
180
|
# time
|
|
193
|
-
obs_length = observation_operations.observation_total_length(
|
|
194
|
-
pj[cfg.OBSERVATIONS][obs_id]
|
|
195
|
-
)
|
|
181
|
+
obs_length = observation_operations.observation_total_length(pj[cfg.OBSERVATIONS][obs_id])
|
|
196
182
|
if obs_length == -1:
|
|
197
183
|
obs_length = 0
|
|
198
184
|
|
|
@@ -210,7 +196,7 @@ def create_behaviors_bar_plot(
|
|
|
210
196
|
except Exception:
|
|
211
197
|
max_time = float(obs_length)
|
|
212
198
|
|
|
213
|
-
if param["time"]
|
|
199
|
+
if param["time"] in (cfg.TIME_ARBITRARY_INTERVAL, cfg.TIME_OBS_INTERVAL):
|
|
214
200
|
min_time = float(start_time)
|
|
215
201
|
max_time = float(end_time)
|
|
216
202
|
|
|
@@ -246,15 +232,10 @@ def create_behaviors_bar_plot(
|
|
|
246
232
|
)
|
|
247
233
|
|
|
248
234
|
for ax_idx, subject in enumerate(sorted(distinct_subjects)):
|
|
249
|
-
|
|
250
235
|
for behavior in distinct_behav:
|
|
251
|
-
|
|
252
236
|
# number of occurences
|
|
253
237
|
cursor.execute(
|
|
254
|
-
(
|
|
255
|
-
"SELECT COUNT(*) AS count FROM aggregated_events "
|
|
256
|
-
"WHERE observation = ? AND subject = ? AND behavior = ?"
|
|
257
|
-
),
|
|
238
|
+
("SELECT COUNT(*) AS count FROM aggregated_events WHERE observation = ? AND subject = ? AND behavior = ?"),
|
|
258
239
|
(
|
|
259
240
|
obs_id,
|
|
260
241
|
subject,
|
|
@@ -262,14 +243,10 @@ def create_behaviors_bar_plot(
|
|
|
262
243
|
),
|
|
263
244
|
)
|
|
264
245
|
for row in cursor.fetchall():
|
|
265
|
-
behaviors[subject][behavior]["number of occurences"] =
|
|
266
|
-
0 if row["count"] is None else row["count"]
|
|
267
|
-
)
|
|
246
|
+
behaviors[subject][behavior]["number of occurences"] = 0 if row["count"] is None else row["count"]
|
|
268
247
|
|
|
269
248
|
# total duration
|
|
270
|
-
if cfg.
|
|
271
|
-
behavior, pj[cfg.ETHOGRAM]
|
|
272
|
-
):
|
|
249
|
+
if project_functions.event_type(behavior, pj[cfg.ETHOGRAM]) in cfg.STATE_EVENT_TYPES:
|
|
273
250
|
cursor.execute(
|
|
274
251
|
(
|
|
275
252
|
"SELECT SUM(stop - start) AS duration FROM aggregated_events "
|
|
@@ -282,9 +259,7 @@ def create_behaviors_bar_plot(
|
|
|
282
259
|
),
|
|
283
260
|
)
|
|
284
261
|
for row in cursor.fetchall():
|
|
285
|
-
behaviors[subject][behavior]["duration"] =
|
|
286
|
-
0 if row["duration"] is None else row["duration"]
|
|
287
|
-
)
|
|
262
|
+
behaviors[subject][behavior]["duration"] = 0 if row["duration"] is None else row["duration"]
|
|
288
263
|
|
|
289
264
|
(
|
|
290
265
|
durations,
|
|
@@ -296,24 +271,14 @@ def create_behaviors_bar_plot(
|
|
|
296
271
|
) = ([], [], [], [], [], [])
|
|
297
272
|
|
|
298
273
|
for behavior in sorted(distinct_behav):
|
|
299
|
-
|
|
300
|
-
if (
|
|
301
|
-
param[cfg.EXCLUDE_BEHAVIORS]
|
|
302
|
-
and behaviors[subject][behavior]["number of occurences"] == 0
|
|
303
|
-
):
|
|
274
|
+
if param[cfg.EXCLUDE_BEHAVIORS] and behaviors[subject][behavior]["number of occurences"] == 0:
|
|
304
275
|
continue
|
|
305
276
|
|
|
306
|
-
n_occurences.append(
|
|
307
|
-
behaviors[subject][behavior]["number of occurences"]
|
|
308
|
-
)
|
|
277
|
+
n_occurences.append(behaviors[subject][behavior]["number of occurences"])
|
|
309
278
|
x_labels.append(behavior)
|
|
310
279
|
|
|
311
280
|
# color
|
|
312
|
-
behav_idx = [
|
|
313
|
-
k
|
|
314
|
-
for k in pj[cfg.ETHOGRAM]
|
|
315
|
-
if pj[cfg.ETHOGRAM][k]["code"] == behavior
|
|
316
|
-
][0]
|
|
281
|
+
behav_idx = [k for k in pj[cfg.ETHOGRAM] if pj[cfg.ETHOGRAM][k]["code"] == behavior][0]
|
|
317
282
|
col = None
|
|
318
283
|
if cfg.COLOR in pj[cfg.ETHOGRAM][behav_idx]:
|
|
319
284
|
col = util.behavior_user_color(pj[cfg.ETHOGRAM], behavior)
|
|
@@ -321,17 +286,11 @@ def create_behaviors_bar_plot(
|
|
|
321
286
|
colors.append(col)
|
|
322
287
|
else:
|
|
323
288
|
try:
|
|
324
|
-
colors.append(
|
|
325
|
-
util.behavior_color(
|
|
326
|
-
plot_colors, all_behaviors.index(behavior)
|
|
327
|
-
)
|
|
328
|
-
)
|
|
289
|
+
colors.append(util.behavior_color(plot_colors, all_behaviors.index(behavior)))
|
|
329
290
|
except Exception:
|
|
330
291
|
colors.append("darkgray")
|
|
331
292
|
|
|
332
|
-
if cfg.
|
|
333
|
-
behavior, pj[cfg.ETHOGRAM]
|
|
334
|
-
):
|
|
293
|
+
if project_functions.event_type(behavior, pj[cfg.ETHOGRAM]) in cfg.STATE_EVENT_TYPES:
|
|
335
294
|
durations.append(behaviors[subject][behavior]["duration"])
|
|
336
295
|
x_labels_duration.append(behavior)
|
|
337
296
|
|
|
@@ -342,11 +301,7 @@ def create_behaviors_bar_plot(
|
|
|
342
301
|
colors_duration.append(col)
|
|
343
302
|
else:
|
|
344
303
|
try:
|
|
345
|
-
colors_duration.append(
|
|
346
|
-
util.behavior_color(
|
|
347
|
-
plot_colors, all_behaviors.index(behavior)
|
|
348
|
-
)
|
|
349
|
-
)
|
|
304
|
+
colors_duration.append(util.behavior_color(plot_colors, all_behaviors.index(behavior)))
|
|
350
305
|
except Exception:
|
|
351
306
|
colors_duration.append("darkgray")
|
|
352
307
|
|
|
@@ -372,9 +327,7 @@ def create_behaviors_bar_plot(
|
|
|
372
327
|
axs[ax_idx].set_title(f"{subject}")
|
|
373
328
|
|
|
374
329
|
axs[ax_idx].set_xticks(np.arange(len(durations)))
|
|
375
|
-
axs[ax_idx].set_xticklabels(
|
|
376
|
-
x_labels_duration, rotation="vertical", fontsize=8
|
|
377
|
-
)
|
|
330
|
+
axs[ax_idx].set_xticklabels(x_labels_duration, rotation="vertical", fontsize=8)
|
|
378
331
|
|
|
379
332
|
if ax_idx == 0:
|
|
380
333
|
axs2[ax_idx].set_ylabel("Number of occurences")
|
|
@@ -392,12 +345,8 @@ def create_behaviors_bar_plot(
|
|
|
392
345
|
|
|
393
346
|
if plot_directory:
|
|
394
347
|
# output_file_name = f"{pathlib.Path(plot_directory) / utilities.safeFileName(obs_id)}.{output_format}"
|
|
395
|
-
fig.savefig(
|
|
396
|
-
|
|
397
|
-
)
|
|
398
|
-
fig2.savefig(
|
|
399
|
-
f"{pl.Path(plot_directory) / util.safeFileName(obs_id)}.number_of_occurences.{output_format}"
|
|
400
|
-
)
|
|
348
|
+
fig.savefig(f"{pl.Path(plot_directory) / util.safeFileName(obs_id)}.duration.{output_format}")
|
|
349
|
+
fig2.savefig(f"{pl.Path(plot_directory) / util.safeFileName(obs_id)}.number_of_occurences.{output_format}")
|
|
401
350
|
plt.close()
|
|
402
351
|
else:
|
|
403
352
|
fig.show()
|
|
@@ -426,9 +375,7 @@ def create_events_plot(
|
|
|
426
375
|
start_time = parameters[cfg.START_TIME]
|
|
427
376
|
end_time = parameters[cfg.END_TIME]
|
|
428
377
|
|
|
429
|
-
ok, msg, db_connector = db_functions.load_aggregated_events_in_db(
|
|
430
|
-
self.pj, selected_subjects, selected_observations, selected_behaviors
|
|
431
|
-
)
|
|
378
|
+
ok, msg, db_connector = db_functions.load_aggregated_events_in_db(self.pj, selected_subjects, selected_observations, selected_behaviors)
|
|
432
379
|
|
|
433
380
|
if not ok:
|
|
434
381
|
return False, msg, None
|
|
@@ -440,9 +387,7 @@ def create_events_plot(
|
|
|
440
387
|
cursor.execute("UPDATE aggregated_events SET modifiers = ''")
|
|
441
388
|
|
|
442
389
|
cursor.execute("SELECT DISTINCT behavior, modifiers FROM aggregated_events")
|
|
443
|
-
distinct_behav_modif = [
|
|
444
|
-
[rows["behavior"], rows["modifiers"]] for rows in cursor.fetchall()
|
|
445
|
-
]
|
|
390
|
+
distinct_behav_modif = [[rows["behavior"], rows["modifiers"]] for rows in cursor.fetchall()]
|
|
446
391
|
|
|
447
392
|
# add selected behaviors that are not observed
|
|
448
393
|
for behav in selected_behaviors:
|
|
@@ -452,31 +397,21 @@ def create_events_plot(
|
|
|
452
397
|
distinct_behav_modif = sorted(distinct_behav_modif)
|
|
453
398
|
max_len = len(distinct_behav_modif)
|
|
454
399
|
|
|
455
|
-
all_behaviors = [
|
|
456
|
-
self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE]
|
|
457
|
-
for x in util.sorted_keys(self.pj[cfg.ETHOGRAM])
|
|
458
|
-
]
|
|
400
|
+
all_behaviors = [self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] for x in util.sorted_keys(self.pj[cfg.ETHOGRAM])]
|
|
459
401
|
|
|
460
402
|
par1 = 1
|
|
461
403
|
bar_height = 0.5
|
|
462
404
|
epoch_date = dt.datetime(2017, 1, 1)
|
|
463
405
|
|
|
464
406
|
for obs_id in selected_observations:
|
|
465
|
-
|
|
466
407
|
if len(selected_subjects) > 1:
|
|
467
|
-
fig, axs = plt.subplots(
|
|
468
|
-
figsize=(20, 8), nrows=len(selected_subjects), ncols=1, sharex=True
|
|
469
|
-
)
|
|
408
|
+
fig, axs = plt.subplots(figsize=(20, 8), nrows=len(selected_subjects), ncols=1, sharex=True)
|
|
470
409
|
else:
|
|
471
|
-
fig, ax = plt.subplots(
|
|
472
|
-
figsize=(20, 8), nrows=len(selected_subjects), ncols=1, sharex=True
|
|
473
|
-
)
|
|
410
|
+
fig, ax = plt.subplots(figsize=(20, 8), nrows=len(selected_subjects), ncols=1, sharex=True)
|
|
474
411
|
axs = np.ndarray(shape=(1), dtype=type(ax))
|
|
475
412
|
axs[0] = ax
|
|
476
413
|
|
|
477
|
-
ok, msg, db_connector = db_functions.load_aggregated_events_in_db(
|
|
478
|
-
self.pj, selected_subjects, [obs_id], selected_behaviors
|
|
479
|
-
)
|
|
414
|
+
ok, msg, db_connector = db_functions.load_aggregated_events_in_db(self.pj, selected_subjects, [obs_id], selected_behaviors)
|
|
480
415
|
|
|
481
416
|
cursor = db_connector.cursor()
|
|
482
417
|
# if modifiers not to be included set modifiers to ""
|
|
@@ -485,9 +420,7 @@ def create_events_plot(
|
|
|
485
420
|
cursor = db_connector.cursor()
|
|
486
421
|
|
|
487
422
|
cursor.execute("SELECT DISTINCT behavior, modifiers FROM aggregated_events")
|
|
488
|
-
distinct_behav_modif = [
|
|
489
|
-
[rows["behavior"], rows["modifiers"]] for rows in cursor.fetchall()
|
|
490
|
-
]
|
|
423
|
+
distinct_behav_modif = [[rows["behavior"], rows["modifiers"]] for rows in cursor.fetchall()]
|
|
491
424
|
|
|
492
425
|
# add selected behaviors that are not observed
|
|
493
426
|
if not parameters["exclude behaviors"]:
|
|
@@ -499,9 +432,7 @@ def create_events_plot(
|
|
|
499
432
|
max_len = len(distinct_behav_modif)
|
|
500
433
|
|
|
501
434
|
# time
|
|
502
|
-
obs_length = observation_operations.observation_total_length(
|
|
503
|
-
self.pj[cfg.OBSERVATIONS][obs_id]
|
|
504
|
-
)
|
|
435
|
+
obs_length = observation_operations.observation_total_length(self.pj[cfg.OBSERVATIONS][obs_id])
|
|
505
436
|
if obs_length == -1: # media length not available
|
|
506
437
|
interval = cfg.TIME_EVENTS
|
|
507
438
|
|
|
@@ -509,17 +440,20 @@ def create_events_plot(
|
|
|
509
440
|
min_time = 0.0
|
|
510
441
|
max_time = float(obs_length)
|
|
511
442
|
|
|
443
|
+
if interval == cfg.TIME_OBS_INTERVAL:
|
|
444
|
+
obs_interval = self.pj[cfg.OBSERVATIONS][obs_id].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0])
|
|
445
|
+
offset = float(self.pj[cfg.OBSERVATIONS][obs_id][cfg.TIME_OFFSET])
|
|
446
|
+
min_time = float(obs_interval[0]) + offset
|
|
447
|
+
# Use max media duration for max time if no interval is defined (=0)
|
|
448
|
+
max_time = float(obs_interval[1]) + offset if obs_interval[1] != 0 else float(obs_length)
|
|
449
|
+
|
|
512
450
|
if interval == cfg.TIME_EVENTS:
|
|
513
451
|
try:
|
|
514
|
-
min_time = float(
|
|
515
|
-
self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS][0][0]
|
|
516
|
-
) # first event
|
|
452
|
+
min_time = float(self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS][0][0]) # first event
|
|
517
453
|
except Exception:
|
|
518
454
|
min_time = 0.0
|
|
519
455
|
try:
|
|
520
|
-
max_time = float(
|
|
521
|
-
self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS][-1][0]
|
|
522
|
-
) # last event
|
|
456
|
+
max_time = float(self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS][-1][0]) # last event
|
|
523
457
|
except Exception:
|
|
524
458
|
max_time = float(obs_length)
|
|
525
459
|
|
|
@@ -566,15 +500,12 @@ def create_events_plot(
|
|
|
566
500
|
|
|
567
501
|
ylabels = [" ".join(x) for x in distinct_behav_modif]
|
|
568
502
|
for ax_idx, subject in enumerate(selected_subjects):
|
|
569
|
-
|
|
570
503
|
if parameters["exclude behaviors"]:
|
|
571
504
|
cursor.execute(
|
|
572
505
|
"SELECT DISTINCT behavior, modifiers FROM aggregated_events WHERE subject = ?",
|
|
573
506
|
(subject,),
|
|
574
507
|
)
|
|
575
|
-
distinct_behav_modif = [
|
|
576
|
-
[rows["behavior"], rows["modifiers"]] for rows in cursor.fetchall()
|
|
577
|
-
]
|
|
508
|
+
distinct_behav_modif = [[rows["behavior"], rows["modifiers"]] for rows in cursor.fetchall()]
|
|
578
509
|
|
|
579
510
|
# add selected behaviors that are not observed
|
|
580
511
|
if not parameters["exclude behaviors"]:
|
|
@@ -594,17 +525,12 @@ def create_events_plot(
|
|
|
594
525
|
i = 0
|
|
595
526
|
for behavior_modifiers in distinct_behav_modif:
|
|
596
527
|
behavior, modifiers = behavior_modifiers
|
|
597
|
-
behavior_modifiers_str = (
|
|
598
|
-
"|".join(behavior_modifiers) if modifiers else behavior
|
|
599
|
-
)
|
|
528
|
+
behavior_modifiers_str = "|".join(behavior_modifiers) if modifiers else behavior
|
|
600
529
|
bars[behavior_modifiers_str] = []
|
|
601
530
|
|
|
602
531
|
# total duration
|
|
603
532
|
cursor.execute(
|
|
604
|
-
(
|
|
605
|
-
"SELECT start, stop FROM aggregated_events "
|
|
606
|
-
"WHERE subject = ? AND behavior = ? AND modifiers = ?"
|
|
607
|
-
),
|
|
533
|
+
("SELECT start, stop FROM aggregated_events WHERE subject = ? AND behavior = ? AND modifiers = ?"),
|
|
608
534
|
(
|
|
609
535
|
subject,
|
|
610
536
|
behavior,
|
|
@@ -615,27 +541,16 @@ def create_events_plot(
|
|
|
615
541
|
bars[behavior_modifiers_str].append((row["start"], row["stop"]))
|
|
616
542
|
|
|
617
543
|
if self.timeFormat == cfg.HHMMSS:
|
|
618
|
-
start_date = matplotlib.dates.date2num(
|
|
619
|
-
epoch_date + dt.timedelta(seconds=row["start"])
|
|
620
|
-
)
|
|
544
|
+
start_date = matplotlib.dates.date2num(epoch_date + dt.timedelta(seconds=row["start"]))
|
|
621
545
|
end_date = matplotlib.dates.date2num(
|
|
622
|
-
epoch_date
|
|
623
|
-
+ dt.timedelta(
|
|
624
|
-
seconds=row["stop"]
|
|
625
|
-
+ cfg.POINT_EVENT_PLOT_DURATION
|
|
626
|
-
* (row["stop"] == row["start"])
|
|
627
|
-
)
|
|
546
|
+
epoch_date + dt.timedelta(seconds=row["stop"] + cfg.POINT_EVENT_PLOT_DURATION * (row["stop"] == row["start"]))
|
|
628
547
|
)
|
|
629
548
|
if self.timeFormat == cfg.S:
|
|
630
549
|
start_date = row["start"]
|
|
631
550
|
end_date = row["stop"]
|
|
632
551
|
|
|
633
552
|
# color
|
|
634
|
-
behav_idx = [
|
|
635
|
-
k
|
|
636
|
-
for k in self.pj[cfg.ETHOGRAM]
|
|
637
|
-
if self.pj[cfg.ETHOGRAM][k]["code"] == behavior
|
|
638
|
-
][0]
|
|
553
|
+
behav_idx = [k for k in self.pj[cfg.ETHOGRAM] if self.pj[cfg.ETHOGRAM][k]["code"] == behavior][0]
|
|
639
554
|
col = None
|
|
640
555
|
if cfg.COLOR in self.pj[cfg.ETHOGRAM][behav_idx]:
|
|
641
556
|
col = util.behavior_user_color(self.pj[cfg.ETHOGRAM], behavior)
|
|
@@ -643,16 +558,10 @@ def create_events_plot(
|
|
|
643
558
|
bar_color = col
|
|
644
559
|
else:
|
|
645
560
|
try:
|
|
646
|
-
bar_color = util.behavior_color(
|
|
647
|
-
plot_colors, all_behaviors.index(behavior)
|
|
648
|
-
)
|
|
561
|
+
bar_color = util.behavior_color(plot_colors, all_behaviors.index(behavior))
|
|
649
562
|
except Exception:
|
|
650
563
|
bar_color = "darkgray"
|
|
651
|
-
bar_color =
|
|
652
|
-
cfg.POINT_EVENT_PLOT_COLOR
|
|
653
|
-
if row["stop"] == row["start"]
|
|
654
|
-
else bar_color
|
|
655
|
-
)
|
|
564
|
+
bar_color = cfg.POINT_EVENT_PLOT_COLOR if row["stop"] == row["start"] else bar_color
|
|
656
565
|
|
|
657
566
|
# sage colors removed from matplotlib colors list
|
|
658
567
|
if bar_color in ("sage", "darksage", "lightsage"):
|
|
@@ -700,12 +609,8 @@ def create_events_plot(
|
|
|
700
609
|
|
|
701
610
|
if self.timeFormat == cfg.HHMMSS:
|
|
702
611
|
axs[ax_idx].set_xlim(
|
|
703
|
-
left=matplotlib.dates.date2num(
|
|
704
|
-
|
|
705
|
-
),
|
|
706
|
-
right=matplotlib.dates.date2num(
|
|
707
|
-
epoch_date + dt.timedelta(seconds=max_time)
|
|
708
|
-
),
|
|
612
|
+
left=matplotlib.dates.date2num(epoch_date + dt.timedelta(seconds=min_time)),
|
|
613
|
+
right=matplotlib.dates.date2num(epoch_date + dt.timedelta(seconds=max_time)),
|
|
709
614
|
)
|
|
710
615
|
|
|
711
616
|
axs[ax_idx].grid(color="g", linestyle=":")
|
|
@@ -724,8 +629,6 @@ def create_events_plot(
|
|
|
724
629
|
plt.tight_layout()
|
|
725
630
|
|
|
726
631
|
if len(selected_observations) > 1:
|
|
727
|
-
plt.savefig(
|
|
728
|
-
f"{pl.Path(plot_directory) / util.safeFileName(obs_id)}.{file_format}"
|
|
729
|
-
)
|
|
632
|
+
plt.savefig(f"{pl.Path(plot_directory) / util.safeFileName(obs_id)}.{file_format}")
|
|
730
633
|
else:
|
|
731
634
|
plt.show()
|
boris/plot_events_rt.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
BORIS
|
|
3
3
|
Behavioral Observation Research Interactive Software
|
|
4
|
-
Copyright 2012-
|
|
4
|
+
Copyright 2012-2025 Olivier Friard
|
|
5
5
|
|
|
6
6
|
This file is part of BORIS.
|
|
7
7
|
|
|
@@ -25,26 +25,27 @@ Plot events in real time
|
|
|
25
25
|
|
|
26
26
|
import matplotlib
|
|
27
27
|
|
|
28
|
-
matplotlib.use("
|
|
28
|
+
matplotlib.use("QtAgg")
|
|
29
|
+
|
|
29
30
|
import numpy as np
|
|
30
|
-
from
|
|
31
|
-
from
|
|
32
|
-
from
|
|
33
|
-
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
|
31
|
+
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel
|
|
32
|
+
from PySide6.QtCore import Signal, QEvent, Qt
|
|
33
|
+
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
|
|
34
34
|
|
|
35
35
|
from matplotlib.figure import Figure
|
|
36
36
|
|
|
37
37
|
from . import config as cfg
|
|
38
38
|
|
|
39
|
+
# matplotlib.pyplot.switch_backend("Qt5Agg")
|
|
39
40
|
|
|
40
|
-
class Plot_events_RT(QWidget):
|
|
41
41
|
|
|
42
|
+
class Plot_events_RT(QWidget):
|
|
42
43
|
# send keypress event to mainwindow
|
|
43
|
-
sendEvent =
|
|
44
|
+
sendEvent = Signal(QEvent)
|
|
44
45
|
|
|
45
46
|
def __init__(self):
|
|
46
47
|
super().__init__()
|
|
47
|
-
self.setWindowTitle(
|
|
48
|
+
self.setWindowTitle("Events plot")
|
|
48
49
|
|
|
49
50
|
self.interval = 60 # default interval of visualization (in seconds)
|
|
50
51
|
self.time_mem = -1
|
|
@@ -65,13 +66,9 @@ class Plot_events_RT(QWidget):
|
|
|
65
66
|
|
|
66
67
|
hlayout1 = QHBoxLayout()
|
|
67
68
|
hlayout1.addWidget(QLabel("Time interval"))
|
|
68
|
-
hlayout1.addWidget(
|
|
69
|
-
|
|
70
|
-
)
|
|
71
|
-
hlayout1.addWidget(
|
|
72
|
-
QPushButton("-", self, clicked=lambda: self.time_interval_changed(-1), focusPolicy=Qt.Qt.NoFocus)
|
|
73
|
-
)
|
|
74
|
-
self.pb_mode = QPushButton("Include modifiers", self, clicked=self.change_mode, focusPolicy=Qt.Qt.NoFocus)
|
|
69
|
+
hlayout1.addWidget(QPushButton("+", self, clicked=lambda: self.time_interval_changed(1), focusPolicy=Qt.NoFocus))
|
|
70
|
+
hlayout1.addWidget(QPushButton("-", self, clicked=lambda: self.time_interval_changed(-1), focusPolicy=Qt.NoFocus))
|
|
71
|
+
self.pb_mode = QPushButton("Include modifiers", self, clicked=self.change_mode, focusPolicy=Qt.NoFocus)
|
|
75
72
|
hlayout1.addWidget(self.pb_mode)
|
|
76
73
|
layout.addLayout(hlayout1)
|
|
77
74
|
|
|
@@ -147,13 +144,11 @@ class Plot_events_RT(QWidget):
|
|
|
147
144
|
intervals_behav[group(event[1], event[2], event[3])] = [(0, 0)]
|
|
148
145
|
|
|
149
146
|
for event in events:
|
|
150
|
-
|
|
151
147
|
time_, subject, code, modifier = event[:4]
|
|
152
148
|
key = group(subject, code, modifier)
|
|
153
149
|
|
|
154
150
|
# check if code is state
|
|
155
151
|
if code in self.state_events_list:
|
|
156
|
-
|
|
157
152
|
if key in mem_behav and mem_behav[key] is not None:
|
|
158
153
|
# stop interval
|
|
159
154
|
|
|
@@ -172,22 +167,16 @@ class Plot_events_RT(QWidget):
|
|
|
172
167
|
mem_behav[key] = time_
|
|
173
168
|
|
|
174
169
|
else: # point event
|
|
175
|
-
|
|
176
170
|
if start <= time_ <= end:
|
|
177
|
-
intervals_behav[key].append(
|
|
178
|
-
(float(time_), float(time_) + self.point_event_plot_duration * 50)
|
|
179
|
-
) # point event -> 1 s
|
|
171
|
+
intervals_behav[key].append((float(time_), float(time_) + self.point_event_plot_duration * 50)) # point event -> 1 s
|
|
180
172
|
|
|
181
173
|
# check if intervals are closed
|
|
182
174
|
for k in mem_behav:
|
|
183
175
|
if mem_behav[k] is not None: # interval open
|
|
184
176
|
if self.observation_type == cfg.LIVE:
|
|
185
|
-
intervals_behav[k].append(
|
|
186
|
-
(float(mem_behav[k]), float((end + start) / 2))
|
|
187
|
-
) # close interval with current time
|
|
177
|
+
intervals_behav[k].append((float(mem_behav[k]), float((end + start) / 2))) # close interval with current time
|
|
188
178
|
|
|
189
179
|
elif self.observation_type == cfg.MEDIA:
|
|
190
|
-
|
|
191
180
|
intervals_behav[k].append((float(mem_behav[k]), float(end))) # close interval with end value
|
|
192
181
|
|
|
193
182
|
return intervals_behav
|
|
@@ -201,9 +190,7 @@ class Plot_events_RT(QWidget):
|
|
|
201
190
|
force_plot (bool): force plot even if media paused
|
|
202
191
|
"""
|
|
203
192
|
|
|
204
|
-
self.events = self.aggregate_events(
|
|
205
|
-
self.events_list, current_time - self.interval / 2, current_time + self.interval / 2
|
|
206
|
-
)
|
|
193
|
+
self.events = self.aggregate_events(self.events_list, current_time - self.interval / 2, current_time + self.interval / 2)
|
|
207
194
|
|
|
208
195
|
if not force_plot and current_time == self.time_mem:
|
|
209
196
|
return
|
|
@@ -211,7 +198,6 @@ class Plot_events_RT(QWidget):
|
|
|
211
198
|
self.time_mem = current_time
|
|
212
199
|
|
|
213
200
|
if self.events != self.events_mem:
|
|
214
|
-
|
|
215
201
|
left, duration = {}, {}
|
|
216
202
|
for k in self.events:
|
|
217
203
|
left[k] = []
|