boris-behav-obs 8.12__py3-none-any.whl → 9.7.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of boris-behav-obs might be problematic. Click here for more details.
- boris/__init__.py +1 -1
- boris/__main__.py +1 -1
- boris/about.py +28 -39
- boris/add_modifier.py +122 -109
- boris/add_modifier_ui.py +239 -135
- boris/advanced_event_filtering.py +81 -45
- 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 +42 -49
- boris/config.py +141 -65
- boris/config_file.py +58 -67
- boris/connections.py +107 -61
- boris/converters.py +13 -37
- boris/converters_ui.py +187 -110
- boris/cooccurence.py +250 -0
- boris/core.py +2373 -1786
- boris/core_qrc.py +15895 -10743
- boris/core_ui.py +943 -798
- boris/db_functions.py +17 -42
- boris/dev.py +109 -8
- boris/dialog.py +482 -236
- boris/duration_widget.py +9 -14
- boris/edit_event.py +61 -31
- boris/edit_event_ui.py +208 -97
- boris/event_operations.py +408 -293
- boris/events_cursor.py +25 -17
- boris/events_snapshots.py +36 -82
- boris/exclusion_matrix.py +4 -9
- boris/export_events.py +184 -223
- boris/export_observation.py +74 -100
- boris/external_processes.py +123 -98
- boris/geometric_measurement.py +644 -290
- 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 +17 -6
- boris/modifier_coding_map_creator.py +1013 -0
- boris/modifiers_coding_map.py +7 -9
- boris/mpv.py +1 -0
- boris/mpv2.py +732 -705
- boris/observation.py +533 -221
- boris/observation_operations.py +1025 -390
- boris/observation_ui.py +572 -362
- boris/observations_list.py +71 -53
- boris/otx_parser.py +74 -68
- boris/param_panel.py +31 -16
- boris/param_panel_ui.py +254 -138
- boris/player_dock_widget.py +90 -60
- boris/plot_data_module.py +25 -33
- boris/plot_events.py +127 -90
- boris/plot_events_rt.py +17 -31
- boris/plot_spectrogram_rt.py +95 -30
- boris/plot_waveform_rt.py +32 -21
- 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 +306 -83
- boris/preferences_ui.py +684 -227
- boris/project.py +448 -293
- boris/project_functions.py +671 -238
- boris/project_import_export.py +213 -222
- boris/project_ui.py +674 -438
- boris/qrc_boris.py +6 -3
- boris/qrc_boris5.py +6 -3
- boris/select_modifiers.py +74 -48
- boris/select_observations.py +20 -198
- boris/select_subj_behav.py +67 -39
- boris/state_events.py +52 -35
- boris/subjects_pad.py +6 -9
- boris/synthetic_time_budget.py +45 -28
- boris/time_budget_functions.py +171 -171
- boris/time_budget_widget.py +84 -114
- boris/transitions.py +41 -47
- boris/utilities.py +627 -236
- boris/version.py +3 -3
- boris/video_equalizer.py +16 -14
- boris/video_equalizer_ui.py +199 -130
- boris/video_operations.py +95 -29
- boris/view_df.py +104 -0
- boris/view_df_ui.py +75 -0
- boris/write_event.py +538 -0
- boris_behav_obs-9.7.6.dist-info/METADATA +139 -0
- boris_behav_obs-9.7.6.dist-info/RECORD +109 -0
- {boris_behav_obs-8.12.dist-info → boris_behav_obs-9.7.6.dist-info}/WHEEL +1 -1
- boris_behav_obs-9.7.6.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 -36
- boris/core.ui +0 -1556
- boris/edit_event.ui +0 -233
- boris/icons/logo_eye.ico +0 -0
- boris/map_creator.py +0 -850
- boris/observation.ui +0 -814
- boris/param_panel.ui +0 -379
- boris/preferences.ui +0 -537
- boris/project.ui +0 -1069
- boris/project_server.py +0 -236
- boris/vlc.py +0 -10343
- boris/vlc_local.py +0 -90
- boris_behav_obs-8.12.dist-info/LICENSE.TXT +0 -674
- boris_behav_obs-8.12.dist-info/METADATA +0 -128
- boris_behav_obs-8.12.dist-info/RECORD +0 -108
- boris_behav_obs-8.12.dist-info/entry_points.txt +0 -3
- {boris → boris_behav_obs-9.7.6.dist-info/licenses}/LICENSE.TXT +0 -0
- {boris_behav_obs-8.12.dist-info → boris_behav_obs-9.7.6.dist-info}/top_level.txt +0 -0
boris/time_budget_widget.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
|
|
|
@@ -26,6 +26,7 @@ import pathlib as pl
|
|
|
26
26
|
from decimal import Decimal as dec
|
|
27
27
|
from io import StringIO
|
|
28
28
|
import pandas as pd
|
|
29
|
+
import time
|
|
29
30
|
|
|
30
31
|
try:
|
|
31
32
|
import pyreadr
|
|
@@ -36,8 +37,8 @@ except ModuleNotFoundError:
|
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
import tablib
|
|
39
|
-
from
|
|
40
|
-
from
|
|
40
|
+
from PySide6.QtCore import Qt
|
|
41
|
+
from PySide6.QtWidgets import (
|
|
41
42
|
QFileDialog,
|
|
42
43
|
QHBoxLayout,
|
|
43
44
|
QInputDialog,
|
|
@@ -51,6 +52,7 @@ from PyQt5.QtWidgets import (
|
|
|
51
52
|
QTableWidgetItem,
|
|
52
53
|
QVBoxLayout,
|
|
53
54
|
QWidget,
|
|
55
|
+
QApplication,
|
|
54
56
|
)
|
|
55
57
|
|
|
56
58
|
from . import config as cfg
|
|
@@ -110,7 +112,7 @@ class timeBudgetResults(QWidget):
|
|
|
110
112
|
spacerItem = QSpacerItem(241, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
|
|
111
113
|
hbox2.addItem(spacerItem)
|
|
112
114
|
|
|
113
|
-
self.pbClose = QPushButton(
|
|
115
|
+
self.pbClose = QPushButton(cfg.CLOSE, clicked=self.close_clicked)
|
|
114
116
|
hbox2.addWidget(self.pbClose)
|
|
115
117
|
|
|
116
118
|
hbox.addLayout(hbox2)
|
|
@@ -129,48 +131,47 @@ class timeBudgetResults(QWidget):
|
|
|
129
131
|
save time budget analysis results in TSV, CSV, ODS, XLS format
|
|
130
132
|
"""
|
|
131
133
|
|
|
132
|
-
def complete(
|
|
134
|
+
def complete(lst: list, max_: int) -> list:
|
|
133
135
|
"""
|
|
134
136
|
complete list with empty string until len = max
|
|
135
137
|
|
|
136
138
|
Args:
|
|
137
|
-
|
|
139
|
+
lst (list): list to complete
|
|
138
140
|
max_ (int): length of the returned list
|
|
139
141
|
|
|
140
142
|
Returns:
|
|
141
143
|
list: completed list
|
|
142
144
|
"""
|
|
143
145
|
|
|
144
|
-
|
|
145
|
-
return
|
|
146
|
+
lst.extend([""] * (max_ - len(lst)))
|
|
147
|
+
return lst
|
|
146
148
|
|
|
147
149
|
logging.debug("save time budget results to file")
|
|
148
150
|
|
|
149
|
-
file_formats =
|
|
151
|
+
file_formats = (cfg.TSV, cfg.CSV, cfg.ODS, cfg.XLSX, cfg.XLS, cfg.HTML, cfg.TEXT_FILE, cfg.PANDAS_DF, cfg.RDS)
|
|
150
152
|
|
|
151
|
-
file_name, filter_ = QFileDialog().getSaveFileName(
|
|
152
|
-
self, "Save Time budget analysis", "", ";;".join(file_formats)
|
|
153
|
-
)
|
|
153
|
+
file_name, filter_ = QFileDialog().getSaveFileName(self, "Save Time budget analysis", "", ";;".join(file_formats))
|
|
154
154
|
|
|
155
155
|
if not file_name:
|
|
156
156
|
return
|
|
157
157
|
|
|
158
158
|
# add correct file extension if not present
|
|
159
159
|
if pl.Path(file_name).suffix != f".{cfg.FILE_NAME_SUFFIX[filter_]}":
|
|
160
|
-
|
|
160
|
+
if cfg.FILE_NAME_SUFFIX[filter_] != "cli":
|
|
161
|
+
file_name = str(pl.Path(file_name)) + "." + cfg.FILE_NAME_SUFFIX[filter_]
|
|
162
|
+
else:
|
|
163
|
+
file_name = str(pl.Path(file_name))
|
|
161
164
|
# check if file with new extension already exists
|
|
162
165
|
if pl.Path(file_name).is_file():
|
|
163
166
|
if (
|
|
164
|
-
dialog.MessageDialog(
|
|
165
|
-
cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE]
|
|
166
|
-
)
|
|
167
|
+
dialog.MessageDialog(cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE])
|
|
167
168
|
== cfg.CANCEL
|
|
168
169
|
):
|
|
169
170
|
return
|
|
170
171
|
|
|
171
|
-
rows = []
|
|
172
|
+
rows: list = []
|
|
172
173
|
|
|
173
|
-
header = ["Observation id", "Observation date", "Description"]
|
|
174
|
+
header: list = ["Observation id", "Observation date", "Description"]
|
|
174
175
|
# indep var labels
|
|
175
176
|
header.extend([self.pj[cfg.INDEPENDENT_VARIABLES][idx]["label"] for idx in self.pj[cfg.INDEPENDENT_VARIABLES]])
|
|
176
177
|
header.extend(["Time budget start", "Time budget stop", "Time budget duration"])
|
|
@@ -203,9 +204,9 @@ class timeBudgetResults(QWidget):
|
|
|
203
204
|
for idx in self.pj.get(cfg.INDEPENDENT_VARIABLES, []):
|
|
204
205
|
if self.lw.count() == 1:
|
|
205
206
|
# var has value in obs?
|
|
206
|
-
if self.pj[cfg.INDEPENDENT_VARIABLES][idx]["label"] in self.pj[cfg.OBSERVATIONS][
|
|
207
|
-
|
|
208
|
-
|
|
207
|
+
if self.pj[cfg.INDEPENDENT_VARIABLES][idx]["label"] in self.pj[cfg.OBSERVATIONS][self.lw.item(0).text()].get(
|
|
208
|
+
cfg.INDEPENDENT_VARIABLES, []
|
|
209
|
+
):
|
|
209
210
|
col1.append(
|
|
210
211
|
self.pj[cfg.OBSERVATIONS][self.lw.item(0).text()][cfg.INDEPENDENT_VARIABLES][
|
|
211
212
|
self.pj[cfg.INDEPENDENT_VARIABLES][idx]["label"]
|
|
@@ -221,10 +222,10 @@ class timeBudgetResults(QWidget):
|
|
|
221
222
|
col1.extend([f"{self.min_time:0.3f}", f"{self.max_time:0.3f}", f"{self.max_time - self.min_time:0.3f}"])
|
|
222
223
|
|
|
223
224
|
if self.time_interval == cfg.TIME_FULL_OBS:
|
|
224
|
-
col1.extend([
|
|
225
|
+
col1.extend(["Full observation", "Full observation", "Full observation"])
|
|
225
226
|
|
|
226
227
|
if self.time_interval == cfg.TIME_EVENTS:
|
|
227
|
-
col1.extend([
|
|
228
|
+
col1.extend(["Limited to coded events", "Limited to coded events", "Limited to coded events"])
|
|
228
229
|
|
|
229
230
|
for row_idx in range(self.twTB.rowCount()):
|
|
230
231
|
values = []
|
|
@@ -376,7 +377,7 @@ class timeBudgetResults(QWidget):
|
|
|
376
377
|
|
|
377
378
|
# write results
|
|
378
379
|
with open(file_name, "wb") as f:
|
|
379
|
-
if filter_ in (cfg.TSV, cfg.CSV, cfg.HTML):
|
|
380
|
+
if filter_ in (cfg.TSV, cfg.CSV, cfg.HTML, cfg.TEXT_FILE):
|
|
380
381
|
f.write(str.encode(data.export(cfg.FILE_NAME_SUFFIX[filter_])))
|
|
381
382
|
if filter_ in (cfg.ODS, cfg.XLSX, cfg.XLS):
|
|
382
383
|
f.write(data.export(cfg.FILE_NAME_SUFFIX[filter_]))
|
|
@@ -389,6 +390,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
389
390
|
Args:
|
|
390
391
|
mode (str): ["by_behavior", "by_category"]
|
|
391
392
|
mode2 (str): must be in ["list", "current"]
|
|
393
|
+
"current" time budget of current observation
|
|
392
394
|
"""
|
|
393
395
|
|
|
394
396
|
if mode2 == "current":
|
|
@@ -415,9 +417,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
415
417
|
flagGroup: bool = False
|
|
416
418
|
if len(selected_observations) > 1:
|
|
417
419
|
flagGroup = (
|
|
418
|
-
dialog.MessageDialog(
|
|
419
|
-
cfg.programName, "Group the selected observations in a single time budget analysis?", [cfg.YES, cfg.NO]
|
|
420
|
-
)
|
|
420
|
+
dialog.MessageDialog(cfg.programName, "Group the selected observations in a single time budget analysis?", [cfg.YES, cfg.NO])
|
|
421
421
|
== cfg.YES
|
|
422
422
|
)
|
|
423
423
|
|
|
@@ -425,20 +425,25 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
425
425
|
self.pj[cfg.OBSERVATIONS], selected_observations
|
|
426
426
|
)
|
|
427
427
|
|
|
428
|
-
logging.debug(
|
|
429
|
-
f"max_media_duration_all_obs: {max_media_duration_all_obs}, total_media_duration_all_obs={total_media_duration_all_obs}"
|
|
430
|
-
)
|
|
428
|
+
logging.debug(f"max_media_duration_all_obs: {max_media_duration_all_obs}, total_media_duration_all_obs={total_media_duration_all_obs}")
|
|
431
429
|
|
|
432
430
|
start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
433
431
|
|
|
434
|
-
|
|
432
|
+
start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
433
|
+
|
|
434
|
+
parameters: dict = select_subj_behav.choose_obs_subj_behav_category(
|
|
435
435
|
self,
|
|
436
436
|
selected_observations,
|
|
437
437
|
start_coding=start_coding,
|
|
438
438
|
end_coding=end_coding,
|
|
439
|
+
# start_interval=start_interval,
|
|
440
|
+
# end_interval=end_interval,
|
|
441
|
+
start_interval=None,
|
|
442
|
+
end_interval=None,
|
|
439
443
|
maxTime=max_media_duration_all_obs,
|
|
440
444
|
by_category=(mode == "by_category"),
|
|
441
445
|
n_observations=len(selected_observations),
|
|
446
|
+
show_exclude_non_coded_modifiers=True,
|
|
442
447
|
)
|
|
443
448
|
if parameters == {}:
|
|
444
449
|
return
|
|
@@ -447,21 +452,27 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
447
452
|
QMessageBox.warning(None, cfg.programName, "Select subject(s) and behavior(s) to analyze")
|
|
448
453
|
return
|
|
449
454
|
|
|
455
|
+
logging.debug(f"{parameters=}")
|
|
456
|
+
|
|
450
457
|
# ask for excluding behaviors durations from total time
|
|
451
|
-
if not start_coding.is_nan():
|
|
458
|
+
if start_coding is not None and not start_coding.is_nan():
|
|
452
459
|
cancel_pressed, parameters[cfg.EXCLUDED_BEHAVIORS] = self.filter_behaviors(
|
|
453
460
|
title="Select behaviors to exclude from the total time",
|
|
454
|
-
text=("The duration of the selected behaviors will
|
|
461
|
+
text=("The duration of the selected behaviors will be subtracted from the total time"),
|
|
455
462
|
table="",
|
|
456
|
-
behavior_type=
|
|
463
|
+
behavior_type=cfg.STATE_EVENT_TYPES,
|
|
457
464
|
)
|
|
458
465
|
if cancel_pressed:
|
|
459
466
|
return
|
|
460
467
|
else:
|
|
461
468
|
parameters[cfg.EXCLUDED_BEHAVIORS] = []
|
|
462
469
|
|
|
470
|
+
self.statusbar.showMessage(f"Generating time budget for {len(selected_observations)} observation(s)")
|
|
471
|
+
QApplication.processEvents()
|
|
472
|
+
|
|
463
473
|
# check if time_budget window must be used
|
|
464
474
|
if flagGroup or len(selected_observations) == 1:
|
|
475
|
+
t0 = time.time()
|
|
465
476
|
|
|
466
477
|
cursor = db_functions.load_events_in_db(
|
|
467
478
|
self.pj,
|
|
@@ -473,7 +484,6 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
473
484
|
|
|
474
485
|
total_observation_time = 0
|
|
475
486
|
for obsId in selected_observations:
|
|
476
|
-
|
|
477
487
|
obs_length = observation_operations.observation_total_length(self.pj[cfg.OBSERVATIONS][obsId])
|
|
478
488
|
|
|
479
489
|
if obs_length == dec(-1): # media length not available
|
|
@@ -482,7 +492,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
482
492
|
if obs_length == dec(-2): # images obs without time
|
|
483
493
|
parameters[cfg.TIME_INTERVAL] = cfg.TIME_EVENTS
|
|
484
494
|
|
|
485
|
-
if parameters[cfg.TIME_INTERVAL] == cfg.TIME_FULL_OBS:
|
|
495
|
+
if parameters[cfg.TIME_INTERVAL] == cfg.TIME_FULL_OBS: # media file duration
|
|
486
496
|
min_time = float(0)
|
|
487
497
|
# check if the last event is recorded after media file length
|
|
488
498
|
try:
|
|
@@ -493,7 +503,14 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
493
503
|
except Exception:
|
|
494
504
|
max_time = float(obs_length)
|
|
495
505
|
|
|
496
|
-
if parameters[cfg.TIME_INTERVAL] == cfg.
|
|
506
|
+
if parameters[cfg.TIME_INTERVAL] == cfg.TIME_OBS_INTERVAL:
|
|
507
|
+
obs_interval = self.pj[cfg.OBSERVATIONS][obsId].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0])
|
|
508
|
+
offset = float(self.pj[cfg.OBSERVATIONS][obsId][cfg.TIME_OFFSET])
|
|
509
|
+
min_time = float(obs_interval[0]) + offset
|
|
510
|
+
# Use max media duration for max time if no interval is defined (=0)
|
|
511
|
+
max_time = float(obs_interval[1]) + offset if obs_interval[1] != 0 else float(obs_length)
|
|
512
|
+
|
|
513
|
+
if parameters[cfg.TIME_INTERVAL] == cfg.TIME_EVENTS: # events duration
|
|
497
514
|
try:
|
|
498
515
|
min_time = float(self.pj[cfg.OBSERVATIONS][obsId][cfg.EVENTS][0][0]) # first event
|
|
499
516
|
except Exception:
|
|
@@ -511,8 +528,10 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
511
528
|
# check intervals
|
|
512
529
|
for subj in parameters[cfg.SELECTED_SUBJECTS]:
|
|
513
530
|
for behav in parameters[cfg.SELECTED_BEHAVIORS]:
|
|
514
|
-
if cfg.POINT in self.eventType(behav).upper():
|
|
531
|
+
# if cfg.POINT in self.eventType(behav).upper():
|
|
532
|
+
if project_functions.event_type(behav, self.pj[cfg.ETHOGRAM]) in cfg.POINT_EVENT_TYPES:
|
|
515
533
|
continue
|
|
534
|
+
|
|
516
535
|
# extract modifiers
|
|
517
536
|
|
|
518
537
|
cursor.execute(
|
|
@@ -524,7 +543,6 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
524
543
|
# logging.debug("distinct_modifiers: {}".format(distinct_modifiers))
|
|
525
544
|
|
|
526
545
|
for modifier in distinct_modifiers:
|
|
527
|
-
|
|
528
546
|
# logging.debug("modifier #{}#".format(modifier[0]))
|
|
529
547
|
|
|
530
548
|
# insert events at boundaries of time interval
|
|
@@ -541,12 +559,8 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
541
559
|
)
|
|
542
560
|
% 2
|
|
543
561
|
):
|
|
544
|
-
|
|
545
562
|
cursor.execute(
|
|
546
|
-
(
|
|
547
|
-
"INSERT INTO events (observation, subject, code, type, modifiers, occurence) "
|
|
548
|
-
"VALUES (?,?,?,?,?,?)"
|
|
549
|
-
),
|
|
563
|
+
("INSERT INTO events (observation, subject, code, type, modifiers, occurence) VALUES (?,?,?,?,?,?)"),
|
|
550
564
|
(obsId, subj, behav, "STATE", modifier[0], min_time),
|
|
551
565
|
)
|
|
552
566
|
|
|
@@ -562,12 +576,8 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
562
576
|
)
|
|
563
577
|
% 2
|
|
564
578
|
):
|
|
565
|
-
|
|
566
579
|
cursor.execute(
|
|
567
|
-
(
|
|
568
|
-
"INSERT INTO events (observation, subject, code, type, modifiers, occurence) "
|
|
569
|
-
"VALUES (?,?,?,?,?,?)"
|
|
570
|
-
),
|
|
580
|
+
("INSERT INTO events (observation, subject, code, type, modifiers, occurence) VALUES (?,?,?,?,?,?)"),
|
|
571
581
|
(obsId, subj, behav, "STATE", modifier[0], max_time),
|
|
572
582
|
)
|
|
573
583
|
try:
|
|
@@ -582,6 +592,10 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
582
592
|
"DELETE FROM events WHERE observation = ? AND (occurence < ? OR occurence > ?)",
|
|
583
593
|
(obsId, min_time, max_time),
|
|
584
594
|
)
|
|
595
|
+
try:
|
|
596
|
+
cursor.execute("COMMIT")
|
|
597
|
+
except Exception:
|
|
598
|
+
pass
|
|
585
599
|
|
|
586
600
|
out, categories = time_budget_functions.time_budget_analysis(
|
|
587
601
|
self.pj[cfg.ETHOGRAM], cursor, selected_observations, parameters, by_category=(mode == "by_category")
|
|
@@ -593,9 +607,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
593
607
|
if element["subject"] not in excl_behaviors_total_time:
|
|
594
608
|
excl_behaviors_total_time[element["subject"]] = 0
|
|
595
609
|
if element["behavior"] in parameters[cfg.EXCLUDED_BEHAVIORS]:
|
|
596
|
-
excl_behaviors_total_time[element["subject"]] += (
|
|
597
|
-
element["duration"] if not isinstance(element["duration"], str) else 0
|
|
598
|
-
)
|
|
610
|
+
excl_behaviors_total_time[element["subject"]] += element["duration"] if not isinstance(element["duration"], str) else 0
|
|
599
611
|
|
|
600
612
|
# widget for results visualization
|
|
601
613
|
self.tb = timeBudgetResults(self.pj, self.config_param)
|
|
@@ -615,20 +627,14 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
615
627
|
if len(selected_observations) > 1:
|
|
616
628
|
if total_observation_time:
|
|
617
629
|
if self.timeFormat == cfg.HHMMSS:
|
|
618
|
-
self.tb.lbTotalObservedTime.setText(
|
|
619
|
-
f"Total observation length: {util.seconds2time(total_observation_time)}"
|
|
620
|
-
)
|
|
630
|
+
self.tb.lbTotalObservedTime.setText(f"Total observation length: {util.seconds2time(total_observation_time)}")
|
|
621
631
|
if self.timeFormat == cfg.S:
|
|
622
|
-
self.tb.lbTotalObservedTime.setText(
|
|
623
|
-
f"Total observation length: {float(total_observation_time):0.3f}"
|
|
624
|
-
)
|
|
632
|
+
self.tb.lbTotalObservedTime.setText(f"Total observation length: {float(total_observation_time):0.3f}")
|
|
625
633
|
else:
|
|
626
634
|
self.tb.lbTotalObservedTime.setText("Total observation length: not available")
|
|
627
635
|
else:
|
|
628
636
|
if self.timeFormat == cfg.HHMMSS:
|
|
629
|
-
self.tb.lbTotalObservedTime.setText(
|
|
630
|
-
f"Analysis from {util.seconds2time(min_time)} to {util.seconds2time(max_time)}"
|
|
631
|
-
)
|
|
637
|
+
self.tb.lbTotalObservedTime.setText(f"Analysis from {util.seconds2time(min_time)} to {util.seconds2time(max_time)}")
|
|
632
638
|
if self.timeFormat == cfg.S:
|
|
633
639
|
self.tb.lbTotalObservedTime.setText(f"Analysis from {float(min_time):0.3f} to {float(max_time):0.3f} s")
|
|
634
640
|
|
|
@@ -640,8 +646,10 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
640
646
|
else:
|
|
641
647
|
self.tb.excluded_behaviors_list.setVisible(False)
|
|
642
648
|
|
|
643
|
-
|
|
649
|
+
self.statusbar.showMessage(f"Time budget generated in {round(time.time() - t0, 3)} s", 5000)
|
|
650
|
+
logging.debug("Time budget generated")
|
|
644
651
|
|
|
652
|
+
if mode == "by_behavior":
|
|
645
653
|
tb_fields = [
|
|
646
654
|
"Subject",
|
|
647
655
|
"Behavior",
|
|
@@ -673,7 +681,6 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
673
681
|
self.tb.twTB.setRowCount(self.tb.twTB.rowCount() + 1)
|
|
674
682
|
column = 0
|
|
675
683
|
for field in fields:
|
|
676
|
-
|
|
677
684
|
if isinstance(row[field], float):
|
|
678
685
|
item = QTableWidgetItem(f"{row[field]:.3f}")
|
|
679
686
|
else:
|
|
@@ -689,10 +696,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
689
696
|
elif row["duration"] not in ("-", cfg.UNPAIRED) and not start_coding.is_nan():
|
|
690
697
|
tot_time = float(total_observation_time)
|
|
691
698
|
# substract time of excluded behaviors from the total for the subject
|
|
692
|
-
if
|
|
693
|
-
row["subject"] in excl_behaviors_total_time
|
|
694
|
-
and row["behavior"] not in parameters[cfg.EXCLUDED_BEHAVIORS]
|
|
695
|
-
):
|
|
699
|
+
if row["subject"] in excl_behaviors_total_time and row["behavior"] not in parameters[cfg.EXCLUDED_BEHAVIORS]:
|
|
696
700
|
tot_time -= excl_behaviors_total_time[row["subject"]]
|
|
697
701
|
item = QTableWidgetItem(f"{row['duration'] / tot_time * 100:.1f}" if tot_time > 0 else cfg.NA)
|
|
698
702
|
|
|
@@ -703,7 +707,6 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
703
707
|
self.tb.twTB.setItem(self.tb.twTB.rowCount() - 1, column, item)
|
|
704
708
|
|
|
705
709
|
if mode == "by_category":
|
|
706
|
-
|
|
707
710
|
tb_fields = ["Subject", "Category", "Total number", "Total duration (s)"]
|
|
708
711
|
fields = ["number", "duration"]
|
|
709
712
|
|
|
@@ -711,9 +714,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
711
714
|
self.tb.twTB.setHorizontalHeaderLabels(tb_fields)
|
|
712
715
|
|
|
713
716
|
for subject in categories:
|
|
714
|
-
|
|
715
717
|
for category in categories[subject]:
|
|
716
|
-
|
|
717
718
|
self.tb.twTB.setRowCount(self.tb.twTB.rowCount() + 1)
|
|
718
719
|
|
|
719
720
|
column = 0
|
|
@@ -745,12 +746,11 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
745
746
|
|
|
746
747
|
self.tb.twTB.resizeColumnsToContents()
|
|
747
748
|
|
|
748
|
-
gui_utilities.restore_geometry(self.tb, "time budget", (
|
|
749
|
+
gui_utilities.restore_geometry(self.tb, "time budget", (800, 600))
|
|
749
750
|
|
|
750
751
|
self.tb.show()
|
|
751
752
|
|
|
752
753
|
if not flagGroup and len(selected_observations) > 1:
|
|
753
|
-
|
|
754
754
|
output_format, ok = QInputDialog.getItem(
|
|
755
755
|
self,
|
|
756
756
|
"Time budget analysis format",
|
|
@@ -776,9 +776,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
776
776
|
if output_format in (cfg.ODS_WB, cfg.XLSX_WB):
|
|
777
777
|
workbook = tablib.Databook()
|
|
778
778
|
|
|
779
|
-
wb_file_name, filter_ = QFileDialog(self).getSaveFileName(
|
|
780
|
-
self, "Save Time budget analysis", "", output_format
|
|
781
|
-
)
|
|
779
|
+
wb_file_name, filter_ = QFileDialog(self).getSaveFileName(self, "Save Time budget analysis", "", output_format)
|
|
782
780
|
if not wb_file_name:
|
|
783
781
|
return
|
|
784
782
|
|
|
@@ -787,9 +785,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
787
785
|
# check if file with new extension already exists
|
|
788
786
|
if pl.Path(wb_file_name).is_file():
|
|
789
787
|
if (
|
|
790
|
-
dialog.MessageDialog(
|
|
791
|
-
cfg.programName, f"The file {wb_file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE]
|
|
792
|
-
)
|
|
788
|
+
dialog.MessageDialog(cfg.programName, f"The file {wb_file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE])
|
|
793
789
|
== cfg.CANCEL
|
|
794
790
|
):
|
|
795
791
|
return
|
|
@@ -805,7 +801,6 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
805
801
|
return
|
|
806
802
|
|
|
807
803
|
if mode == "by_behavior":
|
|
808
|
-
|
|
809
804
|
tb_fields = [
|
|
810
805
|
"Subject",
|
|
811
806
|
"Behavior",
|
|
@@ -831,23 +826,19 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
831
826
|
]
|
|
832
827
|
|
|
833
828
|
if mode == "by_category":
|
|
834
|
-
|
|
835
829
|
tb_fields = ["Subject", "Category", "Total number of occurences", "Total duration (s)"]
|
|
836
830
|
fields = ["subject", "category", "number", "duration"]
|
|
837
831
|
|
|
838
832
|
mem_command = ""
|
|
839
833
|
for obsId in selected_observations:
|
|
840
|
-
|
|
841
|
-
cursor = db_functions.load_events_in_db(
|
|
842
|
-
self.pj, parameters[cfg.SELECTED_SUBJECTS], [obsId], parameters[cfg.SELECTED_BEHAVIORS]
|
|
843
|
-
)
|
|
834
|
+
cursor = db_functions.load_events_in_db(self.pj, parameters[cfg.SELECTED_SUBJECTS], [obsId], parameters[cfg.SELECTED_BEHAVIORS])
|
|
844
835
|
|
|
845
836
|
obs_length = observation_operations.observation_total_length(self.pj[cfg.OBSERVATIONS][obsId])
|
|
846
837
|
|
|
847
838
|
if obs_length == -1:
|
|
848
839
|
obs_length = 0
|
|
849
840
|
|
|
850
|
-
if parameters[
|
|
841
|
+
if parameters[cfg.TIME_INTERVAL] == cfg.TIME_FULL_OBS:
|
|
851
842
|
min_time = float(0)
|
|
852
843
|
# check if the last event is recorded after media file length
|
|
853
844
|
try:
|
|
@@ -858,7 +849,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
858
849
|
except Exception:
|
|
859
850
|
max_time = float(obs_length)
|
|
860
851
|
|
|
861
|
-
if parameters[
|
|
852
|
+
if parameters[cfg.TIME_INTERVAL] == cfg.TIME_EVENTS:
|
|
862
853
|
try:
|
|
863
854
|
min_time = float(self.pj[cfg.OBSERVATIONS][obsId][cfg.EVENTS][0][0])
|
|
864
855
|
except Exception:
|
|
@@ -868,19 +859,15 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
868
859
|
except Exception:
|
|
869
860
|
max_time = float(obs_length)
|
|
870
861
|
|
|
871
|
-
if parameters[
|
|
862
|
+
if parameters[cfg.TIME_INTERVAL] == cfg.TIME_ARBITRARY_INTERVAL:
|
|
872
863
|
min_time = float(parameters[cfg.START_TIME])
|
|
873
864
|
max_time = float(parameters[cfg.END_TIME])
|
|
874
865
|
|
|
875
866
|
# check intervals
|
|
876
867
|
for subj in parameters[cfg.SELECTED_SUBJECTS]:
|
|
877
868
|
for behav in parameters[cfg.SELECTED_BEHAVIORS]:
|
|
878
|
-
if cfg.
|
|
879
|
-
behav, self.pj[cfg.ETHOGRAM]
|
|
880
|
-
): # self.eventType(behav).upper():
|
|
869
|
+
if project_functions.event_type(behav, self.pj[cfg.ETHOGRAM]) in cfg.POINT_EVENT_TYPES:
|
|
881
870
|
continue
|
|
882
|
-
# extract modifiers
|
|
883
|
-
# if plot_parameters["include modifiers"]:
|
|
884
871
|
|
|
885
872
|
cursor.execute(
|
|
886
873
|
"SELECT distinct modifiers FROM events WHERE observation = ? AND subject = ? AND code = ?",
|
|
@@ -889,7 +876,6 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
889
876
|
distinct_modifiers = list(cursor.fetchall())
|
|
890
877
|
|
|
891
878
|
for modifier in distinct_modifiers:
|
|
892
|
-
|
|
893
879
|
if (
|
|
894
880
|
len(
|
|
895
881
|
cursor.execute(
|
|
@@ -904,10 +890,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
904
890
|
% 2
|
|
905
891
|
):
|
|
906
892
|
cursor.execute(
|
|
907
|
-
(
|
|
908
|
-
"INSERT INTO events (observation, subject, code, type, modifiers, occurence) "
|
|
909
|
-
"VALUES (?,?,?,?,?,?)"
|
|
910
|
-
),
|
|
893
|
+
("INSERT INTO events (observation, subject, code, type, modifiers, occurence) VALUES (?,?,?,?,?,?)"),
|
|
911
894
|
(obsId, subj, behav, "STATE", modifier[0], min_time),
|
|
912
895
|
)
|
|
913
896
|
if (
|
|
@@ -923,10 +906,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
923
906
|
% 2
|
|
924
907
|
):
|
|
925
908
|
cursor.execute(
|
|
926
|
-
(
|
|
927
|
-
"INSERT INTO events (observation, subject, code, type, modifiers, occurence) "
|
|
928
|
-
"VALUES (?,?,?,?,?,?)"
|
|
929
|
-
),
|
|
909
|
+
("INSERT INTO events (observation, subject, code, type, modifiers, occurence) VALUES (?,?,?,?,?,?)"),
|
|
930
910
|
(obsId, subj, behav, cfg.STATE, modifier[0], max_time),
|
|
931
911
|
)
|
|
932
912
|
try:
|
|
@@ -949,9 +929,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
949
929
|
if element["subject"] not in excl_behaviors_total_time:
|
|
950
930
|
excl_behaviors_total_time[element["subject"]] = 0
|
|
951
931
|
if element["behavior"] in parameters[cfg.EXCLUDED_BEHAVIORS]:
|
|
952
|
-
excl_behaviors_total_time[element["subject"]] +=
|
|
953
|
-
element["duration"] if element["duration"] != "NA" else 0
|
|
954
|
-
)
|
|
932
|
+
excl_behaviors_total_time[element["subject"]] += element["duration"] if element["duration"] != "NA" else 0
|
|
955
933
|
|
|
956
934
|
rows: list = []
|
|
957
935
|
col1: list = []
|
|
@@ -965,9 +943,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
965
943
|
indep_var_values: list = []
|
|
966
944
|
for _, v in self.pj.get(cfg.INDEPENDENT_VARIABLES, {}).items():
|
|
967
945
|
indep_var_label.append(v["label"])
|
|
968
|
-
indep_var_values.append(
|
|
969
|
-
self.pj[cfg.OBSERVATIONS][obsId].get(cfg.INDEPENDENT_VARIABLES, {}).get(v["label"], "")
|
|
970
|
-
)
|
|
946
|
+
indep_var_values.append(self.pj[cfg.OBSERVATIONS][obsId].get(cfg.INDEPENDENT_VARIABLES, {}).get(v["label"], ""))
|
|
971
947
|
|
|
972
948
|
header.extend(indep_var_label)
|
|
973
949
|
col1.extend(indep_var_values)
|
|
@@ -980,7 +956,6 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
980
956
|
header.extend(["Time budget start", "Time budget stop", "Time budget duration"])
|
|
981
957
|
|
|
982
958
|
if mode == "by_behavior":
|
|
983
|
-
|
|
984
959
|
# header
|
|
985
960
|
rows.append(header + tb_fields)
|
|
986
961
|
|
|
@@ -994,10 +969,7 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
994
969
|
elif row["duration"] not in ("-", cfg.UNPAIRED) and not start_coding.is_nan():
|
|
995
970
|
tot_time = float(max_time - min_time)
|
|
996
971
|
# substract duration of excluded behaviors from total time for each subject
|
|
997
|
-
if
|
|
998
|
-
row["subject"] in excl_behaviors_total_time
|
|
999
|
-
and row["behavior"] not in parameters[cfg.EXCLUDED_BEHAVIORS]
|
|
1000
|
-
):
|
|
972
|
+
if row["subject"] in excl_behaviors_total_time and row["behavior"] not in parameters[cfg.EXCLUDED_BEHAVIORS]:
|
|
1001
973
|
tot_time -= excl_behaviors_total_time[row["subject"]]
|
|
1002
974
|
# % of tot time
|
|
1003
975
|
values.append(round(row["duration"] / tot_time * 100, 1) if tot_time > 0 else cfg.NA)
|
|
@@ -1010,7 +982,6 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
1010
982
|
rows.append(header + tb_fields)
|
|
1011
983
|
|
|
1012
984
|
for subject in categories:
|
|
1013
|
-
|
|
1014
985
|
for category in categories[subject]:
|
|
1015
986
|
values = []
|
|
1016
987
|
values.append(subject)
|
|
@@ -1039,7 +1010,6 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
1039
1010
|
workbook.add_sheet(data)
|
|
1040
1011
|
|
|
1041
1012
|
else:
|
|
1042
|
-
|
|
1043
1013
|
file_name = f"{pl.Path(exportDir) / pl.Path(util.safeFileName(obsId))}.{extension}"
|
|
1044
1014
|
if mem_command != cfg.OVERWRITE_ALL and pl.Path(file_name).is_file():
|
|
1045
1015
|
if mem_command == "Skip all":
|