Skip to content

Commit 4d64476

Browse files
authored
Merge pull request #187 from chrishalcrow/add-user-settings
Add `user_setting` functionality
2 parents 0647e98 + f3a3e04 commit 4d64476

File tree

9 files changed

+227
-19
lines changed

9 files changed

+227
-19
lines changed

README.md

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ sigui --mode=web --root-folder path-to-my-analyzers
222222
![launcher_web](docs/source/images/launcher_web.png)
223223

224224

225-
## Customizing the layout
225+
## Customizing layout and settings
226226

227227
You can create your own custom layout by specifying which views you'd like
228228
to see, and where they go. The basic window layout supports eight "zones",
@@ -248,7 +248,7 @@ E.g. suppose your layout is only non-empty in zones 1, 4, 5, 6 and 7:
248248
+---------------+--------------+
249249
```
250250

251-
Then zone1 will stretch right-wards to make a three-zone view. Zone4 will stretch
251+
Then zone1 will stretch right-wards to make a two-zone view. Zone4 will stretch
252252
downwards to make a long two-zone view.
253253

254254
To specify your own layout, put the specification in a `.json` file. This should
@@ -278,6 +278,41 @@ sigui --layout_file=path/to/my_layout.json path/to/sorting_analyzer
278278

279279
Find a list of available views [in this file](https://github.com/SpikeInterface/spikeinterface-gui/blob/main/spikeinterface_gui/viewlist.py).
280280

281+
You can also edit the initial settings for each view. There are two ways to do this.
282+
First, you can open the gui, edit the settings of any or all views, then press the
283+
"Save as default settings" button in the `mainsettings` view. This will save a
284+
`settings.json` file in your spikeinterface-gui config (saved in `~/.config/spikeinterface_gui/`).
285+
The saved settings will automatically load next time you start the gui.
286+
287+
If you want more direct control, you can pass your own setting json file
288+
to the `settings-file` flag (or by passing a `user_settings` dict to `run_mainwindow`).
289+
The settings file should contain a settings dict for each view. The view names are the
290+
same as used in the layout file, and each setting name can be seen in the settings
291+
tab of each view. Below is an example settings file:
292+
293+
**my_settings.json**
294+
295+
``` json
296+
{
297+
"probe": {
298+
"show_channel_id": true
299+
},
300+
"waveform": {
301+
"overlap": false,
302+
"plot_std": false
303+
},
304+
"spikeamplitude": {
305+
"max_spikes_per_unit": 1000,
306+
"alpha": 0.2
307+
}
308+
}
309+
```
310+
311+
You can then use this file by running e.g.
312+
313+
```bash
314+
sigui --settings_file=path/to/my_settings.json path/to/sorting_analyzer
315+
```
281316

282317
## Deploy the GUI in web mode
283318

docs/source/custom_layout.rst renamed to docs/source/custom_layout_settings.rst

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
Customizing the layout
2-
======================
1+
Customizing layout and settings
2+
===============================
33

44
You can create your own custom layout by specifying which views you'd like
55
to see, and where they go. The basic window layout supports eight "zones",
@@ -26,7 +26,7 @@ E.g. suppose your layout is only non-empty in zones 1, 4, 5, 6 and 7:
2626
+---------------+--------------+
2727
2828
29-
Then zone1 will stretch right-wards to make a three-zone view. Zone4 will stretch
29+
Then zone1 will stretch right-wards to make a two-zone view. Zone4 will stretch
3030
downwards to make a long two-zone view.
3131

3232
To specify your own layout, put the specification in a ``.json`` file. This should
@@ -55,3 +55,40 @@ using the ``--layout_file`` flag:
5555
sigui --layout_file=path/to/my_layout.json path/to/sorting_analyzer
5656
5757
Find a list of available views `in this file <https://github.com/SpikeInterface/spikeinterface-gui/blob/main/spikeinterface_gui/viewlist.py>`_.
58+
59+
You can also edit the initial settings for each view. There are two ways to do this.
60+
First, you can open the gui, edit the settings of any or all views, then press the
61+
"Save as default settings" button in the ``mainsettings`` view. This will save a
62+
``settings.json`` file in your spikeinterface-gui config (saved in ``~/.config/spikeinterface_gui/``).
63+
The saved settings will automatically load next time you start the gui.
64+
65+
If you want more direct control, you can pass your own setting json file
66+
to the ``settings-file`` flag (or by passing a ``user_settings`` dict to ``run_mainwindow``).
67+
The settings file should contain a settings dict for each view. The view names are the
68+
same as used in the layout file, and each setting name can be seen in the settings
69+
tab of each view. Below is an example settings file:
70+
71+
**my_settings.json**
72+
73+
.. code-block:: json
74+
75+
{
76+
"probe": {
77+
"show_channel_id": true
78+
},
79+
"waveform": {
80+
"overlap": false,
81+
"plot_std": false
82+
},
83+
"spikeamplitude": {
84+
"max_spikes_per_unit": 1000,
85+
"alpha": 0.2
86+
}
87+
}
88+
89+
90+
You can then use this file by running e.g.
91+
92+
.. code-block:: bash
93+
94+
sigui --settings_file=path/to/my_settings.json path/to/sorting_analyzer

