boris-behav-obs 8.16.5__py3-none-any.whl → 9.7.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- boris/__init__.py +1 -1
- boris/__main__.py +1 -1
- boris/about.py +28 -40
- boris/add_modifier.py +88 -80
- boris/add_modifier_ui.py +266 -144
- boris/advanced_event_filtering.py +23 -29
- boris/analysis_plugins/__init__.py +0 -0
- boris/analysis_plugins/_export_to_feral.py +225 -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 +235 -236
- 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 +19 -36
- boris/config.py +109 -50
- boris/config_file.py +58 -67
- boris/connections.py +105 -58
- boris/converters.py +13 -37
- boris/converters_ui.py +187 -110
- boris/cooccurence.py +250 -0
- boris/core.py +2174 -1303
- 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 +325 -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 +501 -211
- boris/observation_operations.py +1037 -393
- 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 +20 -53
- boris/plot_events.py +56 -153
- boris/plot_events_rt.py +16 -30
- boris/plot_spectrogram_rt.py +83 -56
- boris/plot_waveform_rt.py +27 -49
- boris/plugins.py +468 -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 +307 -123
- boris/preferences_ui.py +686 -227
- boris/project.py +294 -271
- boris/project_functions.py +626 -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 +7 -9
- 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 +594 -226
- boris/version.py +3 -3
- boris/video_equalizer.py +16 -14
- boris/video_equalizer_ui.py +199 -130
- boris/video_operations.py +86 -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.12.dist-info/METADATA +139 -0
- boris_behav_obs-9.7.12.dist-info/RECORD +110 -0
- {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.12.dist-info}/WHEEL +1 -1
- boris_behav_obs-9.7.12.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.12.dist-info/licenses}/LICENSE.TXT +0 -0
- {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.12.dist-info}/top_level.txt +0 -0
boris/export_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
|
|
|
@@ -38,14 +38,14 @@ from . import project_functions
|
|
|
38
38
|
from . import dialog
|
|
39
39
|
from . import db_functions
|
|
40
40
|
|
|
41
|
-
from
|
|
41
|
+
from PySide6.QtWidgets import QApplication, QFileDialog, QMessageBox, QInputDialog
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
def export_events_as_behavioral_sequences(self, separated_subjects=False, timed=False):
|
|
45
45
|
"""
|
|
46
46
|
export events from selected observations by subject as behavioral sequences (plain text file)
|
|
47
47
|
behaviors are separated by character specified in self.behav_seq_separator (usually pipe |)
|
|
48
|
-
for use with Behatrix (see
|
|
48
|
+
for use with Behatrix (see https://www.boris.unito.it/pages/behatrix)
|
|
49
49
|
|
|
50
50
|
Args:
|
|
51
51
|
separated_subjects (bool):
|
|
@@ -70,24 +70,24 @@ def export_events_as_behavioral_sequences(self, separated_subjects=False, timed=
|
|
|
70
70
|
return
|
|
71
71
|
|
|
72
72
|
if len(selected_observations) == 1:
|
|
73
|
-
max_media_duration_all_obs, _ = observation_operations.media_duration(
|
|
74
|
-
|
|
75
|
-
)
|
|
76
|
-
start_coding, end_coding, _ = observation_operations.coding_time(
|
|
77
|
-
self.pj[cfg.OBSERVATIONS], selected_observations
|
|
78
|
-
)
|
|
73
|
+
max_media_duration_all_obs, _ = observation_operations.media_duration(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
74
|
+
start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
75
|
+
start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
79
76
|
else:
|
|
80
77
|
max_media_duration_all_obs = None
|
|
81
78
|
start_coding, end_coding = dec("NaN"), dec("NaN")
|
|
79
|
+
start_interval, end_interval = None, None
|
|
82
80
|
|
|
83
81
|
parameters = select_subj_behav.choose_obs_subj_behav_category(
|
|
84
82
|
self,
|
|
85
83
|
selected_observations,
|
|
86
84
|
start_coding=start_coding,
|
|
87
85
|
end_coding=end_coding,
|
|
86
|
+
start_interval=start_interval,
|
|
87
|
+
end_interval=end_interval,
|
|
88
88
|
maxTime=max_media_duration_all_obs,
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
show_include_modifiers=True,
|
|
90
|
+
show_exclude_non_coded_behaviors=False,
|
|
91
91
|
n_observations=len(selected_observations),
|
|
92
92
|
)
|
|
93
93
|
|
|
@@ -97,30 +97,28 @@ def export_events_as_behavioral_sequences(self, separated_subjects=False, timed=
|
|
|
97
97
|
QMessageBox.warning(None, cfg.programName, "Select subject(s) and behavior(s) to analyze")
|
|
98
98
|
return
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
file_name, _ = QFileDialog.getSaveFileName(self, "Export events as behavioral sequences", "", "Text files (*.txt);;All files (*)")
|
|
101
|
+
|
|
102
|
+
if not file_name:
|
|
103
|
+
return
|
|
104
|
+
r, msg = export_observation.observation_to_behavioral_sequences(
|
|
105
|
+
pj=self.pj,
|
|
106
|
+
selected_observations=selected_observations,
|
|
107
|
+
parameters=parameters,
|
|
108
|
+
behaviors_separator=self.behav_seq_separator,
|
|
109
|
+
separated_subjects=separated_subjects,
|
|
110
|
+
timed=timed,
|
|
111
|
+
file_name=file_name,
|
|
102
112
|
)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
separated_subjects=separated_subjects,
|
|
112
|
-
timed=timed,
|
|
113
|
-
file_name=file_name,
|
|
113
|
+
if not r:
|
|
114
|
+
logging.critical(f"Error while exporting events as behavioral sequences: {msg}")
|
|
115
|
+
QMessageBox.critical(
|
|
116
|
+
None,
|
|
117
|
+
cfg.programName,
|
|
118
|
+
f"Error while exporting events as behavioral sequences:<br>{msg}",
|
|
119
|
+
QMessageBox.Ok | QMessageBox.Default,
|
|
120
|
+
QMessageBox.NoButton,
|
|
114
121
|
)
|
|
115
|
-
if not r:
|
|
116
|
-
logging.critical(f"Error while exporting events as behavioral sequences: {msg}")
|
|
117
|
-
QMessageBox.critical(
|
|
118
|
-
None,
|
|
119
|
-
cfg.programName,
|
|
120
|
-
f"Error while exporting events as behavioral sequences:<br>{msg}",
|
|
121
|
-
QMessageBox.Ok | QMessageBox.Default,
|
|
122
|
-
QMessageBox.NoButton,
|
|
123
|
-
)
|
|
124
122
|
|
|
125
123
|
|
|
126
124
|
def export_tabular_events(self, mode: str = "tabular") -> None:
|
|
@@ -165,24 +163,24 @@ def export_tabular_events(self, mode: str = "tabular") -> None:
|
|
|
165
163
|
return
|
|
166
164
|
|
|
167
165
|
if len(selected_observations) == 1:
|
|
168
|
-
max_media_duration_all_obs, _ = observation_operations.media_duration(
|
|
169
|
-
|
|
170
|
-
)
|
|
171
|
-
start_coding, end_coding, _ = observation_operations.coding_time(
|
|
172
|
-
self.pj[cfg.OBSERVATIONS], selected_observations
|
|
173
|
-
)
|
|
166
|
+
max_media_duration_all_obs, _ = observation_operations.media_duration(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
167
|
+
start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
168
|
+
start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
174
169
|
else:
|
|
175
170
|
max_media_duration_all_obs = None
|
|
176
171
|
start_coding, end_coding = dec("NaN"), dec("NaN")
|
|
172
|
+
start_interval, end_interval = None, None
|
|
177
173
|
|
|
178
174
|
parameters = select_subj_behav.choose_obs_subj_behav_category(
|
|
179
175
|
self,
|
|
180
176
|
selected_observations,
|
|
181
177
|
start_coding=start_coding,
|
|
182
178
|
end_coding=end_coding,
|
|
179
|
+
start_interval=start_interval,
|
|
180
|
+
end_interval=end_interval,
|
|
183
181
|
maxTime=max_media_duration_all_obs,
|
|
184
|
-
|
|
185
|
-
|
|
182
|
+
show_include_modifiers=False,
|
|
183
|
+
show_exclude_non_coded_behaviors=False,
|
|
186
184
|
n_observations=len(selected_observations),
|
|
187
185
|
)
|
|
188
186
|
if parameters == {}:
|
|
@@ -225,7 +223,12 @@ def export_tabular_events(self, mode: str = "tabular") -> None:
|
|
|
225
223
|
return
|
|
226
224
|
|
|
227
225
|
if len(selected_observations) == 1:
|
|
228
|
-
|
|
226
|
+
file_dialog_options = QFileDialog.Options()
|
|
227
|
+
file_dialog_options |= QFileDialog.DontConfirmOverwrite
|
|
228
|
+
|
|
229
|
+
file_name, filter_ = QFileDialog().getSaveFileName(
|
|
230
|
+
self, "Export events", "", ";;".join(available_formats), options=file_dialog_options
|
|
231
|
+
)
|
|
229
232
|
if not file_name:
|
|
230
233
|
return
|
|
231
234
|
|
|
@@ -233,11 +236,9 @@ def export_tabular_events(self, mode: str = "tabular") -> None:
|
|
|
233
236
|
if pl.Path(file_name).suffix != "." + output_format:
|
|
234
237
|
file_name = str(pl.Path(file_name)) + "." + output_format
|
|
235
238
|
# check if file with new extension already exists
|
|
236
|
-
if pl.Path(file_name).
|
|
239
|
+
if pl.Path(file_name).exists():
|
|
237
240
|
if (
|
|
238
|
-
dialog.MessageDialog(
|
|
239
|
-
cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE]
|
|
240
|
-
)
|
|
241
|
+
dialog.MessageDialog(cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE])
|
|
241
242
|
== cfg.CANCEL
|
|
242
243
|
):
|
|
243
244
|
return
|
|
@@ -295,7 +296,7 @@ def export_aggregated_events(self):
|
|
|
295
296
|
- select subjects and behaviors
|
|
296
297
|
- export events in aggregated format
|
|
297
298
|
|
|
298
|
-
Formats can be SQL (sql), SDIS (sds)
|
|
299
|
+
Formats can be SQL (sql), SDIS (sds), Tabular format (tsv, csv, ods, xlsx, xls, html) or Pandas dataframe
|
|
299
300
|
"""
|
|
300
301
|
|
|
301
302
|
def fields_type(max_modif_number: int) -> dict:
|
|
@@ -305,7 +306,8 @@ def export_aggregated_events(self):
|
|
|
305
306
|
"Description": str,
|
|
306
307
|
"Observation type": str,
|
|
307
308
|
"Source": str,
|
|
308
|
-
"
|
|
309
|
+
"Time offset (s)": str,
|
|
310
|
+
"Coding duration": float,
|
|
309
311
|
"Media duration (s)": str,
|
|
310
312
|
"FPS (frame/s)": str,
|
|
311
313
|
}
|
|
@@ -351,9 +353,7 @@ def export_aggregated_events(self):
|
|
|
351
353
|
|
|
352
354
|
return fields_type_dict
|
|
353
355
|
|
|
354
|
-
_, selected_observations = select_observations.select_observations2(
|
|
355
|
-
self, cfg.MULTIPLE, "Select observations for exporting events"
|
|
356
|
-
)
|
|
356
|
+
_, selected_observations = select_observations.select_observations2(self, cfg.MULTIPLE, "Select observations for exporting events")
|
|
357
357
|
if not selected_observations:
|
|
358
358
|
return
|
|
359
359
|
|
|
@@ -367,40 +367,37 @@ def export_aggregated_events(self):
|
|
|
367
367
|
return
|
|
368
368
|
|
|
369
369
|
if len(selected_observations) == 1:
|
|
370
|
-
max_media_duration_all_obs, _ = observation_operations.media_duration(
|
|
371
|
-
|
|
372
|
-
)
|
|
373
|
-
start_coding, end_coding, _ = observation_operations.coding_time(
|
|
374
|
-
self.pj[cfg.OBSERVATIONS], selected_observations
|
|
375
|
-
)
|
|
370
|
+
max_media_duration_all_obs, _ = observation_operations.media_duration(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
371
|
+
start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
372
|
+
start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
376
373
|
else:
|
|
377
374
|
max_media_duration_all_obs = None
|
|
378
375
|
start_coding, end_coding = dec("NaN"), dec("NaN")
|
|
376
|
+
start_interval, end_interval = None, None
|
|
379
377
|
|
|
380
378
|
parameters = select_subj_behav.choose_obs_subj_behav_category(
|
|
381
379
|
self,
|
|
382
380
|
selected_observations,
|
|
383
381
|
start_coding=start_coding,
|
|
384
382
|
end_coding=end_coding,
|
|
383
|
+
start_interval=start_interval,
|
|
384
|
+
end_interval=end_interval,
|
|
385
385
|
maxTime=max_media_duration_all_obs,
|
|
386
|
-
|
|
387
|
-
|
|
386
|
+
show_include_modifiers=False,
|
|
387
|
+
show_exclude_non_coded_behaviors=False,
|
|
388
388
|
n_observations=len(selected_observations),
|
|
389
389
|
)
|
|
390
390
|
if parameters == {}:
|
|
391
391
|
return
|
|
392
392
|
if not parameters[cfg.SELECTED_SUBJECTS] or not parameters[cfg.SELECTED_BEHAVIORS]:
|
|
393
|
-
QMessageBox.warning(None, cfg.programName, "Select subject(s) and behavior(s) to
|
|
393
|
+
QMessageBox.warning(None, cfg.programName, "Select subject(s) and behavior(s) to export")
|
|
394
394
|
return
|
|
395
395
|
|
|
396
396
|
# check for grouping results
|
|
397
397
|
flag_group = True
|
|
398
398
|
if len(selected_observations) > 1:
|
|
399
399
|
flag_group = (
|
|
400
|
-
dialog.MessageDialog(
|
|
401
|
-
cfg.programName, "Group events from selected observations in one file?", [cfg.YES, cfg.NO]
|
|
402
|
-
)
|
|
403
|
-
== cfg.YES
|
|
400
|
+
dialog.MessageDialog(cfg.programName, "Group events from selected observations in one file?", [cfg.YES, cfg.NO]) == cfg.YES
|
|
404
401
|
)
|
|
405
402
|
|
|
406
403
|
if flag_group:
|
|
@@ -418,7 +415,12 @@ def export_aggregated_events(self):
|
|
|
418
415
|
cfg.RDS,
|
|
419
416
|
)
|
|
420
417
|
|
|
421
|
-
|
|
418
|
+
file_dialog_options = QFileDialog.Options()
|
|
419
|
+
file_dialog_options |= QFileDialog.DontConfirmOverwrite
|
|
420
|
+
|
|
421
|
+
fileName, filter_ = QFileDialog().getSaveFileName(
|
|
422
|
+
self, "Export aggregated events", "", ";;".join(file_formats), options=file_dialog_options
|
|
423
|
+
)
|
|
422
424
|
|
|
423
425
|
if not fileName:
|
|
424
426
|
return
|
|
@@ -427,13 +429,8 @@ def export_aggregated_events(self):
|
|
|
427
429
|
if pl.Path(fileName).suffix != "." + outputFormat:
|
|
428
430
|
# check if file with new extension already exists
|
|
429
431
|
fileName = str(pl.Path(fileName)) + "." + outputFormat
|
|
430
|
-
if pl.Path(fileName).
|
|
431
|
-
if (
|
|
432
|
-
dialog.MessageDialog(
|
|
433
|
-
cfg.programName, f"The file {fileName} already exists.", [cfg.CANCEL, cfg.OVERWRITE]
|
|
434
|
-
)
|
|
435
|
-
== cfg.CANCEL
|
|
436
|
-
):
|
|
432
|
+
if pl.Path(fileName).exists():
|
|
433
|
+
if dialog.MessageDialog(cfg.programName, f"The file {fileName} already exists.", [cfg.CANCEL, cfg.OVERWRITE]) == cfg.CANCEL:
|
|
437
434
|
return
|
|
438
435
|
|
|
439
436
|
else: # not grouping
|
|
@@ -481,11 +478,13 @@ def export_aggregated_events(self):
|
|
|
481
478
|
return
|
|
482
479
|
|
|
483
480
|
# compute the maximum number of modifiers
|
|
484
|
-
tot_max_modifiers = 0
|
|
481
|
+
tot_max_modifiers: int = 0
|
|
485
482
|
for obs_id in selected_observations:
|
|
486
483
|
_, max_modifiers = export_observation.export_aggregated_events(self.pj, parameters, obs_id)
|
|
487
484
|
tot_max_modifiers = max(tot_max_modifiers, max_modifiers)
|
|
488
485
|
|
|
486
|
+
logging.debug(f"tot_max_modifiers: {tot_max_modifiers}")
|
|
487
|
+
|
|
489
488
|
data_grouped_obs = tablib.Dataset()
|
|
490
489
|
|
|
491
490
|
mem_command: str = "" # remember user choice when file already exists
|
|
@@ -494,31 +493,30 @@ def export_aggregated_events(self):
|
|
|
494
493
|
for obs_id in selected_observations:
|
|
495
494
|
logging.debug(f"Exporting aggregated events for obs Id: {obs_id}")
|
|
496
495
|
|
|
497
|
-
data_single_obs,
|
|
496
|
+
data_single_obs, _ = export_observation.export_aggregated_events(
|
|
497
|
+
self.pj, parameters, obs_id, force_number_modifiers=tot_max_modifiers
|
|
498
|
+
)
|
|
498
499
|
|
|
499
500
|
try:
|
|
500
501
|
# order by start time
|
|
501
502
|
index = header.index("Start (s)")
|
|
502
503
|
if cfg.NA not in [x[index] for x in list(data_single_obs)]:
|
|
503
504
|
data_single_obs_sorted = tablib.Dataset(
|
|
504
|
-
# *data.sort(col=index),
|
|
505
505
|
*sorted(list(data_single_obs), key=lambda x: float(x[index])),
|
|
506
|
-
headers=list(fields_type(
|
|
506
|
+
headers=list(fields_type(tot_max_modifiers).keys()),
|
|
507
507
|
)
|
|
508
508
|
else:
|
|
509
509
|
# order by image index
|
|
510
510
|
index = header.index("Image index start")
|
|
511
511
|
data_single_obs_sorted = tablib.Dataset(
|
|
512
|
-
# *data.sort(col=index),
|
|
513
512
|
*sorted(list(data_single_obs), key=lambda x: float(x[index])),
|
|
514
|
-
headers=list(fields_type(
|
|
513
|
+
headers=list(fields_type(tot_max_modifiers).keys()),
|
|
515
514
|
)
|
|
516
515
|
except Exception:
|
|
517
516
|
# if error no order
|
|
518
517
|
data_single_obs_sorted = tablib.Dataset(
|
|
519
|
-
# data.sort(col=0), # Observation id
|
|
520
518
|
*list(data_single_obs),
|
|
521
|
-
headers=list(fields_type(
|
|
519
|
+
headers=list(fields_type(tot_max_modifiers).keys()),
|
|
522
520
|
)
|
|
523
521
|
|
|
524
522
|
data_single_obs_sorted.title = obs_id
|
|
@@ -539,14 +537,12 @@ def export_aggregated_events(self):
|
|
|
539
537
|
if mem_command in (cfg.SKIP, cfg.SKIP_ALL):
|
|
540
538
|
continue
|
|
541
539
|
|
|
542
|
-
r, msg = export_observation.dataset_write(
|
|
543
|
-
data_single_obs_sorted, fileName, outputFormat, dtype=fields_type(max_modifiers)
|
|
544
|
-
)
|
|
540
|
+
r, msg = export_observation.dataset_write(data_single_obs_sorted, fileName, outputFormat, dtype=fields_type(max_modifiers))
|
|
545
541
|
if not r:
|
|
546
|
-
QMessageBox.warning(
|
|
547
|
-
None, cfg.programName, msg, QMessageBox.Ok | QMessageBox.Default, QMessageBox.NoButton
|
|
548
|
-
)
|
|
542
|
+
QMessageBox.warning(None, cfg.programName, msg, QMessageBox.Ok | QMessageBox.Default, QMessageBox.NoButton)
|
|
549
543
|
|
|
544
|
+
"""
|
|
545
|
+
# disabled after introduction of the force_number_modifiers parameter in export_aggregated_events function
|
|
550
546
|
if len(data_single_obs_sorted) and max_modifiers < tot_max_modifiers:
|
|
551
547
|
for i in range(tot_max_modifiers - max_modifiers):
|
|
552
548
|
data_single_obs_sorted.insert_col(
|
|
@@ -554,9 +550,12 @@ def export_aggregated_events(self):
|
|
|
554
550
|
col=[""] * (len(list(data_single_obs_sorted))),
|
|
555
551
|
header=f"Modif #{i}",
|
|
556
552
|
)
|
|
553
|
+
"""
|
|
554
|
+
|
|
557
555
|
data_grouped_obs.extend(data_single_obs_sorted)
|
|
558
556
|
|
|
559
557
|
data_grouped_obs_all = tablib.Dataset(headers=list(fields_type(tot_max_modifiers).keys()))
|
|
558
|
+
|
|
560
559
|
data_grouped_obs_all.extend(data_grouped_obs)
|
|
561
560
|
data_grouped_obs_all.title = "Aggregated events"
|
|
562
561
|
|
|
@@ -600,10 +599,7 @@ def export_aggregated_events(self):
|
|
|
600
599
|
return
|
|
601
600
|
|
|
602
601
|
if outputFormat == cfg.SDIS_EXT: # SDIS format
|
|
603
|
-
out: str = (
|
|
604
|
-
"% SDIS file created by BORIS (www.boris.unito.it) "
|
|
605
|
-
f"at {util.datetime_iso8601(dt.datetime.now())}\nTimed <seconds>;\n"
|
|
606
|
-
)
|
|
602
|
+
out: str = f"% SDIS file created by BORIS (www.boris.unito.it) at {util.datetime_iso8601(dt.datetime.now())}\nTimed <seconds>;\n"
|
|
607
603
|
for obs_id in selected_observations:
|
|
608
604
|
# observation id
|
|
609
605
|
out += "\n<{}>\n".format(obs_id)
|
|
@@ -629,10 +625,7 @@ def export_aggregated_events(self):
|
|
|
629
625
|
fileName = f"{pl.Path(exportDir) / util.safeFileName(obs_id)}.{outputFormat}"
|
|
630
626
|
with open(fileName, "wb") as f:
|
|
631
627
|
f.write(str.encode(out))
|
|
632
|
-
out = (
|
|
633
|
-
"% SDIS file created by BORIS (www.boris.unito.it) "
|
|
634
|
-
f"at {util.datetime_iso8601(dt.datetime.now())}\nTimed <seconds>;\n"
|
|
635
|
-
)
|
|
628
|
+
out = f"% SDIS file created by BORIS (www.boris.unito.it) at {util.datetime_iso8601(dt.datetime.now())}\nTimed <seconds>;\n"
|
|
636
629
|
|
|
637
630
|
if flag_group:
|
|
638
631
|
with open(fileName, "wb") as f:
|
|
@@ -640,9 +633,7 @@ def export_aggregated_events(self):
|
|
|
640
633
|
return
|
|
641
634
|
|
|
642
635
|
if flag_group:
|
|
643
|
-
r, msg = export_observation.dataset_write(
|
|
644
|
-
data_grouped_obs_all, fileName, outputFormat, dtype=fields_type(max_modifiers)
|
|
645
|
-
)
|
|
636
|
+
r, msg = export_observation.dataset_write(data_grouped_obs_all, fileName, outputFormat, dtype=fields_type(max_modifiers))
|
|
646
637
|
if not r:
|
|
647
638
|
QMessageBox.warning(None, cfg.programName, msg, QMessageBox.Ok | QMessageBox.Default, QMessageBox.NoButton)
|
|
648
639
|
|
|
@@ -683,13 +674,19 @@ def export_events_as_textgrid(self) -> None:
|
|
|
683
674
|
|
|
684
675
|
start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
685
676
|
|
|
677
|
+
start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
678
|
+
|
|
686
679
|
parameters = select_subj_behav.choose_obs_subj_behav_category(
|
|
687
680
|
self,
|
|
688
681
|
selected_observations,
|
|
689
682
|
start_coding=start_coding,
|
|
690
683
|
end_coding=end_coding,
|
|
691
|
-
|
|
692
|
-
|
|
684
|
+
# start_interval=start_interval,
|
|
685
|
+
# end_interval=end_interval,
|
|
686
|
+
start_interval=None,
|
|
687
|
+
end_interval=None,
|
|
688
|
+
show_include_modifiers=False,
|
|
689
|
+
show_exclude_non_coded_behaviors=False,
|
|
693
690
|
maxTime=max_obs_length,
|
|
694
691
|
n_observations=len(selected_observations),
|
|
695
692
|
)
|
|
@@ -699,13 +696,13 @@ def export_events_as_textgrid(self) -> None:
|
|
|
699
696
|
QMessageBox.warning(None, cfg.programName, "Select subject(s) and behavior(s) to export")
|
|
700
697
|
return
|
|
701
698
|
|
|
702
|
-
export_dir = QFileDialog
|
|
703
|
-
self, "Export events as Praat TextGrid", os.path.expanduser("~"), options=QFileDialog
|
|
699
|
+
export_dir = QFileDialog.getExistingDirectory(
|
|
700
|
+
self, "Export events as Praat TextGrid", os.path.expanduser("~"), options=QFileDialog.ShowDirsOnly
|
|
704
701
|
)
|
|
705
702
|
if not export_dir:
|
|
706
703
|
return
|
|
707
704
|
|
|
708
|
-
mem_command = ""
|
|
705
|
+
mem_command: str = ""
|
|
709
706
|
|
|
710
707
|
# see https://www.fon.hum.uva.nl/praat/manual/TextGrid_file_formats.html
|
|
711
708
|
|
|
@@ -718,12 +715,7 @@ def export_events_as_textgrid(self) -> None:
|
|
|
718
715
|
" intervals: size = {intervalsSize}\n"
|
|
719
716
|
)
|
|
720
717
|
|
|
721
|
-
interval_template =
|
|
722
|
-
" intervals [{count}]:\n"
|
|
723
|
-
" xmin = {xmin}\n"
|
|
724
|
-
" xmax = {xmax}\n"
|
|
725
|
-
' text = "{name}"\n'
|
|
726
|
-
)
|
|
718
|
+
interval_template = ' intervals [{count}]:\n xmin = {xmin}\n xmax = {xmax}\n text = "{name}"\n'
|
|
727
719
|
|
|
728
720
|
point_subject_header = (
|
|
729
721
|
" item [{subject_index}]:\n"
|
|
@@ -734,7 +726,7 @@ def export_events_as_textgrid(self) -> None:
|
|
|
734
726
|
" points: size = {intervalsSize}\n"
|
|
735
727
|
)
|
|
736
728
|
|
|
737
|
-
point_template =
|
|
729
|
+
point_template = ' points [{count}]:\n number = {number}\n mark = "{mark}"\n'
|
|
738
730
|
|
|
739
731
|
# widget for results
|
|
740
732
|
self.results = dialog.Results_dialog()
|
|
@@ -746,7 +738,7 @@ def export_events_as_textgrid(self) -> None:
|
|
|
746
738
|
)
|
|
747
739
|
|
|
748
740
|
if db_connector is None:
|
|
749
|
-
logging.critical(
|
|
741
|
+
logging.critical("Error when loading aggregated events in DB")
|
|
750
742
|
return
|
|
751
743
|
|
|
752
744
|
cursor = db_connector.cursor()
|
|
@@ -755,9 +747,7 @@ def export_events_as_textgrid(self) -> None:
|
|
|
755
747
|
|
|
756
748
|
for obs_id in selected_observations:
|
|
757
749
|
if parameters["time"] == cfg.TIME_EVENTS:
|
|
758
|
-
start_coding, end_coding, coding_duration = observation_operations.coding_time(
|
|
759
|
-
self.pj[cfg.OBSERVATIONS], [obs_id]
|
|
760
|
-
)
|
|
750
|
+
start_coding, end_coding, coding_duration = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], [obs_id])
|
|
761
751
|
if start_coding is None and end_coding is None: # no events
|
|
762
752
|
self.results.ptText.appendHtml(f"The observation <b>{obs_id}</b> does not have events.")
|
|
763
753
|
QApplication.processEvents()
|
|
@@ -779,9 +769,7 @@ def export_events_as_textgrid(self) -> None:
|
|
|
779
769
|
coding_duration = max_media_duration
|
|
780
770
|
|
|
781
771
|
if self.pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] in (cfg.LIVE, cfg.IMAGES):
|
|
782
|
-
start_coding, end_coding, coding_duration = observation_operations.coding_time(
|
|
783
|
-
self.pj[cfg.OBSERVATIONS], [obs_id]
|
|
784
|
-
)
|
|
772
|
+
start_coding, end_coding, coding_duration = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], [obs_id])
|
|
785
773
|
if start_coding is None and end_coding is None: # no events
|
|
786
774
|
self.results.ptText.appendHtml(f"The observation <b>{obs_id}</b> does not have events.")
|
|
787
775
|
QApplication.processEvents()
|
|
@@ -798,7 +786,16 @@ def export_events_as_textgrid(self) -> None:
|
|
|
798
786
|
min_time = float(parameters[cfg.START_TIME])
|
|
799
787
|
max_time = float(parameters[cfg.END_TIME])
|
|
800
788
|
|
|
789
|
+
if parameters["time"] == cfg.TIME_OBS_INTERVAL:
|
|
790
|
+
max_media_duration, _ = observation_operations.media_duration(self.pj[cfg.OBSERVATIONS], [obs_id])
|
|
791
|
+
obs_interval = self.pj[cfg.OBSERVATIONS][obs_id].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0])
|
|
792
|
+
offset = float(self.pj[cfg.OBSERVATIONS][obs_id][cfg.TIME_OFFSET])
|
|
793
|
+
min_time = float(obs_interval[0]) + offset
|
|
794
|
+
# Use max media duration for max time if no interval is defined (=0)
|
|
795
|
+
max_time = float(obs_interval[1]) + offset if obs_interval[1] != 0 else float(max_media_duration)
|
|
796
|
+
|
|
801
797
|
# delete events outside time interval
|
|
798
|
+
|
|
802
799
|
cursor.execute(
|
|
803
800
|
"DELETE FROM aggregated_events WHERE observation = ? AND (start < ? AND stop < ?) OR (start > ? AND stop > ?)",
|
|
804
801
|
(
|
|
@@ -842,37 +839,30 @@ def export_events_as_textgrid(self) -> None:
|
|
|
842
839
|
|
|
843
840
|
next_obs: bool = False
|
|
844
841
|
|
|
845
|
-
|
|
846
|
-
total_media_duration = round(
|
|
847
|
-
observation_operations.observation_total_length(self.pj[cfg.OBSERVATIONS][obs_id]), 3
|
|
848
|
-
)
|
|
849
|
-
"""
|
|
850
|
-
|
|
842
|
+
# number of items for size parameter
|
|
851
843
|
cursor.execute(
|
|
852
844
|
(
|
|
853
|
-
"SELECT COUNT(
|
|
854
|
-
"WHERE observation = ? AND subject IN ({})
|
|
855
|
-
",".join(["?"] * len(parameters[cfg.SELECTED_SUBJECTS]))
|
|
856
|
-
)
|
|
845
|
+
"SELECT COUNT(*) FROM (SELECT * FROM aggregated_events "
|
|
846
|
+
f"WHERE observation = ? AND subject IN ({','.join(['?'] * len(parameters[cfg.SELECTED_SUBJECTS]))}) GROUP BY subject, behavior) "
|
|
857
847
|
),
|
|
858
848
|
[obs_id] + parameters[cfg.SELECTED_SUBJECTS],
|
|
859
849
|
)
|
|
860
850
|
|
|
861
|
-
|
|
862
|
-
|
|
851
|
+
subjects_num = int(cursor.fetchone()[0])
|
|
852
|
+
subjects_max = max_time
|
|
863
853
|
|
|
864
854
|
out = (
|
|
865
855
|
'File type = "ooTextFile"\n'
|
|
866
856
|
'Object class = "TextGrid"\n'
|
|
867
857
|
"\n"
|
|
868
858
|
f"xmin = 0.0\n"
|
|
869
|
-
f"xmax = {
|
|
859
|
+
f"xmax = {subjects_max}\n"
|
|
870
860
|
"tiers? <exists>\n"
|
|
871
|
-
f"size = {
|
|
861
|
+
f"size = {subjects_num}\n"
|
|
872
862
|
"item []:\n"
|
|
873
863
|
)
|
|
874
864
|
|
|
875
|
-
subject_index = 0
|
|
865
|
+
subject_index: int = 0
|
|
876
866
|
for subject in parameters[cfg.SELECTED_SUBJECTS]:
|
|
877
867
|
if subject not in [
|
|
878
868
|
x[cfg.EVENT_SUBJECT_FIELD_IDX] if x[cfg.EVENT_SUBJECT_FIELD_IDX] else cfg.NO_FOCAL_SUBJECT
|
|
@@ -880,7 +870,8 @@ def export_events_as_textgrid(self) -> None:
|
|
|
880
870
|
]:
|
|
881
871
|
continue
|
|
882
872
|
|
|
883
|
-
intervalsMin
|
|
873
|
+
intervalsMin = min_time
|
|
874
|
+
intervalsMax = max_time
|
|
884
875
|
|
|
885
876
|
# STATE events
|
|
886
877
|
cursor.execute(
|
|
@@ -895,77 +886,70 @@ def export_events_as_textgrid(self) -> None:
|
|
|
895
886
|
{"start": util.float2decimal(r["start"]), "stop": util.float2decimal(r["stop"]), "code": r["behavior"]}
|
|
896
887
|
for r in cursor.fetchall()
|
|
897
888
|
]
|
|
898
|
-
if
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
out += interval_subject_header
|
|
889
|
+
if rows:
|
|
890
|
+
out += interval_subject_header
|
|
902
891
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
# check if 1st behavior starts at the beginning
|
|
906
|
-
if rows[0]["start"] > 0:
|
|
907
|
-
count += 1
|
|
908
|
-
out += interval_template.format(count=count, name="null", xmin=0.0, xmax=rows[0]["start"])
|
|
892
|
+
count = 0
|
|
909
893
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
894
|
+
# check if 1st behavior starts at the beginning
|
|
895
|
+
if rows[0]["start"] > 0:
|
|
896
|
+
count += 1
|
|
897
|
+
out += interval_template.format(count=count, name="null", xmin=0.0, xmax=rows[0]["start"])
|
|
898
|
+
|
|
899
|
+
for idx, row in enumerate(rows):
|
|
900
|
+
# check if events are overlapping
|
|
901
|
+
if (idx + 1 < len(rows)) and (row["stop"] > rows[idx + 1]["start"]):
|
|
902
|
+
self.results.ptText.appendHtml(
|
|
903
|
+
(
|
|
904
|
+
f"The events overlap for subject <b>{subject}</b> in the observation <b>{obs_id}</b>. "
|
|
905
|
+
"It is not possible to create the Praat TextGrid file."
|
|
906
|
+
)
|
|
917
907
|
)
|
|
918
|
-
|
|
919
|
-
QApplication.processEvents()
|
|
908
|
+
QApplication.processEvents()
|
|
920
909
|
|
|
921
|
-
|
|
922
|
-
|
|
910
|
+
next_obs = True
|
|
911
|
+
break
|
|
923
912
|
|
|
924
|
-
|
|
913
|
+
count += 1
|
|
925
914
|
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
915
|
+
if (idx + 1 < len(rows)) and (rows[idx + 1]["start"] - dec("0.001") <= row["stop"] < rows[idx + 1]["start"]):
|
|
916
|
+
xmax = rows[idx + 1]["start"]
|
|
917
|
+
else:
|
|
918
|
+
xmax = row["stop"]
|
|
919
|
+
|
|
920
|
+
out += interval_template.format(count=count, name=row["code"], xmin=row["start"], xmax=xmax)
|
|
921
|
+
|
|
922
|
+
# check if no behavior
|
|
923
|
+
if (idx + 1 < len(rows)) and (row["stop"] < rows[idx + 1]["start"] - dec("0.001")):
|
|
924
|
+
count += 1
|
|
925
|
+
out += interval_template.format(
|
|
926
|
+
count=count,
|
|
927
|
+
name="null",
|
|
928
|
+
xmin=row["stop"],
|
|
929
|
+
xmax=rows[idx + 1]["start"],
|
|
930
|
+
)
|
|
932
931
|
|
|
933
|
-
|
|
932
|
+
if next_obs:
|
|
933
|
+
break
|
|
934
934
|
|
|
935
|
-
# check if
|
|
936
|
-
if
|
|
935
|
+
# check if last event ends at the end of media file
|
|
936
|
+
if rows[-1]["stop"] < max_time:
|
|
937
937
|
count += 1
|
|
938
|
-
out += interval_template.format(
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
if rows[-1]["stop"] < max_time:
|
|
950
|
-
count += 1
|
|
951
|
-
out += interval_template.format(count=count, name="null", xmin=rows[-1]["stop"], xmax=max_time)
|
|
952
|
-
|
|
953
|
-
# add info
|
|
954
|
-
subject_index += 1
|
|
955
|
-
out = out.format(
|
|
956
|
-
subject_index=subject_index,
|
|
957
|
-
subject=subject,
|
|
958
|
-
intervalsSize=count,
|
|
959
|
-
intervalsMin=intervalsMin,
|
|
960
|
-
intervalsMax=intervalsMax,
|
|
961
|
-
)
|
|
938
|
+
out += interval_template.format(count=count, name="null", xmin=rows[-1]["stop"], xmax=max_time)
|
|
939
|
+
|
|
940
|
+
# add info
|
|
941
|
+
subject_index += 1
|
|
942
|
+
out = out.format(
|
|
943
|
+
subject_index=subject_index,
|
|
944
|
+
subject=subject,
|
|
945
|
+
intervalsSize=count,
|
|
946
|
+
intervalsMin=intervalsMin,
|
|
947
|
+
intervalsMax=intervalsMax,
|
|
948
|
+
)
|
|
962
949
|
|
|
963
950
|
# POINT events
|
|
964
951
|
cursor.execute(
|
|
965
|
-
(
|
|
966
|
-
"SELECT start, behavior FROM aggregated_events "
|
|
967
|
-
"WHERE observation = ? AND subject = ? AND type = 'POINT' ORDER BY start"
|
|
968
|
-
),
|
|
952
|
+
("SELECT start, behavior FROM aggregated_events WHERE observation = ? AND subject = ? AND type = 'POINT' ORDER BY start"),
|
|
969
953
|
(obs_id, subject),
|
|
970
954
|
)
|
|
971
955
|
|
|
@@ -995,15 +979,12 @@ def export_events_as_textgrid(self) -> None:
|
|
|
995
979
|
continue
|
|
996
980
|
|
|
997
981
|
# check if file already exists
|
|
998
|
-
if (
|
|
999
|
-
mem_command != cfg.OVERWRITE_ALL
|
|
1000
|
-
and pl.Path(f"{pl.Path(export_dir) / util.safeFileName(obs_id)}.textGrid").is_file()
|
|
1001
|
-
):
|
|
982
|
+
if mem_command != cfg.OVERWRITE_ALL and pl.Path(f"{pl.Path(export_dir) / util.safeFileName(obs_id)}.TextGrid").is_file():
|
|
1002
983
|
if mem_command == cfg.SKIP_ALL:
|
|
1003
984
|
continue
|
|
1004
985
|
mem_command = dialog.MessageDialog(
|
|
1005
986
|
cfg.programName,
|
|
1006
|
-
f"The file <b>{pl.Path(export_dir) / util.safeFileName(obs_id)}.
|
|
987
|
+
f"The file <b>{pl.Path(export_dir) / util.safeFileName(obs_id)}.TextGrid</b> already exists.",
|
|
1007
988
|
[cfg.OVERWRITE, cfg.OVERWRITE_ALL, cfg.SKIP, cfg.SKIP_ALL, cfg.CANCEL],
|
|
1008
989
|
)
|
|
1009
990
|
if mem_command == cfg.CANCEL:
|
|
@@ -1012,17 +993,13 @@ def export_events_as_textgrid(self) -> None:
|
|
|
1012
993
|
continue
|
|
1013
994
|
|
|
1014
995
|
try:
|
|
1015
|
-
with open(f"{pl.Path(export_dir) / util.safeFileName(obs_id)}.
|
|
996
|
+
with open(f"{pl.Path(export_dir) / util.safeFileName(obs_id)}.TextGrid", "w") as f:
|
|
1016
997
|
f.write(out)
|
|
1017
998
|
file_count += 1
|
|
1018
|
-
self.results.ptText.appendHtml(
|
|
1019
|
-
f"File {pl.Path(export_dir) / util.safeFileName(obs_id)}.textGrid was created."
|
|
1020
|
-
)
|
|
999
|
+
self.results.ptText.appendHtml(f"File {pl.Path(export_dir) / util.safeFileName(obs_id)}.TextGrid was created.")
|
|
1021
1000
|
QApplication.processEvents()
|
|
1022
1001
|
except Exception:
|
|
1023
|
-
self.results.ptText.appendHtml(
|
|
1024
|
-
f"The file {pl.Path(export_dir) / util.safeFileName(obs_id)}.textGrid can not be created."
|
|
1025
|
-
)
|
|
1002
|
+
self.results.ptText.appendHtml(f"The file {pl.Path(export_dir) / util.safeFileName(obs_id)}.TextGrid can not be created.")
|
|
1026
1003
|
QApplication.processEvents()
|
|
1027
1004
|
|
|
1028
1005
|
self.results.ptText.appendHtml(f"Done. {file_count} file(s) were created in {export_dir}.")
|