boris-behav-obs 9.1.1__tar.gz → 9.2__tar.gz
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_behav_obs-9.1.1 → boris_behav_obs-9.2}/PKG-INFO +1 -1
- boris_behav_obs-9.2/boris/analysis_plugins/number_of_occurences.py +22 -0
- boris_behav_obs-9.2/boris/analysis_plugins/number_of_occurences_by_independent_variable.py +34 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/analysis_plugins/time_budget.py +9 -47
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/cooccurence.py +2 -1
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/core.py +11 -91
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/observation_operations.py +5 -5
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/param_panel.py +6 -0
- boris_behav_obs-9.2/boris/plugins.py +247 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/project_functions.py +37 -22
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/select_subj_behav.py +4 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/version.py +2 -2
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris_behav_obs.egg-info/PKG-INFO +1 -1
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/pyproject.toml +2 -2
- boris_behav_obs-9.1.1/boris/analysis_plugins/number_of_occurences.py +0 -62
- boris_behav_obs-9.1.1/boris/analysis_plugins/number_of_occurences_by_independent_variable.py +0 -74
- boris_behav_obs-9.1.1/boris/plugins.py +0 -79
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/LICENSE.TXT +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/MANIFEST.in +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/README.TXT +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/README.md +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/__init__.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/__main__.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/about.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/add_modifier.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/add_modifier_ui.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/advanced_event_filtering.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/analysis_plugins/__init__.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/behav_coding_map_creator.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/behavior_binary_table.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/behaviors_coding_map.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/boris_cli.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/cmd_arguments.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/coding_pad.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/config.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/config_file.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/connections.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/converters.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/converters_ui.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/core_qrc.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/core_ui.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/db_functions.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/dev.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/dialog.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/duration_widget.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/edit_event.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/edit_event_ui.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/event_operations.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/events_cursor.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/events_snapshots.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/exclusion_matrix.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/export_events.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/export_observation.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/external_processes.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/geometric_measurement.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/gui_utilities.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/image_overlay.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/import_observations.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/irr.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/latency.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/map_creator.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/measurement_widget.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/media_file.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/menu_options.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/modifiers_coding_map.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/mpv-1.0.3.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/mpv.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/mpv2.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/observation.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/observation_ui.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/observations_list.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/otx_parser.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/param_panel_ui.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/player_dock_widget.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/plot_data_module.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/plot_events.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/plot_events_rt.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/plot_spectrogram_rt.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/plot_waveform_rt.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/portion/__init__.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/portion/const.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/portion/dict.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/portion/func.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/portion/interval.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/portion/io.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/preferences.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/preferences_ui.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/project.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/project_import_export.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/project_ui.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/qrc_boris.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/qrc_boris5.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/select_modifiers.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/select_observations.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/state_events.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/subjects_pad.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/synthetic_time_budget.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/time_budget_functions.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/time_budget_widget.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/transitions.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/utilities.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/video_equalizer.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/video_equalizer_ui.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/video_operations.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/view_df.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/view_df_ui.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris/write_event.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris_behav_obs.egg-info/SOURCES.txt +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris_behav_obs.egg-info/dependency_links.txt +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris_behav_obs.egg-info/entry_points.txt +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris_behav_obs.egg-info/requires.txt +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/boris_behav_obs.egg-info/top_level.txt +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/setup.cfg +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/tests/test_db_functions.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/tests/test_export_observation.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/tests/test_irr.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/tests/test_observation_gui.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/tests/test_otx_parser.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/tests/test_preferences_gui.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/tests/test_project_functions.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/tests/test_time_budget.py +0 -0
- {boris_behav_obs-9.1.1 → boris_behav_obs-9.2}/tests/test_utilities.py +0 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BORIS plugin
|
|
3
|
+
|
|
4
|
+
number of occurences of behaviors
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
__version__ = "0.3.0"
|
|
10
|
+
__version_date__ = "2025-03-17"
|
|
11
|
+
__plugin_name__ = "Number of occurences of behaviors"
|
|
12
|
+
__author__ = "Olivier Friard - University of Torino - Italy"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def run(df: pd.DataFrame):
|
|
16
|
+
"""
|
|
17
|
+
Calculate the number of occurrences of behaviors by subject.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
df_results: pd.DataFrame = df.groupby(["Subject", "Behavior"])["Behavior"].count().reset_index(name="number of occurences")
|
|
21
|
+
|
|
22
|
+
return df_results
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BORIS plugin
|
|
3
|
+
|
|
4
|
+
number of occurences of behaviors by independent_variable
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
__version__ = "0.3.0"
|
|
10
|
+
__version_date__ = "2025-03-17"
|
|
11
|
+
__plugin_name__ = "Number of occurences of behaviors by subject by independent_variable"
|
|
12
|
+
__author__ = "Olivier Friard - University of Torino - Italy"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def run(df: pd.DataFrame):
|
|
16
|
+
"""
|
|
17
|
+
Calculate the number of occurrences of behaviors by subject and by independent_variable.
|
|
18
|
+
|
|
19
|
+
This plugin returns a Pandas dataframe
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
df_results: df.DataFrame = (
|
|
23
|
+
df.groupby(
|
|
24
|
+
[
|
|
25
|
+
"independent variable 'Weather'",
|
|
26
|
+
"Subject",
|
|
27
|
+
"Behavior",
|
|
28
|
+
]
|
|
29
|
+
)["Behavior"]
|
|
30
|
+
.count()
|
|
31
|
+
.reset_index(name="number of occurences")
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return df_results
|
|
@@ -7,8 +7,8 @@ Time budget
|
|
|
7
7
|
import pandas as pd
|
|
8
8
|
import numpy as np
|
|
9
9
|
|
|
10
|
-
__version__ = "0.
|
|
11
|
-
__version_date__ = "2025-
|
|
10
|
+
__version__ = "0.3.0"
|
|
11
|
+
__version_date__ = "2025-03-17"
|
|
12
12
|
__plugin_name__ = "Time budget"
|
|
13
13
|
__author__ = "Olivier Friard - University of Torino - Italy"
|
|
14
14
|
|
|
@@ -18,7 +18,7 @@ def run(df: pd.DataFrame):
|
|
|
18
18
|
Calculate the following values:
|
|
19
19
|
|
|
20
20
|
- Total number of occurences of behavior
|
|
21
|
-
- Total duration of behavior (in seconds)
|
|
21
|
+
- Total duration of behavior (in seconds) (pandas.DataFrame.sum() ignore NaN values when computing the sum. Use min_count=1)
|
|
22
22
|
- Duration mean of behavior (in seconds)
|
|
23
23
|
- Standard deviation of behavior duration (in seconds)
|
|
24
24
|
- Inter-event intervals mean (in seconds)
|
|
@@ -26,11 +26,15 @@ def run(df: pd.DataFrame):
|
|
|
26
26
|
- % of total subject observation duration
|
|
27
27
|
"""
|
|
28
28
|
|
|
29
|
+
print("running time budget plugin")
|
|
30
|
+
|
|
31
|
+
print(df)
|
|
32
|
+
|
|
29
33
|
group_by = ["Subject", "Behavior"]
|
|
30
34
|
|
|
31
35
|
dfs = [
|
|
32
36
|
df.groupby(group_by)["Behavior"].count().reset_index(name="number of occurences"),
|
|
33
|
-
df.groupby(group_by)["Duration (s)"].sum().reset_index(name="total duration"),
|
|
37
|
+
df.groupby(group_by)["Duration (s)"].sum(min_count=1).reset_index(name="total duration"),
|
|
34
38
|
df.groupby(group_by)["Duration (s)"].mean().astype(float).round(3).reset_index(name="duration mean"),
|
|
35
39
|
df.groupby(group_by)["Duration (s)"].std().astype(float).round(3).reset_index(name="duration std dev"),
|
|
36
40
|
]
|
|
@@ -48,7 +52,7 @@ def run(df: pd.DataFrame):
|
|
|
48
52
|
interval = (df.groupby(["Subject"])["Stop (s)"].max() - df.groupby(["Subject"])["Start (s)"].min()).replace(0, np.nan)
|
|
49
53
|
|
|
50
54
|
dfs.append(
|
|
51
|
-
(100 * df.groupby(group_by)["Duration (s)"].sum() / interval)
|
|
55
|
+
(100 * df.groupby(group_by)["Duration (s)"].sum(min_count=1) / interval)
|
|
52
56
|
.astype(float)
|
|
53
57
|
.round(3)
|
|
54
58
|
.reset_index(name="% of total subject observation duration")
|
|
@@ -59,45 +63,3 @@ def run(df: pd.DataFrame):
|
|
|
59
63
|
merged_df = pd.merge(merged_df, df, on=group_by)
|
|
60
64
|
|
|
61
65
|
return merged_df
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def main(df: pd.DataFrame, observations_list: list = [], parameters: dict = {}) -> pd.DataFrame:
|
|
65
|
-
"""
|
|
66
|
-
filter by selected observations.
|
|
67
|
-
filter by selected subjects.
|
|
68
|
-
filter by selected behaviors.
|
|
69
|
-
filter by time interval.
|
|
70
|
-
"""
|
|
71
|
-
|
|
72
|
-
# filter selected observations
|
|
73
|
-
if observations_list:
|
|
74
|
-
df = df[df["Observation id"].isin(observations_list)]
|
|
75
|
-
|
|
76
|
-
if parameters:
|
|
77
|
-
# filter selected subjects
|
|
78
|
-
df = df[df["Subject"].isin(parameters["selected subjects"])]
|
|
79
|
-
|
|
80
|
-
# filter selected behaviors
|
|
81
|
-
df = df[df["Behavior"].isin(parameters["selected behaviors"])]
|
|
82
|
-
|
|
83
|
-
# filter selected time interval
|
|
84
|
-
if parameters["start time"] is not None and parameters["end time"] is not None:
|
|
85
|
-
MIN_TIME = parameters["start time"]
|
|
86
|
-
MAX_TIME = parameters["end time"]
|
|
87
|
-
|
|
88
|
-
df_interval = df[
|
|
89
|
-
(
|
|
90
|
-
((df["Start (s)"] >= MIN_TIME) & (df["Start (s)"] <= MAX_TIME))
|
|
91
|
-
| ((df["Stop (s)"] >= MIN_TIME) & (df["Stop (s)"] <= MAX_TIME))
|
|
92
|
-
)
|
|
93
|
-
| ((df["Start (s)"] < MIN_TIME) & (df["Stop (s)"] > MAX_TIME))
|
|
94
|
-
]
|
|
95
|
-
|
|
96
|
-
df_interval.loc[df["Start (s)"] < MIN_TIME, "Start (s)"] = MIN_TIME
|
|
97
|
-
df_interval.loc[df["Stop (s)"] > MAX_TIME, "Stop (s)"] = MAX_TIME
|
|
98
|
-
|
|
99
|
-
df_interval.loc[:, "Duration (s)"] = df_interval["Stop (s)"] - df_interval["Start (s)"]
|
|
100
|
-
|
|
101
|
-
df = df_interval
|
|
102
|
-
|
|
103
|
-
return run(df)
|
|
@@ -98,6 +98,7 @@ def get_cooccurence(self):
|
|
|
98
98
|
|
|
99
99
|
start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
100
100
|
|
|
101
|
+
# loop on choose subjects /behaviors until parameters are OK
|
|
101
102
|
while True:
|
|
102
103
|
flag_ok: bool = True
|
|
103
104
|
parameters = select_subj_behav.choose_obs_subj_behav_category(
|
|
@@ -113,7 +114,7 @@ def get_cooccurence(self):
|
|
|
113
114
|
show_exclude_non_coded_behaviors=True,
|
|
114
115
|
)
|
|
115
116
|
|
|
116
|
-
if parameters
|
|
117
|
+
if not parameters: # cancel button pressed
|
|
117
118
|
return
|
|
118
119
|
|
|
119
120
|
if not parameters[cfg.SELECTED_SUBJECTS]:
|
|
@@ -34,7 +34,6 @@ import json
|
|
|
34
34
|
import logging
|
|
35
35
|
import pathlib as pl
|
|
36
36
|
import platform
|
|
37
|
-
import importlib
|
|
38
37
|
import re
|
|
39
38
|
import PIL.Image
|
|
40
39
|
import PIL.ImageEnhance
|
|
@@ -52,7 +51,6 @@ from decimal import Decimal as dec
|
|
|
52
51
|
from decimal import ROUND_DOWN
|
|
53
52
|
import gzip
|
|
54
53
|
from collections import deque
|
|
55
|
-
import pandas as pd
|
|
56
54
|
import matplotlib
|
|
57
55
|
import zipfile
|
|
58
56
|
import shutil
|
|
@@ -129,7 +127,6 @@ from . import config_file
|
|
|
129
127
|
from . import select_subj_behav
|
|
130
128
|
from . import observation_operations
|
|
131
129
|
from . import write_event
|
|
132
|
-
from . import view_df
|
|
133
130
|
|
|
134
131
|
|
|
135
132
|
# matplotlib.pyplot.switch_backend("Qt5Agg")
|
|
@@ -5762,6 +5759,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5762
5759
|
self.extract_frame(self.dw_player[0])
|
|
5763
5760
|
|
|
5764
5761
|
def obs_param(self):
|
|
5762
|
+
"""
|
|
5763
|
+
allow user to select observations and then subjects and behaviors
|
|
5764
|
+
"""
|
|
5765
5765
|
_, selected_observations = select_observations.select_observations2(self, mode=cfg.MULTIPLE, windows_title="")
|
|
5766
5766
|
|
|
5767
5767
|
if not selected_observations:
|
|
@@ -5786,8 +5786,14 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5786
5786
|
|
|
5787
5787
|
start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
5788
5788
|
|
|
5789
|
+
print(f"{start_coding=}")
|
|
5790
|
+
print(f"{end_coding=}")
|
|
5791
|
+
|
|
5789
5792
|
start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
|
|
5790
5793
|
|
|
5794
|
+
print(f"{start_interval=}")
|
|
5795
|
+
print(f"{end_interval=}")
|
|
5796
|
+
|
|
5791
5797
|
parameters: dict = select_subj_behav.choose_obs_subj_behav_category(
|
|
5792
5798
|
self,
|
|
5793
5799
|
selected_observations,
|
|
@@ -5803,6 +5809,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5803
5809
|
if parameters == {}:
|
|
5804
5810
|
return [], {}
|
|
5805
5811
|
|
|
5812
|
+
print(f"{parameters=}")
|
|
5813
|
+
|
|
5806
5814
|
if not parameters[cfg.SELECTED_SUBJECTS] or not parameters[cfg.SELECTED_BEHAVIORS]:
|
|
5807
5815
|
QMessageBox.warning(None, cfg.programName, "Select subject(s) and behavior(s) to analyze")
|
|
5808
5816
|
return [], {}
|
|
@@ -5810,94 +5818,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5810
5818
|
logging.debug(f"{parameters=}")
|
|
5811
5819
|
return selected_observations, parameters
|
|
5812
5820
|
|
|
5813
|
-
def run_plugin(self):
|
|
5814
|
-
"""
|
|
5815
|
-
run plugin
|
|
5816
|
-
"""
|
|
5817
|
-
if not self.project:
|
|
5818
|
-
QMessageBox.warning(
|
|
5819
|
-
self,
|
|
5820
|
-
cfg.programName,
|
|
5821
|
-
"No observations found. Open a project first",
|
|
5822
|
-
QMessageBox.Ok | QMessageBox.Default,
|
|
5823
|
-
QMessageBox.NoButton,
|
|
5824
|
-
)
|
|
5825
|
-
return
|
|
5826
|
-
|
|
5827
|
-
logging.debug(f"{self.config_param.get(cfg.ANALYSIS_PLUGINS, {})=}")
|
|
5828
|
-
|
|
5829
|
-
plugin_name = self.sender().text()
|
|
5830
|
-
if plugin_name not in self.config_param.get(cfg.ANALYSIS_PLUGINS, {}):
|
|
5831
|
-
QMessageBox.critical(self, cfg.programName, f"Plugin '{plugin_name}' not found")
|
|
5832
|
-
return
|
|
5833
|
-
|
|
5834
|
-
plugin_path = self.config_param.get(cfg.ANALYSIS_PLUGINS, {})[plugin_name]
|
|
5835
|
-
|
|
5836
|
-
logging.debug(f"{plugin_path=}")
|
|
5837
|
-
|
|
5838
|
-
if not pl.Path(plugin_path).is_file():
|
|
5839
|
-
QMessageBox.critical(self, cfg.programName, f"The plugin {plugin_path} was not found.")
|
|
5840
|
-
return
|
|
5841
|
-
|
|
5842
|
-
logging.debug(f"run plugin from {plugin_path}")
|
|
5843
|
-
|
|
5844
|
-
module_name = pl.Path(plugin_path).stem
|
|
5845
|
-
|
|
5846
|
-
spec = importlib.util.spec_from_file_location(module_name, plugin_path)
|
|
5847
|
-
plugin_module = importlib.util.module_from_spec(spec)
|
|
5848
|
-
|
|
5849
|
-
logging.debug(f"{plugin_module=}")
|
|
5850
|
-
|
|
5851
|
-
spec.loader.exec_module(plugin_module)
|
|
5852
|
-
|
|
5853
|
-
logging.info(
|
|
5854
|
-
f"{plugin_module.__plugin_name__} loaded v.{getattr(plugin_module, '__version__')} v. {getattr(plugin_module, '__version_date__')}"
|
|
5855
|
-
)
|
|
5856
|
-
|
|
5857
|
-
selected_observations, parameters = self.obs_param()
|
|
5858
|
-
if not selected_observations:
|
|
5859
|
-
return
|
|
5860
|
-
|
|
5861
|
-
df = project_functions.project2dataframe(self.pj, selected_observations)
|
|
5862
|
-
|
|
5863
|
-
logging.debug("dataframe info")
|
|
5864
|
-
logging.debug(f"{df.info()}")
|
|
5865
|
-
logging.debug(f"{df.head()}")
|
|
5866
|
-
|
|
5867
|
-
# df_results, str_results = plugin_module.main(df, observations_list=selected_observations, parameters=parameters)
|
|
5868
|
-
|
|
5869
|
-
plugin_results = plugin_module.main(df, observations_list=selected_observations, parameters=parameters)
|
|
5870
|
-
# test if tuple: if not transform to tuple
|
|
5871
|
-
if not isinstance(plugin_results, tuple):
|
|
5872
|
-
plugin_results = tuple([plugin_results])
|
|
5873
|
-
|
|
5874
|
-
self.plugin_visu: list = []
|
|
5875
|
-
for result in plugin_results:
|
|
5876
|
-
if isinstance(result, str):
|
|
5877
|
-
self.plugin_visu.append(dialog.Results_dialog())
|
|
5878
|
-
self.plugin_visu[-1].setWindowTitle(self.sender().text())
|
|
5879
|
-
self.plugin_visu[-1].ptText.clear()
|
|
5880
|
-
self.plugin_visu[-1].ptText.appendPlainText(result)
|
|
5881
|
-
self.plugin_visu[-1].show()
|
|
5882
|
-
elif isinstance(result, pd.DataFrame):
|
|
5883
|
-
self.plugin_visu.append(
|
|
5884
|
-
view_df.View_df(self.sender().text(), f"{plugin_module.__version__} ({plugin_module.__version_date__})", result)
|
|
5885
|
-
)
|
|
5886
|
-
self.plugin_visu[-1].show()
|
|
5887
|
-
else:
|
|
5888
|
-
# result is not str nor dataframe
|
|
5889
|
-
QMessageBox.critical(
|
|
5890
|
-
None,
|
|
5891
|
-
cfg.programName,
|
|
5892
|
-
(
|
|
5893
|
-
f"Plugin returns an unknown object type: {type(result)}\n\n"
|
|
5894
|
-
"Plugins must return str and/or Pandas Dataframes.\n"
|
|
5895
|
-
"Check the plugin code."
|
|
5896
|
-
),
|
|
5897
|
-
QMessageBox.Ok | QMessageBox.Default,
|
|
5898
|
-
QMessageBox.NoButton,
|
|
5899
|
-
)
|
|
5900
|
-
|
|
5901
5821
|
|
|
5902
5822
|
def main():
|
|
5903
5823
|
# QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
|
|
@@ -43,9 +43,8 @@ from PySide6.QtWidgets import (
|
|
|
43
43
|
QSlider,
|
|
44
44
|
QMainWindow,
|
|
45
45
|
QDockWidget,
|
|
46
|
-
QPushButton,
|
|
47
46
|
)
|
|
48
|
-
from PySide6.QtCore import Qt, QDateTime, QTimer
|
|
47
|
+
from PySide6.QtCore import Qt, QDateTime, QTimer
|
|
49
48
|
from PySide6.QtGui import QFont, QIcon, QTextCursor
|
|
50
49
|
|
|
51
50
|
from PySide6 import QtTest
|
|
@@ -365,8 +364,9 @@ def time_intervals_range(observations: dict, observations_list: list) -> Tuple[O
|
|
|
365
364
|
for obs_id in observations_list:
|
|
366
365
|
observation = observations[obs_id]
|
|
367
366
|
offset = observation[cfg.TIME_OFFSET]
|
|
368
|
-
|
|
369
|
-
|
|
367
|
+
if dec(observation[cfg.OBSERVATION_TIME_INTERVAL][0]) + offset and dec(observation[cfg.OBSERVATION_TIME_INTERVAL][1]) + offset:
|
|
368
|
+
start_interval_list.append(dec(observation[cfg.OBSERVATION_TIME_INTERVAL][0]) + offset)
|
|
369
|
+
end_interval_list.append(dec(observation[cfg.OBSERVATION_TIME_INTERVAL][1]) + offset)
|
|
370
370
|
|
|
371
371
|
if not start_interval_list:
|
|
372
372
|
earliest_start_interval = None
|
|
@@ -376,7 +376,7 @@ def time_intervals_range(observations: dict, observations_list: list) -> Tuple[O
|
|
|
376
376
|
if not end_interval_list:
|
|
377
377
|
latest_end_interval = None
|
|
378
378
|
else:
|
|
379
|
-
latest_end_interval =
|
|
379
|
+
latest_end_interval = max([x for x in end_interval_list])
|
|
380
380
|
|
|
381
381
|
return earliest_start_interval, latest_end_interval
|
|
382
382
|
|
|
@@ -95,7 +95,13 @@ class Param_panel(QDialog, Ui_Dialog):
|
|
|
95
95
|
if not ((self.start_interval is None) or self.start_interval.is_nan()):
|
|
96
96
|
# Set start_time and end_time widgets values even if it is not shown with
|
|
97
97
|
# more than 1 observation as some analyses might use it (eg: advanced event filtering)
|
|
98
|
+
|
|
99
|
+
print(f"{self.end_interval=}")
|
|
100
|
+
|
|
98
101
|
end_interval = self.end_interval if self.end_interval != 0 else self.media_duration
|
|
102
|
+
|
|
103
|
+
print(f"{end_interval=}")
|
|
104
|
+
|
|
99
105
|
self.start_time.set_time(self.start_interval)
|
|
100
106
|
self.end_time.set_time(end_interval)
|
|
101
107
|
self.frm_time_interval.setEnabled(False)
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BORIS
|
|
3
|
+
Behavioral Observation Research Interactive Software
|
|
4
|
+
Copyright 2012-2025 Olivier Friard
|
|
5
|
+
|
|
6
|
+
This program is free software; you can redistribute it and/or modify
|
|
7
|
+
it under the terms of the GNU General Public License as published by
|
|
8
|
+
the Free Software Foundation; either version 2 of the License, or
|
|
9
|
+
(at your option) any later version.
|
|
10
|
+
|
|
11
|
+
This program is distributed in the hope that it will be useful,
|
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
GNU General Public License for more details.
|
|
15
|
+
|
|
16
|
+
You should have received a copy of the GNU General Public License
|
|
17
|
+
along with this program; if not, write to the Free Software
|
|
18
|
+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
19
|
+
MA 02110-1301, USA.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import importlib
|
|
23
|
+
import logging
|
|
24
|
+
import numpy as np
|
|
25
|
+
import pandas as pd
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
from PySide6.QtGui import QAction
|
|
29
|
+
from PySide6.QtWidgets import QMessageBox
|
|
30
|
+
|
|
31
|
+
from . import config as cfg
|
|
32
|
+
from . import project_functions
|
|
33
|
+
from . import dialog
|
|
34
|
+
from . import view_df
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def add_plugins_to_menu(self):
|
|
38
|
+
"""
|
|
39
|
+
add plugins to the plugins menu
|
|
40
|
+
"""
|
|
41
|
+
for plugin_name in self.config_param.get(cfg.ANALYSIS_PLUGINS, {}):
|
|
42
|
+
logging.debug(f"adding plugin '{plugin_name}' to menu")
|
|
43
|
+
# Create an action for each submenu option
|
|
44
|
+
action = QAction(self, triggered=lambda checked=False, name=plugin_name: run_plugin(self, name))
|
|
45
|
+
action.setText(plugin_name)
|
|
46
|
+
|
|
47
|
+
self.menu_plugins.addAction(action)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_plugin_name(plugin_path: str):
|
|
51
|
+
"""
|
|
52
|
+
get name of plugin
|
|
53
|
+
"""
|
|
54
|
+
# search plugin name
|
|
55
|
+
plugin_name = None
|
|
56
|
+
with open(plugin_path, "r") as f_in:
|
|
57
|
+
for line in f_in:
|
|
58
|
+
if line.startswith("__plugin_name__"):
|
|
59
|
+
plugin_name = line.split("=")[1].strip().replace('"', "")
|
|
60
|
+
break
|
|
61
|
+
return plugin_name
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def load_plugins(self):
|
|
65
|
+
"""
|
|
66
|
+
load selected plugins in analysis menu
|
|
67
|
+
"""
|
|
68
|
+
self.menu_plugins.clear()
|
|
69
|
+
self.config_param[cfg.ANALYSIS_PLUGINS] = {}
|
|
70
|
+
|
|
71
|
+
# load BORIS plugins
|
|
72
|
+
for file_ in (Path(__file__).parent / "analysis_plugins").glob("*.py"):
|
|
73
|
+
if file_.name == "__init__.py":
|
|
74
|
+
continue
|
|
75
|
+
plugin_name = get_plugin_name(file_)
|
|
76
|
+
if plugin_name is not None and plugin_name not in self.config_param.get(cfg.EXCLUDED_PLUGINS, set()):
|
|
77
|
+
self.config_param[cfg.ANALYSIS_PLUGINS][plugin_name] = str(file_)
|
|
78
|
+
|
|
79
|
+
# load personal plugins
|
|
80
|
+
if self.config_param.get(cfg.PERSONAL_PLUGINS_DIR, ""):
|
|
81
|
+
for file_ in Path(self.config_param.get(cfg.PERSONAL_PLUGINS_DIR, "")).glob("*.py"):
|
|
82
|
+
if file_.name == "__init__.py":
|
|
83
|
+
continue
|
|
84
|
+
plugin_name = get_plugin_name(file_)
|
|
85
|
+
if plugin_name is not None and plugin_name not in self.config_param.get(cfg.EXCLUDED_PLUGINS, set()):
|
|
86
|
+
self.config_param[cfg.ANALYSIS_PLUGINS][plugin_name] = str(file_)
|
|
87
|
+
|
|
88
|
+
logging.debug(f"{self.config_param.get(cfg.ANALYSIS_PLUGINS, {})=}")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def plugin_df_filter(df: pd.DataFrame, observations_list: list = [], parameters: dict = {}) -> pd.DataFrame:
|
|
92
|
+
"""
|
|
93
|
+
filter the dataframe following parameters
|
|
94
|
+
|
|
95
|
+
filter by selected observations.
|
|
96
|
+
filter by selected subjects.
|
|
97
|
+
filter by selected behaviors.
|
|
98
|
+
filter by time interval.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
# filter selected observations
|
|
102
|
+
df = df[df["Observation id"].isin(observations_list)]
|
|
103
|
+
|
|
104
|
+
if parameters:
|
|
105
|
+
# filter selected subjects
|
|
106
|
+
df = df[df["Subject"].isin(parameters["selected subjects"])]
|
|
107
|
+
|
|
108
|
+
# filter selected behaviors
|
|
109
|
+
df = df[df["Behavior"].isin(parameters["selected behaviors"])]
|
|
110
|
+
|
|
111
|
+
if parameters["time"] == "interval of observation":
|
|
112
|
+
# filter each observation with observation interval start/stop
|
|
113
|
+
|
|
114
|
+
# keep events between observation interval start time and observation interval stop/end
|
|
115
|
+
df_interval = df[
|
|
116
|
+
(
|
|
117
|
+
((df["Start (s)"] >= df["Observation interval start"]) & (df["Start (s)"] <= df["Observation interval stop"]))
|
|
118
|
+
| ((df["Stop (s)"] >= df["Observation interval start"]) & (df["Stop (s)"] <= df["Observation interval stop"]))
|
|
119
|
+
)
|
|
120
|
+
| ((df["Start (s)"] < df["Observation interval start"]) & (df["Stop (s)"] > df["Observation interval stop"]))
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
df_interval.loc[df["Start (s)"] < df["Observation interval start"], "Start (s)"] = df["Observation interval start"]
|
|
124
|
+
df_interval.loc[df["Stop (s)"] > df["Observation interval stop"], "Stop (s)"] = df["Observation interval stop"]
|
|
125
|
+
|
|
126
|
+
df_interval.loc[:, "Duration (s)"] = (df_interval["Stop (s)"] - df_interval["Start (s)"]).replace(0, np.nan)
|
|
127
|
+
|
|
128
|
+
df = df_interval
|
|
129
|
+
|
|
130
|
+
else:
|
|
131
|
+
# filter selected time interval
|
|
132
|
+
if parameters["start time"] is not None and parameters["end time"] is not None:
|
|
133
|
+
MIN_TIME = parameters["start time"]
|
|
134
|
+
MAX_TIME = parameters["end time"]
|
|
135
|
+
|
|
136
|
+
# keep events between start time and end_time
|
|
137
|
+
df_interval = df[
|
|
138
|
+
(
|
|
139
|
+
((df["Start (s)"] >= MIN_TIME) & (df["Start (s)"] <= MAX_TIME))
|
|
140
|
+
| ((df["Stop (s)"] >= MIN_TIME) & (df["Stop (s)"] <= MAX_TIME))
|
|
141
|
+
)
|
|
142
|
+
| ((df["Start (s)"] < MIN_TIME) & (df["Stop (s)"] > MAX_TIME))
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
df_interval.loc[df["Start (s)"] < MIN_TIME, "Start (s)"] = MIN_TIME
|
|
146
|
+
df_interval.loc[df["Stop (s)"] > MAX_TIME, "Stop (s)"] = MAX_TIME
|
|
147
|
+
|
|
148
|
+
df_interval.loc[:, "Duration (s)"] = (df_interval["Stop (s)"] - df_interval["Start (s)"]).replace(0, np.nan)
|
|
149
|
+
|
|
150
|
+
df = df_interval
|
|
151
|
+
|
|
152
|
+
print("filtered")
|
|
153
|
+
print("=" * 50)
|
|
154
|
+
|
|
155
|
+
print(f"{df=}")
|
|
156
|
+
|
|
157
|
+
return df
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def run_plugin(self, plugin_name):
|
|
161
|
+
"""
|
|
162
|
+
run plugin
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
if not self.project:
|
|
166
|
+
QMessageBox.warning(
|
|
167
|
+
self,
|
|
168
|
+
cfg.programName,
|
|
169
|
+
"No observations found. Open a project first",
|
|
170
|
+
QMessageBox.Ok | QMessageBox.Default,
|
|
171
|
+
QMessageBox.NoButton,
|
|
172
|
+
)
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
logging.debug(f"{self.config_param.get(cfg.ANALYSIS_PLUGINS, {})=}")
|
|
176
|
+
|
|
177
|
+
if plugin_name not in self.config_param.get(cfg.ANALYSIS_PLUGINS, {}):
|
|
178
|
+
QMessageBox.critical(self, cfg.programName, f"Plugin '{plugin_name}' not found")
|
|
179
|
+
return
|
|
180
|
+
|
|
181
|
+
plugin_path = self.config_param.get(cfg.ANALYSIS_PLUGINS, {})[plugin_name]
|
|
182
|
+
|
|
183
|
+
logging.debug(f"{plugin_path=}")
|
|
184
|
+
|
|
185
|
+
if not Path(plugin_path).is_file():
|
|
186
|
+
QMessageBox.critical(self, cfg.programName, f"The plugin {plugin_path} was not found.")
|
|
187
|
+
return
|
|
188
|
+
|
|
189
|
+
logging.debug(f"run plugin from {plugin_path}")
|
|
190
|
+
|
|
191
|
+
module_name = Path(plugin_path).stem
|
|
192
|
+
|
|
193
|
+
spec = importlib.util.spec_from_file_location(module_name, plugin_path)
|
|
194
|
+
plugin_module = importlib.util.module_from_spec(spec)
|
|
195
|
+
|
|
196
|
+
logging.debug(f"{plugin_module=}")
|
|
197
|
+
|
|
198
|
+
spec.loader.exec_module(plugin_module)
|
|
199
|
+
|
|
200
|
+
logging.info(
|
|
201
|
+
f"{plugin_module.__plugin_name__} loaded v.{getattr(plugin_module, '__version__')} v. {getattr(plugin_module, '__version_date__')}"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
selected_observations, parameters = self.obs_param()
|
|
205
|
+
if not selected_observations:
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
df = project_functions.project2dataframe(self.pj, selected_observations)
|
|
209
|
+
|
|
210
|
+
"""
|
|
211
|
+
logging.debug("dataframe info")
|
|
212
|
+
logging.debug(f"{df.info()}")
|
|
213
|
+
logging.debug(f"{df.head()}")
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
# filter the dataframe with parameters
|
|
217
|
+
filtered_df = plugin_df_filter(df, observations_list=selected_observations, parameters=parameters)
|
|
218
|
+
|
|
219
|
+
plugin_results = plugin_module.run(filtered_df)
|
|
220
|
+
# test if plugin_tests is a tuple: if not transform to tuple
|
|
221
|
+
if not isinstance(plugin_results, tuple):
|
|
222
|
+
plugin_results = tuple([plugin_results])
|
|
223
|
+
|
|
224
|
+
self.plugin_visu: list = []
|
|
225
|
+
for result in plugin_results:
|
|
226
|
+
if isinstance(result, str):
|
|
227
|
+
self.plugin_visu.append(dialog.Results_dialog())
|
|
228
|
+
self.plugin_visu[-1].setWindowTitle(plugin_name)
|
|
229
|
+
self.plugin_visu[-1].ptText.clear()
|
|
230
|
+
self.plugin_visu[-1].ptText.appendPlainText(result)
|
|
231
|
+
self.plugin_visu[-1].show()
|
|
232
|
+
elif isinstance(result, pd.DataFrame):
|
|
233
|
+
self.plugin_visu.append(view_df.View_df(plugin_name, f"{plugin_module.__version__} ({plugin_module.__version_date__})", result))
|
|
234
|
+
self.plugin_visu[-1].show()
|
|
235
|
+
else:
|
|
236
|
+
# result is not str nor dataframe
|
|
237
|
+
QMessageBox.critical(
|
|
238
|
+
None,
|
|
239
|
+
cfg.programName,
|
|
240
|
+
(
|
|
241
|
+
f"Plugin returns an unknown object type: {type(result)}\n\n"
|
|
242
|
+
"Plugins must return str and/or Pandas Dataframes.\n"
|
|
243
|
+
"Check the plugin code."
|
|
244
|
+
),
|
|
245
|
+
QMessageBox.Ok | QMessageBox.Default,
|
|
246
|
+
QMessageBox.NoButton,
|
|
247
|
+
)
|