boris-behav-obs 9.7.7__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 +26 -0
- boris/__main__.py +25 -0
- boris/about.py +143 -0
- boris/add_modifier.py +635 -0
- boris/add_modifier_ui.py +303 -0
- boris/advanced_event_filtering.py +455 -0
- 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 +1110 -0
- boris/behavior_binary_table.py +305 -0
- boris/behaviors_coding_map.py +239 -0
- boris/boris_cli.py +340 -0
- boris/cmd_arguments.py +49 -0
- boris/coding_pad.py +280 -0
- boris/config.py +785 -0
- boris/config_file.py +356 -0
- boris/connections.py +409 -0
- boris/converters.py +333 -0
- boris/converters_ui.py +225 -0
- boris/cooccurence.py +250 -0
- boris/core.py +5901 -0
- boris/core_qrc.py +15958 -0
- boris/core_ui.py +1107 -0
- boris/db_functions.py +324 -0
- boris/dev.py +134 -0
- boris/dialog.py +1108 -0
- boris/duration_widget.py +238 -0
- boris/edit_event.py +245 -0
- boris/edit_event_ui.py +233 -0
- boris/event_operations.py +1040 -0
- boris/events_cursor.py +61 -0
- boris/events_snapshots.py +596 -0
- boris/exclusion_matrix.py +141 -0
- boris/export_events.py +1006 -0
- boris/export_observation.py +1203 -0
- boris/external_processes.py +332 -0
- boris/geometric_measurement.py +941 -0
- boris/gui_utilities.py +135 -0
- boris/image_overlay.py +72 -0
- boris/import_observations.py +242 -0
- boris/ipc_mpv.py +325 -0
- boris/irr.py +634 -0
- boris/latency.py +244 -0
- boris/measurement_widget.py +161 -0
- boris/media_file.py +115 -0
- boris/menu_options.py +213 -0
- boris/modifier_coding_map_creator.py +1013 -0
- boris/modifiers_coding_map.py +157 -0
- boris/mpv.py +2016 -0
- boris/mpv2.py +2193 -0
- boris/observation.py +1453 -0
- boris/observation_operations.py +2538 -0
- boris/observation_ui.py +679 -0
- boris/observations_list.py +337 -0
- boris/otx_parser.py +442 -0
- boris/param_panel.py +201 -0
- boris/param_panel_ui.py +305 -0
- boris/player_dock_widget.py +198 -0
- boris/plot_data_module.py +536 -0
- boris/plot_events.py +634 -0
- boris/plot_events_rt.py +237 -0
- boris/plot_spectrogram_rt.py +316 -0
- boris/plot_waveform_rt.py +230 -0
- boris/plugins.py +431 -0
- boris/portion/__init__.py +31 -0
- boris/portion/const.py +95 -0
- boris/portion/dict.py +365 -0
- boris/portion/func.py +52 -0
- boris/portion/interval.py +581 -0
- boris/portion/io.py +181 -0
- boris/preferences.py +510 -0
- boris/preferences_ui.py +770 -0
- boris/project.py +2007 -0
- boris/project_functions.py +2041 -0
- boris/project_import_export.py +1096 -0
- boris/project_ui.py +794 -0
- boris/qrc_boris.py +10389 -0
- boris/qrc_boris5.py +2579 -0
- boris/select_modifiers.py +312 -0
- boris/select_observations.py +210 -0
- boris/select_subj_behav.py +286 -0
- boris/state_events.py +197 -0
- boris/subjects_pad.py +106 -0
- boris/synthetic_time_budget.py +290 -0
- boris/time_budget_functions.py +1136 -0
- boris/time_budget_widget.py +1039 -0
- boris/transitions.py +365 -0
- boris/utilities.py +1810 -0
- boris/version.py +24 -0
- boris/video_equalizer.py +159 -0
- boris/video_equalizer_ui.py +248 -0
- boris/video_operations.py +310 -0
- boris/view_df.py +104 -0
- boris/view_df_ui.py +75 -0
- boris/write_event.py +538 -0
- boris_behav_obs-9.7.7.dist-info/METADATA +139 -0
- boris_behav_obs-9.7.7.dist-info/RECORD +109 -0
- boris_behav_obs-9.7.7.dist-info/WHEEL +5 -0
- boris_behav_obs-9.7.7.dist-info/entry_points.txt +2 -0
- boris_behav_obs-9.7.7.dist-info/licenses/LICENSE.TXT +674 -0
- boris_behav_obs-9.7.7.dist-info/top_level.txt +1 -0
boris/portion/io.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from .const import Bound, inf
|
|
4
|
+
from .interval import Interval
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def from_string(
|
|
8
|
+
string,
|
|
9
|
+
conv,
|
|
10
|
+
*,
|
|
11
|
+
bound=r".+?",
|
|
12
|
+
disj=r" ?\| ?",
|
|
13
|
+
sep=r", ?",
|
|
14
|
+
left_open=r"\(",
|
|
15
|
+
left_closed=r"\[",
|
|
16
|
+
right_open=r"\)",
|
|
17
|
+
right_closed=r"\]",
|
|
18
|
+
pinf=r"\+inf",
|
|
19
|
+
ninf=r"-inf",
|
|
20
|
+
):
|
|
21
|
+
"""
|
|
22
|
+
Parse given string and create an Interval instance.
|
|
23
|
+
A converter function has to be provided to convert a bound (as string) to a value.
|
|
24
|
+
|
|
25
|
+
:param string: string to parse.
|
|
26
|
+
:param conv: function that converts a bound (as string) to an object.
|
|
27
|
+
:param bound: pattern that matches a value.
|
|
28
|
+
:param disj: pattern that matches the disjunctive operator (default matches '|').
|
|
29
|
+
:param sep: pattern that matches a bounds separator (default matches ',').
|
|
30
|
+
:param left_open: pattern that matches a left open boundary (default matches '(').
|
|
31
|
+
:param left_closed: pattern that matches a left closed boundary (default matches '[').
|
|
32
|
+
:param right_open: pattern that matches a right open boundary (default matches ')').
|
|
33
|
+
:param right_closed: pattern that matches a right closed boundary (default matches ']').
|
|
34
|
+
:param pinf: pattern that matches a positive infinity (default matches '+inf').
|
|
35
|
+
:param ninf: pattern that matches a negative infinity (default matches '-inf').
|
|
36
|
+
:return: an Interval instance.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
re_left_boundary = r"(?P<left>{}|{})".format(left_open, left_closed)
|
|
40
|
+
re_right_boundary = r"(?P<right>{}|{})".format(right_open, right_closed)
|
|
41
|
+
re_bounds = r"(?P<lower>{bound})({sep}(?P<upper>{bound}))?".format(bound=bound, sep=sep)
|
|
42
|
+
re_interval = r"{}(|{}){}".format(re_left_boundary, re_bounds, re_right_boundary)
|
|
43
|
+
re_intervals = r"{}(?P<disj>{})?".format(re_interval, disj)
|
|
44
|
+
|
|
45
|
+
intervals = []
|
|
46
|
+
has_more = True
|
|
47
|
+
|
|
48
|
+
def _convert(bound):
|
|
49
|
+
if re.match(pinf, bound):
|
|
50
|
+
return inf
|
|
51
|
+
elif re.match(ninf, bound):
|
|
52
|
+
return -inf
|
|
53
|
+
else:
|
|
54
|
+
return conv(bound)
|
|
55
|
+
|
|
56
|
+
while has_more:
|
|
57
|
+
match = re.match(re_intervals, string)
|
|
58
|
+
if match is None:
|
|
59
|
+
has_more = False
|
|
60
|
+
else:
|
|
61
|
+
group = match.groupdict()
|
|
62
|
+
|
|
63
|
+
left = Bound.CLOSED if re.match(left_closed + "$", group["left"]) else Bound.OPEN
|
|
64
|
+
right = Bound.CLOSED if re.match(right_closed + "$", group["right"]) else Bound.OPEN
|
|
65
|
+
|
|
66
|
+
lower = group.get("lower", None)
|
|
67
|
+
upper = group.get("upper", None)
|
|
68
|
+
lower = _convert(lower) if lower is not None else inf
|
|
69
|
+
upper = _convert(upper) if upper is not None else lower
|
|
70
|
+
|
|
71
|
+
intervals.append(Interval.from_atomic(left, lower, upper, right))
|
|
72
|
+
string = string[match.end() :]
|
|
73
|
+
|
|
74
|
+
return Interval(*intervals)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def to_string(
|
|
78
|
+
interval, conv=repr, *, disj=" | ", sep=",", left_open="(", left_closed="[", right_open=")", right_closed="]", pinf="+inf", ninf="-inf"
|
|
79
|
+
):
|
|
80
|
+
"""
|
|
81
|
+
Export given interval to string.
|
|
82
|
+
|
|
83
|
+
:param interval: an interval.
|
|
84
|
+
:param conv: function that is used to represent a bound (default is `repr`).
|
|
85
|
+
:param disj: string representing disjunctive operator (default is ' | ').
|
|
86
|
+
:param sep: string representing bound separator (default is ',').
|
|
87
|
+
:param left_open: string representing left open boundary (default is '(').
|
|
88
|
+
:param left_closed: string representing left closed boundary (default is '[').
|
|
89
|
+
:param right_open: string representing right open boundary (default is ')').
|
|
90
|
+
:param right_closed: string representing right closed boundary (default is ']').
|
|
91
|
+
:param pinf: string representing a positive infinity (default is '+inf').
|
|
92
|
+
:param ninf: string representing a negative infinity (default is '-inf').
|
|
93
|
+
:return: a string representation for given interval.
|
|
94
|
+
"""
|
|
95
|
+
if interval.empty:
|
|
96
|
+
return "{}{}".format(left_open, right_open)
|
|
97
|
+
|
|
98
|
+
def _convert(bound):
|
|
99
|
+
if bound == inf:
|
|
100
|
+
return pinf
|
|
101
|
+
elif bound == -inf:
|
|
102
|
+
return ninf
|
|
103
|
+
else:
|
|
104
|
+
return conv(bound)
|
|
105
|
+
|
|
106
|
+
exported_intervals = []
|
|
107
|
+
for item in interval:
|
|
108
|
+
left = left_open if item.left == Bound.OPEN else left_closed
|
|
109
|
+
right = right_open if item.right == Bound.OPEN else right_closed
|
|
110
|
+
|
|
111
|
+
lower = _convert(item.lower)
|
|
112
|
+
upper = _convert(item.upper)
|
|
113
|
+
|
|
114
|
+
if item.lower == item.upper:
|
|
115
|
+
exported_intervals.append("{}{}{}".format(left, lower, right))
|
|
116
|
+
else:
|
|
117
|
+
exported_intervals.append("{}{}{}{}{}".format(left, lower, sep, upper, right))
|
|
118
|
+
|
|
119
|
+
return disj.join(exported_intervals)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def from_data(data, conv=None, *, pinf=float("inf"), ninf=float("-inf")):
|
|
123
|
+
"""
|
|
124
|
+
Import an interval from a piece of data.
|
|
125
|
+
|
|
126
|
+
:param data: a list of 4-uples (left, lower, upper, right).
|
|
127
|
+
:param conv: function that converts "lower" and "upper" to bounds, default to identity.
|
|
128
|
+
:param pinf: value used to represent positive infinity.
|
|
129
|
+
:param ninf: value used to represent negative infinity.
|
|
130
|
+
:return: an Interval instance.
|
|
131
|
+
"""
|
|
132
|
+
intervals = []
|
|
133
|
+
conv = (lambda v: v) if conv is None else conv
|
|
134
|
+
|
|
135
|
+
def _convert(bound):
|
|
136
|
+
if bound == pinf:
|
|
137
|
+
return inf
|
|
138
|
+
elif bound == ninf:
|
|
139
|
+
return -inf
|
|
140
|
+
else:
|
|
141
|
+
return conv(bound)
|
|
142
|
+
|
|
143
|
+
for item in data:
|
|
144
|
+
left, lower, upper, right = item
|
|
145
|
+
intervals.append(
|
|
146
|
+
Interval.from_atomic(
|
|
147
|
+
Bound(left),
|
|
148
|
+
_convert(lower),
|
|
149
|
+
_convert(upper),
|
|
150
|
+
Bound(right),
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
return Interval(*intervals)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def to_data(interval, conv=None, *, pinf=float("inf"), ninf=float("-inf")):
|
|
157
|
+
"""
|
|
158
|
+
Export given interval to a list of 4-uples (left, lower,
|
|
159
|
+
upper, right).
|
|
160
|
+
|
|
161
|
+
:param interval: an interval.
|
|
162
|
+
:param conv: function that convert bounds to "lower" and "upper", default to identity.
|
|
163
|
+
:param pinf: value used to encode positive infinity.
|
|
164
|
+
:param ninf: value used to encode negative infinity.
|
|
165
|
+
:return: a list of 4-uples (left, lower, upper, right)
|
|
166
|
+
"""
|
|
167
|
+
conv = (lambda v: v) if conv is None else conv
|
|
168
|
+
|
|
169
|
+
data = []
|
|
170
|
+
|
|
171
|
+
def _convert(bound):
|
|
172
|
+
if bound == inf:
|
|
173
|
+
return pinf
|
|
174
|
+
elif bound == -inf:
|
|
175
|
+
return ninf
|
|
176
|
+
else:
|
|
177
|
+
return conv(bound)
|
|
178
|
+
|
|
179
|
+
for item in interval:
|
|
180
|
+
data.append((item.left.value, _convert(item.lower), _convert(item.upper), item.right.value))
|
|
181
|
+
return data
|
boris/preferences.py
ADDED
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BORIS
|
|
3
|
+
Behavioral Observation Research Interactive Software
|
|
4
|
+
Copyright 2012-2025 Olivier Friard
|
|
5
|
+
|
|
6
|
+
This file is part of BORIS.
|
|
7
|
+
|
|
8
|
+
BORIS is free software; you can redistribute it and/or modify
|
|
9
|
+
it under the terms of the GNU General Public License as published by
|
|
10
|
+
the Free Software Foundation; either version 3 of the License, or
|
|
11
|
+
any later version.
|
|
12
|
+
|
|
13
|
+
BORIS is distributed in the hope that it will be useful,
|
|
14
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
GNU General Public License for more details.
|
|
17
|
+
|
|
18
|
+
You should have received a copy of the GNU General Public License
|
|
19
|
+
along with this program; if not see <http://www.gnu.org/licenses/>.
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import logging
|
|
24
|
+
import os
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
import sys
|
|
27
|
+
from . import dialog
|
|
28
|
+
from . import gui_utilities
|
|
29
|
+
from . import menu_options
|
|
30
|
+
from . import config as cfg
|
|
31
|
+
from . import config_file
|
|
32
|
+
from . import plugins
|
|
33
|
+
|
|
34
|
+
from .preferences_ui import Ui_prefDialog
|
|
35
|
+
|
|
36
|
+
from PySide6.QtWidgets import QDialog, QFileDialog, QListWidgetItem, QMessageBox
|
|
37
|
+
from PySide6.QtCore import Qt
|
|
38
|
+
from PySide6.QtGui import QFont
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Preferences(QDialog, Ui_prefDialog):
|
|
42
|
+
def __init__(self, parent=None):
|
|
43
|
+
super().__init__()
|
|
44
|
+
self.setupUi(self)
|
|
45
|
+
|
|
46
|
+
# plugins
|
|
47
|
+
self.pb_browse_plugins_dir.clicked.connect(self.browse_plugins_dir)
|
|
48
|
+
|
|
49
|
+
self.pbBrowseFFmpegCacheDir.clicked.connect(self.browseFFmpegCacheDir)
|
|
50
|
+
|
|
51
|
+
self.pb_reset_behav_colors.clicked.connect(self.reset_behav_colors)
|
|
52
|
+
self.pb_reset_category_colors.clicked.connect(self.reset_category_colors)
|
|
53
|
+
|
|
54
|
+
self.pb_refresh.clicked.connect(self.refresh_preferences)
|
|
55
|
+
self.pbOK.clicked.connect(self.accept)
|
|
56
|
+
self.pbCancel.clicked.connect(self.reject)
|
|
57
|
+
|
|
58
|
+
self.flag_refresh = False
|
|
59
|
+
|
|
60
|
+
# Create a monospace QFont
|
|
61
|
+
monospace_font = QFont("Courier New") # or "Monospace", "Consolas", "Liberation Mono", etc.
|
|
62
|
+
monospace_font.setStyleHint(QFont.Monospace)
|
|
63
|
+
monospace_font.setPointSize(12)
|
|
64
|
+
self.pte_plugin_code.setFont(monospace_font)
|
|
65
|
+
|
|
66
|
+
def browse_plugins_dir(self):
|
|
67
|
+
"""
|
|
68
|
+
get the personal plugins directory
|
|
69
|
+
"""
|
|
70
|
+
directory = QFileDialog.getExistingDirectory(None, "Select the plugins directory", self.le_personal_plugins_dir.text())
|
|
71
|
+
if not directory:
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
self.le_personal_plugins_dir.setText(directory)
|
|
75
|
+
self.lw_personal_plugins.clear()
|
|
76
|
+
for file_ in Path(directory).glob("*.py"):
|
|
77
|
+
if file_.name.startswith("_"):
|
|
78
|
+
continue
|
|
79
|
+
plugin_name = plugins.get_plugin_name(file_)
|
|
80
|
+
if plugin_name is None:
|
|
81
|
+
continue
|
|
82
|
+
# check if personal plugin name is in BORIS plugins (case sensitive)
|
|
83
|
+
if plugin_name in [self.lv_all_plugins.item(i).text() for i in range(self.lv_all_plugins.count())]:
|
|
84
|
+
continue
|
|
85
|
+
item = QListWidgetItem(plugin_name)
|
|
86
|
+
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
|
|
87
|
+
item.setCheckState(Qt.Checked)
|
|
88
|
+
item.setData(100, str(file_))
|
|
89
|
+
self.lw_personal_plugins.addItem(item)
|
|
90
|
+
|
|
91
|
+
if self.lw_personal_plugins.count() == 0:
|
|
92
|
+
QMessageBox.warning(self, cfg.programName, f"No plugin found in {directory}")
|
|
93
|
+
|
|
94
|
+
def refresh_preferences(self):
|
|
95
|
+
"""
|
|
96
|
+
allow user to delete the config file (.boris)
|
|
97
|
+
"""
|
|
98
|
+
if (
|
|
99
|
+
dialog.MessageDialog(
|
|
100
|
+
"BORIS",
|
|
101
|
+
("Refresh will re-initialize all your preferences and close BORIS"),
|
|
102
|
+
[cfg.CANCEL, "Refresh preferences"],
|
|
103
|
+
)
|
|
104
|
+
== "Refresh preferences"
|
|
105
|
+
):
|
|
106
|
+
self.flag_refresh = True
|
|
107
|
+
self.accept()
|
|
108
|
+
|
|
109
|
+
def browseFFmpegCacheDir(self):
|
|
110
|
+
"""
|
|
111
|
+
allow user select a cache dir for ffmpeg images
|
|
112
|
+
"""
|
|
113
|
+
FFmpegCacheDir = QFileDialog.getExistingDirectory(
|
|
114
|
+
self,
|
|
115
|
+
"Select a directory",
|
|
116
|
+
os.path.expanduser("~"),
|
|
117
|
+
options=QFileDialog.ShowDirsOnly,
|
|
118
|
+
)
|
|
119
|
+
if FFmpegCacheDir:
|
|
120
|
+
self.leFFmpegCacheDir.setText(FFmpegCacheDir)
|
|
121
|
+
|
|
122
|
+
def reset_behav_colors(self):
|
|
123
|
+
"""
|
|
124
|
+
reset behavior colors to default
|
|
125
|
+
"""
|
|
126
|
+
self.te_behav_colors.setPlainText("\n".join(cfg.BEHAVIORS_PLOT_COLORS))
|
|
127
|
+
|
|
128
|
+
logging.debug("reset behaviors colors to default")
|
|
129
|
+
|
|
130
|
+
def reset_category_colors(self):
|
|
131
|
+
"""
|
|
132
|
+
reset category colors to default
|
|
133
|
+
"""
|
|
134
|
+
self.te_category_colors.setPlainText("\n".join(cfg.CATEGORY_COLORS_LIST))
|
|
135
|
+
|
|
136
|
+
logging.debug("reset category colors to default")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def preferences(self):
|
|
140
|
+
"""
|
|
141
|
+
show preferences window
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
def show_plugin_info(item):
|
|
145
|
+
"""
|
|
146
|
+
display information about the clicked plugin
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
if item.text() not in self.config_param[cfg.ANALYSIS_PLUGINS]:
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
plugin_path = item.data(100)
|
|
153
|
+
|
|
154
|
+
# Python plugins
|
|
155
|
+
if Path(plugin_path).suffix == ".py":
|
|
156
|
+
import importlib
|
|
157
|
+
|
|
158
|
+
module_name = Path(plugin_path).stem
|
|
159
|
+
spec = importlib.util.spec_from_file_location(module_name, plugin_path)
|
|
160
|
+
plugin_module = importlib.util.module_from_spec(spec)
|
|
161
|
+
spec.loader.exec_module(plugin_module)
|
|
162
|
+
attributes_list = dir(plugin_module)
|
|
163
|
+
|
|
164
|
+
out: list = []
|
|
165
|
+
out.append((plugin_module.__plugin_name__ + "\n") if "__plugin_name__" in attributes_list else "No plugin name provided")
|
|
166
|
+
out.append(plugin_module.__author__ if "__author__" in attributes_list else "No author provided")
|
|
167
|
+
version_str: str = ""
|
|
168
|
+
if "__version__" in attributes_list:
|
|
169
|
+
version_str += str(plugin_module.__version__)
|
|
170
|
+
if "__version_date__" in attributes_list:
|
|
171
|
+
version_str += " " if version_str else ""
|
|
172
|
+
version_str += f"({plugin_module.__version_date__})"
|
|
173
|
+
|
|
174
|
+
out.append(f"Version: {version_str}\n" if version_str else "No version provided")
|
|
175
|
+
|
|
176
|
+
# out.append(plugin_module.run.__doc__.strip())
|
|
177
|
+
# description
|
|
178
|
+
if "__description__" in attributes_list:
|
|
179
|
+
out.append("Description:\n")
|
|
180
|
+
out.append(plugin_module.__description__ if "__description__" in attributes_list else "No description provided")
|
|
181
|
+
|
|
182
|
+
preferencesWindow.pte_plugin_description.setPlainText("\n".join(out))
|
|
183
|
+
|
|
184
|
+
# R plugins
|
|
185
|
+
if Path(plugin_path).suffix == ".R":
|
|
186
|
+
plugin_description = plugins.get_r_plugin_description(plugin_path)
|
|
187
|
+
if plugin_description is not None:
|
|
188
|
+
preferencesWindow.pte_plugin_description.setPlainText("\n".join(plugin_description.split("\\n")))
|
|
189
|
+
else:
|
|
190
|
+
preferencesWindow.pte_plugin_description.setPlainText("No description provided")
|
|
191
|
+
|
|
192
|
+
# display plugin code
|
|
193
|
+
try:
|
|
194
|
+
with open(plugin_path, "r") as f_in:
|
|
195
|
+
plugin_code = f_in.read()
|
|
196
|
+
except Exception:
|
|
197
|
+
plugin_code = "Not available"
|
|
198
|
+
|
|
199
|
+
preferencesWindow.pte_plugin_code.setPlainText(plugin_code)
|
|
200
|
+
|
|
201
|
+
preferencesWindow = Preferences()
|
|
202
|
+
preferencesWindow.tabWidget.setCurrentIndex(0)
|
|
203
|
+
|
|
204
|
+
if self.timeFormat == cfg.S:
|
|
205
|
+
preferencesWindow.cbTimeFormat.setCurrentIndex(0)
|
|
206
|
+
|
|
207
|
+
if self.timeFormat == cfg.HHMMSS:
|
|
208
|
+
preferencesWindow.cbTimeFormat.setCurrentIndex(1)
|
|
209
|
+
|
|
210
|
+
preferencesWindow.sbffSpeed.setValue(self.fast)
|
|
211
|
+
preferencesWindow.cb_adapt_fast_jump.setChecked(self.config_param.get(cfg.ADAPT_FAST_JUMP, False))
|
|
212
|
+
preferencesWindow.sbRepositionTimeOffset.setValue(self.repositioningTimeOffset)
|
|
213
|
+
preferencesWindow.sbSpeedStep.setValue(self.play_rate_step)
|
|
214
|
+
# automatic backup
|
|
215
|
+
preferencesWindow.sbAutomaticBackup.setValue(self.automaticBackup)
|
|
216
|
+
# separator for behavioural strings
|
|
217
|
+
preferencesWindow.leSeparator.setText(self.behav_seq_separator)
|
|
218
|
+
# close same event indep of modifiers
|
|
219
|
+
preferencesWindow.cbCloseSameEvent.setChecked(self.close_the_same_current_event)
|
|
220
|
+
# confirm sound
|
|
221
|
+
preferencesWindow.cbConfirmSound.setChecked(self.confirmSound)
|
|
222
|
+
# beep every
|
|
223
|
+
preferencesWindow.sbBeepEvery.setValue(self.beep_every)
|
|
224
|
+
# frame step size
|
|
225
|
+
# preferencesWindow.sb_frame_step_size.setValue(self.config_param.get(cfg.FRAME_STEP_SIZE, cfg.FRAME_STEP_SIZE_DEFAULT_VALUE))
|
|
226
|
+
|
|
227
|
+
# alert no focal subject
|
|
228
|
+
preferencesWindow.cbAlertNoFocalSubject.setChecked(self.alertNoFocalSubject)
|
|
229
|
+
# tracking cursor above event
|
|
230
|
+
preferencesWindow.cbTrackingCursorAboveEvent.setChecked(self.trackingCursorAboveEvent)
|
|
231
|
+
# check for new version
|
|
232
|
+
preferencesWindow.cbCheckForNewVersion.setChecked(self.checkForNewVersion)
|
|
233
|
+
# display subtitles
|
|
234
|
+
preferencesWindow.cb_display_subtitles.setChecked(self.config_param.get(cfg.DISPLAY_SUBTITLES, False))
|
|
235
|
+
# pause before add event
|
|
236
|
+
preferencesWindow.cb_pause_before_addevent.setChecked(self.pause_before_addevent)
|
|
237
|
+
# MPV hwdec
|
|
238
|
+
preferencesWindow.cb_hwdec.clear()
|
|
239
|
+
preferencesWindow.cb_hwdec.addItems(cfg.MPV_HWDEC_OPTIONS)
|
|
240
|
+
try:
|
|
241
|
+
preferencesWindow.cb_hwdec.setCurrentIndex(
|
|
242
|
+
cfg.MPV_HWDEC_OPTIONS.index(self.config_param.get(cfg.MPV_HWDEC, cfg.MPV_HWDEC_DEFAULT_VALUE))
|
|
243
|
+
)
|
|
244
|
+
except Exception:
|
|
245
|
+
preferencesWindow.cb_hwdec.setCurrentIndex(cfg.MPV_HWDEC_OPTIONS.index(cfg.MPV_HWDEC_DEFAULT_VALUE))
|
|
246
|
+
# check integrity
|
|
247
|
+
preferencesWindow.cb_check_integrity_at_opening.setChecked(self.config_param.get(cfg.CHECK_PROJECT_INTEGRITY, True))
|
|
248
|
+
|
|
249
|
+
# BORIS plugins
|
|
250
|
+
preferencesWindow.lv_all_plugins.itemClicked.connect(show_plugin_info)
|
|
251
|
+
|
|
252
|
+
preferencesWindow.lv_all_plugins.clear()
|
|
253
|
+
|
|
254
|
+
for file_ in (Path(__file__).parent / "analysis_plugins").glob("*.py"):
|
|
255
|
+
if file_.name.startswith("_"):
|
|
256
|
+
continue
|
|
257
|
+
plugin_name = plugins.get_plugin_name(file_)
|
|
258
|
+
if plugin_name is not None:
|
|
259
|
+
item = QListWidgetItem(plugin_name)
|
|
260
|
+
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
|
|
261
|
+
if plugin_name in self.config_param.get(cfg.EXCLUDED_PLUGINS, set()):
|
|
262
|
+
item.setCheckState(Qt.Unchecked)
|
|
263
|
+
else:
|
|
264
|
+
item.setCheckState(Qt.Checked)
|
|
265
|
+
item.setData(100, str(file_))
|
|
266
|
+
preferencesWindow.lv_all_plugins.addItem(item)
|
|
267
|
+
|
|
268
|
+
# personal plugins
|
|
269
|
+
preferencesWindow.le_personal_plugins_dir.setText(self.config_param.get(cfg.PERSONAL_PLUGINS_DIR, ""))
|
|
270
|
+
preferencesWindow.lw_personal_plugins.itemClicked.connect(show_plugin_info)
|
|
271
|
+
|
|
272
|
+
preferencesWindow.lw_personal_plugins.clear()
|
|
273
|
+
if self.config_param.get(cfg.PERSONAL_PLUGINS_DIR, ""):
|
|
274
|
+
# Python plugins
|
|
275
|
+
for file_ in Path(self.config_param[cfg.PERSONAL_PLUGINS_DIR]).glob("*.py"):
|
|
276
|
+
if file_.name.startswith("_"):
|
|
277
|
+
continue
|
|
278
|
+
plugin_name = plugins.get_plugin_name(file_)
|
|
279
|
+
if plugin_name is None:
|
|
280
|
+
continue
|
|
281
|
+
# check if personal plugin name is in BORIS plugins (case sensitive)
|
|
282
|
+
if plugin_name in [preferencesWindow.lv_all_plugins.item(i).text() for i in range(preferencesWindow.lv_all_plugins.count())]:
|
|
283
|
+
continue
|
|
284
|
+
item = QListWidgetItem(plugin_name)
|
|
285
|
+
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
|
|
286
|
+
if plugin_name in self.config_param.get(cfg.EXCLUDED_PLUGINS, set()):
|
|
287
|
+
item.setCheckState(Qt.Unchecked)
|
|
288
|
+
else:
|
|
289
|
+
item.setCheckState(Qt.Checked)
|
|
290
|
+
item.setData(100, str(file_))
|
|
291
|
+
preferencesWindow.lw_personal_plugins.addItem(item)
|
|
292
|
+
|
|
293
|
+
# R plugins
|
|
294
|
+
for file_ in Path(self.config_param[cfg.PERSONAL_PLUGINS_DIR]).glob("*.R"):
|
|
295
|
+
plugin_name = plugins.get_r_plugin_name(file_)
|
|
296
|
+
if plugin_name is None:
|
|
297
|
+
continue
|
|
298
|
+
# check if personal plugin name is in BORIS plugins (case sensitive)
|
|
299
|
+
if plugin_name in [preferencesWindow.lv_all_plugins.item(i).text() for i in range(preferencesWindow.lv_all_plugins.count())]:
|
|
300
|
+
continue
|
|
301
|
+
item = QListWidgetItem(plugin_name)
|
|
302
|
+
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
|
|
303
|
+
if plugin_name in self.config_param.get(cfg.EXCLUDED_PLUGINS, set()):
|
|
304
|
+
item.setCheckState(Qt.Unchecked)
|
|
305
|
+
else:
|
|
306
|
+
item.setCheckState(Qt.Checked)
|
|
307
|
+
item.setData(100, str(file_))
|
|
308
|
+
preferencesWindow.lw_personal_plugins.addItem(item)
|
|
309
|
+
|
|
310
|
+
# PROJET FILE INDENTATION
|
|
311
|
+
preferencesWindow.combo_project_file_indentation.clear()
|
|
312
|
+
preferencesWindow.combo_project_file_indentation.addItems(cfg.PROJECT_FILE_INDENTATION_COMBO_OPTIONS)
|
|
313
|
+
try:
|
|
314
|
+
preferencesWindow.combo_project_file_indentation.setCurrentIndex(
|
|
315
|
+
cfg.PROJECT_FILE_INDENTATION_OPTIONS.index(
|
|
316
|
+
self.config_param.get(
|
|
317
|
+
cfg.PROJECT_FILE_INDENTATION,
|
|
318
|
+
cfg.PROJECT_FILE_INDENTATION_DEFAULT_VALUE,
|
|
319
|
+
)
|
|
320
|
+
)
|
|
321
|
+
)
|
|
322
|
+
except Exception:
|
|
323
|
+
preferencesWindow.combo_project_file_indentation.setCurrentText(
|
|
324
|
+
cfg.PROJECT_FILE_INDENTATION_COMBO_OPTIONS[
|
|
325
|
+
cfg.PROJECT_FILE_INDENTATION_OPTIONS.index(cfg.PROJECT_FILE_INDENTATION_DEFAULT_VALUE)
|
|
326
|
+
]
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
# FFmpeg for frame by frame mode
|
|
330
|
+
preferencesWindow.lbFFmpegPath.setText(f"FFmpeg path: {self.ffmpeg_bin}")
|
|
331
|
+
preferencesWindow.leFFmpegCacheDir.setText(self.ffmpeg_cache_dir)
|
|
332
|
+
|
|
333
|
+
# spectrogram
|
|
334
|
+
preferencesWindow.cbSpectrogramColorMap.clear()
|
|
335
|
+
preferencesWindow.cbSpectrogramColorMap.addItems(cfg.SPECTROGRAM_COLOR_MAPS)
|
|
336
|
+
try:
|
|
337
|
+
preferencesWindow.cbSpectrogramColorMap.setCurrentIndex(cfg.SPECTROGRAM_COLOR_MAPS.index(self.spectrogram_color_map))
|
|
338
|
+
except Exception:
|
|
339
|
+
preferencesWindow.cbSpectrogramColorMap.setCurrentIndex(cfg.SPECTROGRAM_COLOR_MAPS.index(cfg.SPECTROGRAM_DEFAULT_COLOR_MAP))
|
|
340
|
+
# time interval
|
|
341
|
+
try:
|
|
342
|
+
preferencesWindow.sb_time_interval.setValue(self.spectrogram_time_interval)
|
|
343
|
+
except Exception:
|
|
344
|
+
preferencesWindow.sb_time_interval.setValue(cfg.SPECTROGRAM_DEFAULT_TIME_INTERVAL)
|
|
345
|
+
# window type
|
|
346
|
+
preferencesWindow.cb_window_type.setCurrentText(self.config_param.get(cfg.SPECTROGRAM_WINDOW_TYPE, cfg.SPECTROGRAM_DEFAULT_WINDOW_TYPE))
|
|
347
|
+
# NFFT
|
|
348
|
+
preferencesWindow.cb_NFFT.setCurrentText(self.config_param.get(cfg.SPECTROGRAM_NFFT, cfg.SPECTROGRAM_DEFAULT_NFFT))
|
|
349
|
+
# noverlap
|
|
350
|
+
preferencesWindow.sb_noverlap.setValue(self.config_param.get(cfg.SPECTROGRAM_NOVERLAP, cfg.SPECTROGRAM_DEFAULT_NOVERLAP))
|
|
351
|
+
# vmin
|
|
352
|
+
preferencesWindow.sb_vmin.setValue(self.config_param.get(cfg.SPECTROGRAM_VMIN, cfg.SPECTROGRAM_DEFAULT_VMIN))
|
|
353
|
+
# vmax
|
|
354
|
+
preferencesWindow.sb_vmax.setValue(self.config_param.get(cfg.SPECTROGRAM_VMAX, cfg.SPECTROGRAM_DEFAULT_VMAX))
|
|
355
|
+
|
|
356
|
+
# behavior colors
|
|
357
|
+
if not self.plot_colors:
|
|
358
|
+
self.plot_colors = cfg.BEHAVIORS_PLOT_COLORS
|
|
359
|
+
preferencesWindow.te_behav_colors.setPlainText("\n".join(self.plot_colors))
|
|
360
|
+
|
|
361
|
+
# category colors
|
|
362
|
+
if not self.behav_category_colors:
|
|
363
|
+
self.behav_category_colors = cfg.CATEGORY_COLORS_LIST
|
|
364
|
+
preferencesWindow.te_category_colors.setPlainText("\n".join(self.behav_category_colors))
|
|
365
|
+
|
|
366
|
+
# interface
|
|
367
|
+
preferencesWindow.sb_toolbar_icon_size.setValue(self.config_param.get(cfg.TOOLBAR_ICON_SIZE, cfg.DEFAULT_TOOLBAR_ICON_SIZE_VALUE))
|
|
368
|
+
|
|
369
|
+
gui_utilities.restore_geometry(preferencesWindow, "preferences", (700, 500))
|
|
370
|
+
|
|
371
|
+
while True:
|
|
372
|
+
if preferencesWindow.exec():
|
|
373
|
+
if preferencesWindow.sb_vmin.value() >= preferencesWindow.sb_vmax.value():
|
|
374
|
+
QMessageBox.warning(self, cfg.programName, "Spectrogram parameters: the vmin value must be lower than the vmax value.")
|
|
375
|
+
continue
|
|
376
|
+
|
|
377
|
+
if preferencesWindow.sb_noverlap.value() >= int(preferencesWindow.cb_NFFT.currentText()):
|
|
378
|
+
QMessageBox.warning(self, cfg.programName, "Spectrogram parameters: the noverlap value must be lower than the NFFT value.")
|
|
379
|
+
continue
|
|
380
|
+
|
|
381
|
+
gui_utilities.save_geometry(preferencesWindow, "preferences")
|
|
382
|
+
|
|
383
|
+
if preferencesWindow.flag_refresh:
|
|
384
|
+
# refresh preferences remove the config file
|
|
385
|
+
|
|
386
|
+
logging.debug("flag refresh ")
|
|
387
|
+
|
|
388
|
+
self.config_param["refresh_preferences"] = True
|
|
389
|
+
self.close()
|
|
390
|
+
# check if refresh canceled for not saved project
|
|
391
|
+
if "refresh_preferences" in self.config_param:
|
|
392
|
+
if (Path.home() / ".boris").exists():
|
|
393
|
+
os.remove(Path.home() / ".boris")
|
|
394
|
+
sys.exit()
|
|
395
|
+
|
|
396
|
+
if preferencesWindow.cbTimeFormat.currentIndex() == 0:
|
|
397
|
+
self.timeFormat = cfg.S
|
|
398
|
+
|
|
399
|
+
if preferencesWindow.cbTimeFormat.currentIndex() == 1:
|
|
400
|
+
self.timeFormat = cfg.HHMMSS
|
|
401
|
+
|
|
402
|
+
self.fast = preferencesWindow.sbffSpeed.value()
|
|
403
|
+
|
|
404
|
+
self.config_param[cfg.ADAPT_FAST_JUMP] = preferencesWindow.cb_adapt_fast_jump.isChecked()
|
|
405
|
+
|
|
406
|
+
self.repositioningTimeOffset = preferencesWindow.sbRepositionTimeOffset.value()
|
|
407
|
+
|
|
408
|
+
self.play_rate_step = preferencesWindow.sbSpeedStep.value()
|
|
409
|
+
|
|
410
|
+
self.automaticBackup = preferencesWindow.sbAutomaticBackup.value()
|
|
411
|
+
if self.automaticBackup:
|
|
412
|
+
self.automaticBackupTimer.start(self.automaticBackup * 60000)
|
|
413
|
+
else:
|
|
414
|
+
self.automaticBackupTimer.stop()
|
|
415
|
+
|
|
416
|
+
self.behav_seq_separator = preferencesWindow.leSeparator.text()
|
|
417
|
+
|
|
418
|
+
self.close_the_same_current_event = preferencesWindow.cbCloseSameEvent.isChecked()
|
|
419
|
+
|
|
420
|
+
self.confirmSound = preferencesWindow.cbConfirmSound.isChecked()
|
|
421
|
+
|
|
422
|
+
self.beep_every = preferencesWindow.sbBeepEvery.value()
|
|
423
|
+
|
|
424
|
+
# frame step size
|
|
425
|
+
# self.config_param[cfg.FRAME_STEP_SIZE] = preferencesWindow.sb_frame_step_size.value()
|
|
426
|
+
|
|
427
|
+
self.alertNoFocalSubject = preferencesWindow.cbAlertNoFocalSubject.isChecked()
|
|
428
|
+
|
|
429
|
+
self.trackingCursorAboveEvent = preferencesWindow.cbTrackingCursorAboveEvent.isChecked()
|
|
430
|
+
|
|
431
|
+
self.checkForNewVersion = preferencesWindow.cbCheckForNewVersion.isChecked()
|
|
432
|
+
|
|
433
|
+
self.config_param[cfg.DISPLAY_SUBTITLES] = preferencesWindow.cb_display_subtitles.isChecked()
|
|
434
|
+
|
|
435
|
+
self.pause_before_addevent = preferencesWindow.cb_pause_before_addevent.isChecked()
|
|
436
|
+
|
|
437
|
+
# MPV hwdec
|
|
438
|
+
self.config_param[cfg.MPV_HWDEC] = cfg.MPV_HWDEC_OPTIONS[preferencesWindow.cb_hwdec.currentIndex()]
|
|
439
|
+
|
|
440
|
+
# check project integrity
|
|
441
|
+
self.config_param[cfg.CHECK_PROJECT_INTEGRITY] = preferencesWindow.cb_check_integrity_at_opening.isChecked()
|
|
442
|
+
|
|
443
|
+
# update BORIS analysis plugins
|
|
444
|
+
self.config_param[cfg.ANALYSIS_PLUGINS] = {}
|
|
445
|
+
self.config_param[cfg.EXCLUDED_PLUGINS] = set()
|
|
446
|
+
for i in range(preferencesWindow.lv_all_plugins.count()):
|
|
447
|
+
if preferencesWindow.lv_all_plugins.item(i).checkState() == Qt.Checked:
|
|
448
|
+
self.config_param[cfg.ANALYSIS_PLUGINS][preferencesWindow.lv_all_plugins.item(i).text()] = (
|
|
449
|
+
preferencesWindow.lv_all_plugins.item(i).data(100)
|
|
450
|
+
)
|
|
451
|
+
else:
|
|
452
|
+
self.config_param[cfg.EXCLUDED_PLUGINS].add(preferencesWindow.lv_all_plugins.item(i).text())
|
|
453
|
+
|
|
454
|
+
# update personal plugins
|
|
455
|
+
self.config_param[cfg.PERSONAL_PLUGINS_DIR] = preferencesWindow.le_personal_plugins_dir.text()
|
|
456
|
+
for i in range(preferencesWindow.lw_personal_plugins.count()):
|
|
457
|
+
if preferencesWindow.lw_personal_plugins.item(i).checkState() == Qt.Checked:
|
|
458
|
+
self.config_param[cfg.ANALYSIS_PLUGINS][preferencesWindow.lw_personal_plugins.item(i).text()] = (
|
|
459
|
+
preferencesWindow.lw_personal_plugins.item(i).data(100)
|
|
460
|
+
)
|
|
461
|
+
else:
|
|
462
|
+
self.config_param[cfg.EXCLUDED_PLUGINS].add(preferencesWindow.lw_personal_plugins.item(i).text())
|
|
463
|
+
|
|
464
|
+
plugins.load_plugins(self)
|
|
465
|
+
plugins.add_plugins_to_menu(self)
|
|
466
|
+
|
|
467
|
+
# project file indentation
|
|
468
|
+
self.config_param[cfg.PROJECT_FILE_INDENTATION] = cfg.PROJECT_FILE_INDENTATION_OPTIONS[
|
|
469
|
+
preferencesWindow.combo_project_file_indentation.currentIndex()
|
|
470
|
+
]
|
|
471
|
+
|
|
472
|
+
if self.observationId:
|
|
473
|
+
self.load_tw_events(self.observationId)
|
|
474
|
+
self.display_statusbar_info(self.observationId)
|
|
475
|
+
|
|
476
|
+
self.ffmpeg_cache_dir = preferencesWindow.leFFmpegCacheDir.text()
|
|
477
|
+
|
|
478
|
+
# spectrogram
|
|
479
|
+
self.spectrogram_color_map = preferencesWindow.cbSpectrogramColorMap.currentText()
|
|
480
|
+
self.spectrogram_time_interval = preferencesWindow.sb_time_interval.value()
|
|
481
|
+
# window type
|
|
482
|
+
self.config_param[cfg.SPECTROGRAM_WINDOW_TYPE] = preferencesWindow.cb_window_type.currentText()
|
|
483
|
+
# NFFT
|
|
484
|
+
self.config_param[cfg.SPECTROGRAM_NFFT] = preferencesWindow.cb_NFFT.currentText()
|
|
485
|
+
# noverlap
|
|
486
|
+
self.config_param[cfg.SPECTROGRAM_NOVERLAP] = preferencesWindow.sb_noverlap.value()
|
|
487
|
+
# vmin
|
|
488
|
+
self.config_param[cfg.SPECTROGRAM_VMIN] = preferencesWindow.sb_vmin.value()
|
|
489
|
+
# vmax
|
|
490
|
+
self.config_param[cfg.SPECTROGRAM_VMAX] = preferencesWindow.sb_vmax.value()
|
|
491
|
+
|
|
492
|
+
# behav colors
|
|
493
|
+
self.plot_colors = preferencesWindow.te_behav_colors.toPlainText().split()
|
|
494
|
+
# category colors
|
|
495
|
+
self.behav_category_colors = preferencesWindow.te_category_colors.toPlainText().split()
|
|
496
|
+
|
|
497
|
+
# interface
|
|
498
|
+
self.config_param[cfg.TOOLBAR_ICON_SIZE] = preferencesWindow.sb_toolbar_icon_size.value()
|
|
499
|
+
|
|
500
|
+
menu_options.update_menu(self)
|
|
501
|
+
|
|
502
|
+
config_file.save(self)
|
|
503
|
+
|
|
504
|
+
break
|
|
505
|
+
|
|
506
|
+
else:
|
|
507
|
+
break
|
|
508
|
+
|
|
509
|
+
# activate main window
|
|
510
|
+
self.activateWindow()
|