docs/source/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ This is a web app internally using Panel, useful when the data is remote
5050
installation
5151
launch
5252
usage
53-
custom_layout
53+
custom_layout_settings
5454
deployments
5555
views
5656
developers

spikeinterface_gui/backend_panel.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,13 +185,13 @@ def listen_setting_changes(view):
185185

186186
class PanelMainWindow:
187187

188-
def __init__(self, controller, layout_preset=None, layout=None):
188+
def __init__(self, controller, layout_preset=None, layout=None, user_settings=None):
189189
self.controller = controller
190190
self.layout_preset = layout_preset
191191
self.layout = layout
192192
self.verbose = controller.verbose
193193

194-
self.make_views()
194+
self.make_views(user_settings)
195195
self.create_main_layout()
196196

197197
# refresh all views wihtout notiying
@@ -202,7 +202,7 @@ def __init__(self, controller, layout_preset=None, layout=None):
202202
if view.is_view_visible():
203203
view.refresh()
204204

205-
def make_views(self):
205+
def make_views(self, user_settings):
206206
self.views = {}
207207
# this contains view layout + settings + compute
208208
self.view_layouts = {}
@@ -227,6 +227,13 @@ def make_views(self):
227227
sizing_mode="stretch_both"
228228
)
229229

230+
if user_settings is not None and user_settings.get(view_name) is not None:
231+
for setting_name, user_setting in user_settings.get(view_name).items():
232+
if setting_name not in view.settings.keys():
233+
raise KeyError(f"Setting {setting_name} is not a valid setting for View {view_name}. Check your settings file.")
234+
with param.parameterized.discard_events(view.settings._parameterized):
235+
view.settings[setting_name] = user_setting
236+
230237
tabs = [("📊", view.layout)]
231238
if view_class._settings is not None:
232239
settings = pn.Param(view.settings._parameterized, sizing_mode="stretch_height",

spikeinterface_gui/backend_qt.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,18 +134,22 @@ def create_settings(view, parent):
134134
def listen_setting_changes(view):
135135
view.settings.sigTreeStateChanged.connect(view.on_settings_changed)
136136

137+
def stop_listen_setting_changes(view):
138+
view.settings.sigTreeStateChanged.disconnect(view.on_settings_changed)
139+
137140

138141
class QtMainWindow(QT.QMainWindow):
139142
main_window_closed = QT.pyqtSignal(object)
140143

141-
def __init__(self, controller, parent=None, layout_preset=None, layout=None):
144+
def __init__(self, controller, parent=None, layout_preset=None, layout=None, user_settings=None):
142145
QT.QMainWindow.__init__(self, parent)
143146

144147
self.controller = controller
145148
self.verbose = controller.verbose
146149
self.layout_preset = layout_preset
147150
self.layout = layout
148-
self.make_views()
151+
152+
self.make_views(user_settings)
149153
self.create_main_layout()
150154

151155
# refresh all views wihtout notiying
@@ -159,7 +163,7 @@ def __init__(self, controller, parent=None, layout_preset=None, layout=None):
159163
# for view_name, dock in self.docks.items():
160164
# dock.visibilityChanged.connect(self.views[view_name].refresh)
161165

162-
def make_views(self):
166+
def make_views(self, user_settings):
163167
self.views = {}
164168
self.docks = {}
165169
for view_name, view_class in possible_class_views.items():
@@ -176,6 +180,16 @@ def make_views(self):
176180

177181
widget = ViewWidget(view_class)
178182
view = view_class(controller=self.controller, parent=widget, backend='qt')
183+
184+
if user_settings is not None and user_settings.get(view_name) is not None:
185+
for setting_name, user_setting in user_settings.get(view_name).items():
186+
if setting_name not in view.settings.keys().keys():
187+
raise KeyError(f"Setting {setting_name} is not a valid setting for View {view_name}. Check your settings file.")
188+
stop_listen_setting_changes(view)
189+
view.settings[setting_name] = user_setting
190+
listen_setting_changes(view)
191+
192+
179193
widget.set_view(view)
180194
dock = QT.QDockWidget(view_name)
181195
dock.setWidget(widget)

spikeinterface_gui/controller.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,12 @@ class Controller():
3434
def __init__(self, analyzer=None, backend="qt", parent=None, verbose=False, save_on_compute=False,
3535
curation=False, curation_data=None, label_definitions=None, with_traces=True,
3636
displayed_unit_properties=None,
37-
extra_unit_properties=None, skip_extensions=None):
37+
extra_unit_properties=None, skip_extensions=None, disable_save_settings_button=False):
3838
self.views = []
3939
skip_extensions = skip_extensions if skip_extensions is not None else []
4040
self.skip_extensions = skip_extensions
4141
self.backend = backend
42+
self.disable_save_settings_button = disable_save_settings_button
4243
if self.backend == "qt":
4344
from .backend_qt import SignalHandler
4445
self.signal_handler = SignalHandler(self, parent=parent)

spikeinterface_gui/main.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77

88
from spikeinterface import load_sorting_analyzer, load
99
from spikeinterface.core.core_tools import is_path_remote
10+
from .utils_global import get_config_folder
1011

12+
import spikeinterface_gui
1113
from spikeinterface_gui.controller import Controller
1214

1315

@@ -30,6 +32,8 @@ def run_mainwindow(
3032
panel_start_server_kwargs=None,
3133
panel_window_servable=True,
3234
verbose=False,
35+
user_settings=None,
36+
disable_save_settings_button=False,
3337
):
3438
"""
3539
Create the main window and start the QT app loop.
@@ -82,6 +86,10 @@ def run_mainwindow(
8286
the `panel_window_servable` should be set to False.
8387
verbose: bool, default: False
8488
If True, print some information in the console
89+
user_settings: dict, default: None
90+
A dictionary of user settings for each view, which overwrite the default settings.
91+
disable_save_settings_button: bool, default: False
92+
If True, disables the "save default settings" button, so that user cannot do this.
8593
"""
8694

8795
if mode == "desktop":
@@ -91,6 +99,21 @@ def run_mainwindow(
9199
else:
92100
raise ValueError(f"spikeinterface-gui wrong mode {mode}")
93101

102+
# Order of preference for settings is set here:
103+
# 1) User specified settings
104+
# 2) Settings in the config folder
105+
# 3) Default settings of each view
106+
if user_settings is None:
107+
sigui_version = spikeinterface_gui.__version__
108+
config_version_folder = get_config_folder() / sigui_version
109+
settings_file = config_version_folder / "settings.json"
110+
if settings_file.is_file():
111+
try:
112+
with open(settings_file) as f:
113+
user_settings = json.load(f)
114+
except json.JSONDecodeError as e:
115+
print(f"Config file at {settings_file} is not decodable. Error: {e}")
116+
print("Using default settings.")
94117

95118
if recording is not None:
96119
analyzer.set_temporary_recording(recording)
@@ -106,6 +129,7 @@ def run_mainwindow(
106129
displayed_unit_properties=displayed_unit_properties,
107130
extra_unit_properties=extra_unit_properties,
108131
skip_extensions=skip_extensions,
132+
disable_save_settings_button=disable_save_settings_button
109133
)
110134
if verbose:
111135
t1 = time.perf_counter()
@@ -122,7 +146,7 @@ def run_mainwindow(
122146

123147
app = mkQApp()
124148

125-
win = QtMainWindow(controller, layout_preset=layout_preset, layout=layout)
149+
win = QtMainWindow(controller, layout_preset=layout_preset, layout=layout, user_settings=user_settings)
126150
win.setWindowTitle('SpikeInterface GUI')
127151
# Set window icon
128152
icon_file = Path(__file__).absolute().parent / 'img' / 'si.png'
@@ -134,7 +158,7 @@ def run_mainwindow(
134158

135159
elif backend == "panel":
136160
from .backend_panel import PanelMainWindow, start_server
137-
win = PanelMainWindow(controller, layout_preset=layout_preset, layout=layout)
161+
win = PanelMainWindow(controller, layout_preset=layout_preset, layout=layout, user_settings=user_settings)
138162

139163
if start_app or panel_window_servable:
140164
win.main_layout.servable(title='SpikeInterface GUI')
@@ -261,6 +285,8 @@ def run_mainwindow_cli():
261285
parser.add_argument('--address', help='Address for web mode', default='localhost')
262286
parser.add_argument('--layout-file', help='Path to json file defining layout', default=None)
263287
parser.add_argument('--curation-file', help='Path to json file defining a curation', default=None)
288+
parser.add_argument('--settings-file', help='Path to json file specifying the settings of each view', default=None)
289+
parser.add_argument('--disable_save_settings_button', help='Disables button allowing for user to save default settings', action='store_true', default=False)
264290

265291
args = parser.parse_args(argv)
266292

@@ -302,6 +328,14 @@ def run_mainwindow_cli():
302328
else:
303329
curation_data = None
304330

331+
if args.settings_file is not None:
332+
with open(args.settings_file, "r") as f:
333+
user_settings = json.load(f)
334+
else:
335+
user_settings = None
336+
337+
disable_save_settings_button = args.disable_save_settings_button
338+
305339
run_mainwindow(
306340
analyzer,
307341
mode=args.mode,
@@ -311,5 +345,7 @@ def run_mainwindow_cli():
311345
verbose=args.verbose,
312346
layout=args.layout_file,
313347
curation_dict=curation_data,
348+
user_settings=user_settings,
349+
disable_save_settings_button=disable_save_settings_button,
314350
)
315351

0 commit comments

Comments
 (0)