From 2109e87529ce8857fc943de66954684d456492a2 Mon Sep 17 00:00:00 2001 From: chrishalcrow Date: Thu, 24 Jul 2025 12:21:13 +0100 Subject: [PATCH 1/3] Allow long zone --- spikeinterface_gui/backend_qt.py | 22 +++++++++++----------- spikeinterface_gui/layout_presets.py | 6 +++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/spikeinterface_gui/backend_qt.py b/spikeinterface_gui/backend_qt.py index f3e10cc..714c9e9 100644 --- a/spikeinterface_gui/backend_qt.py +++ b/spikeinterface_gui/backend_qt.py @@ -222,25 +222,25 @@ def create_main_layout(self): self.splitDockWidget(self.docks[first_left], dock, orientations['vertical']) ## Handle right - first_left = None - for zone in ['zone3', 'zone7', 'zone4', 'zone8']: + first_top = None + for zone in ['zone3', 'zone4', 'zone7', 'zone8']: if len(widgets_zone[zone]) == 0: continue view_name = widgets_zone[zone][0] dock = self.docks[view_name] - if len(widgets_zone[zone]) > 0 and first_left is None: + if len(widgets_zone[zone]) > 0 and first_top is None: self.addDockWidget(areas['right'], dock) - first_left = view_name - elif zone == 'zone7': - self.splitDockWidget(self.docks[first_left], dock, orientations['vertical']) + first_top = view_name elif zone == 'zone4': - self.splitDockWidget(self.docks[first_left], dock, orientations['horizontal']) + self.splitDockWidget(self.docks[first_top], dock, orientations['horizontal']) + elif zone == 'zone7': + self.splitDockWidget(self.docks[first_top], dock, orientations['vertical']) elif zone == 'zone8': - if len(widgets_zone['zone7']) > 0: - z = widgets_zone['zone7'][0] - self.splitDockWidget(self.docks[z], dock, orientations['horizontal']) + if len(widgets_zone['zone4']) > 0: + z = widgets_zone['zone4'][0] + self.splitDockWidget(self.docks[z], dock, orientations['vertical']) else: - self.splitDockWidget(self.docks[first_left], dock, orientations['vertical']) + self.splitDockWidget(self.docks[first_top], dock, orientations['horizontal']) # make tabs for zone, view_names in widgets_zone.items(): diff --git a/spikeinterface_gui/layout_presets.py b/spikeinterface_gui/layout_presets.py index 2dee02f..1f25d3a 100644 --- a/spikeinterface_gui/layout_presets.py +++ b/spikeinterface_gui/layout_presets.py @@ -2,9 +2,9 @@ A preset need 8 zones like this: +-----------------+-----------------+ -| [zone1 zone2] | [zone3 zone4] | -+-----------------+-----------------+ -| [zone5 zone6] | [zone7 zone8] | +| [zone1 zone2] | [zone3 | [zone4 | ++-----------------+ | + +| [zone5 zone6] | zone7] | zone8] | +-----------------+-----------------+ """ From ac3d923af00927c392f9d1c347657a54d121d4a2 Mon Sep 17 00:00:00 2001 From: chrishalcrow Date: Mon, 11 Aug 2025 09:06:10 +0100 Subject: [PATCH 2/3] add zone layout to web --- spikeinterface_gui/backend_panel.py | 107 +++++++++++++--------------- 1 file changed, 48 insertions(+), 59 deletions(-) diff --git a/spikeinterface_gui/backend_panel.py b/spikeinterface_gui/backend_panel.py index ac3152e..2d47a3f 100644 --- a/spikeinterface_gui/backend_panel.py +++ b/spikeinterface_gui/backend_panel.py @@ -277,71 +277,60 @@ def create_main_layout(self): )) # Create GridStack layout with resizable regions - grid_per_zone = 2 gs = pn.GridStack( sizing_mode='stretch_both', allow_resize=False, allow_drag=False, ) - # Top modifications - for zone in ['zone1', 'zone2', 'zone3', 'zone4']: - view = layout_zone[zone] - row_slice = slice(0, 2 * grid_per_zone) # First two rows - - if zone == 'zone1': - if layout_zone.get('zone2') is None or len(layout_zone['zone2']) == 0: - col_slice = slice(0, 2 * grid_per_zone) # Full width when merged - else: - col_slice = slice(0, grid_per_zone) # Half width when not merged - elif zone == 'zone2': - if layout_zone.get('zone1') is None or len(layout_zone['zone1']) == 0: - col_slice = slice(0, 2 * grid_per_zone) # Full width when merged - else: - col_slice = slice(grid_per_zone, 2 * grid_per_zone) # Right half - elif zone == 'zone3': - if layout_zone.get('zone4') is None or len(layout_zone['zone4']) == 0: - col_slice = slice(2 * grid_per_zone, 4 * grid_per_zone) # Full width when merged - else: - col_slice = slice(2 * grid_per_zone, 3 * grid_per_zone) # Left half - elif zone == 'zone4': - if layout_zone.get('zone3') is None or len(layout_zone['zone3']) == 0: - col_slice = slice(2 * grid_per_zone, 4 * grid_per_zone) # Full width when merged - else: - col_slice = slice(3 * grid_per_zone, 4 * grid_per_zone) # Right half - - if view is not None and len(view) > 0: - # Note: order of slices swapped to [row, col] - gs[row_slice, col_slice] = view - - # Bottom - for zone in ['zone5', 'zone6', 'zone7', 'zone8']: - view = layout_zone[zone] - row_slice = slice(2 * grid_per_zone, 4 * grid_per_zone) - - if zone == 'zone5': - if layout_zone.get('zone6') is None or len(layout_zone['zone6']) == 0: - col_slice = slice(0, 2 * grid_per_zone) - else: - col_slice = slice(0, grid_per_zone) - elif zone == 'zone6': - if layout_zone.get('zone5') is None or len(layout_zone['zone5']) == 0: - col_slice = slice(0, 2 * grid_per_zone) - else: - col_slice = slice(grid_per_zone, 2 * grid_per_zone) - elif zone == 'zone7': - if layout_zone.get('zone8') is None or len(layout_zone['zone8']) == 0: - col_slice = slice(2 * grid_per_zone, 4 * grid_per_zone) - else: - col_slice = slice(2 * grid_per_zone, 3 * grid_per_zone) - elif zone == 'zone8': - if layout_zone.get('zone7') is None or len(layout_zone['zone7']) == 0: - col_slice = slice(2 * grid_per_zone, 4 * grid_per_zone) - else: - col_slice = slice(3 * grid_per_zone, 4 * grid_per_zone) - - if view is not None and len(view) > 0: - gs[row_slice, col_slice] = view + all_zones = [f'zone{a}' for a in range(1,9)] + is_zone = [(layout_zone.get(zone) is not None) and (len(layout_zone.get(zone)) > 0) for zone in all_zones] + + # Get number of columns and rows per sub-region + num_cols_12 = max(is_zone[0]*1 + is_zone[1]*1, is_zone[4]*1 + is_zone[5]*1) + num_cols_56 = num_cols_12 + num_cols_37 = (is_zone[2] or is_zone[6])*1 + + num_rows_12 = ((is_zone[0] or is_zone[1])*1 - (is_zone[4] or is_zone[5])*1) + 1 + + # Do sub-regions [1,2], [4,5] [3][7] and [5][8] separately. For each, find out if + # both zones are present. If they are, place both in sub-region. If not, place one. + + row_slice_12 = slice(0,num_rows_12) + if (is_zone[0] and is_zone[1]): + gs[row_slice_12, slice(0,1)] = layout_zone.get('zone1') + gs[row_slice_12, slice(1,2)] = layout_zone.get('zone2') + elif (is_zone[0] and not is_zone[1]): + gs[row_slice_12, slice(0,num_cols_12)] = layout_zone.get('zone1') + elif (is_zone[1] and not is_zone[0]): + gs[row_slice_12, slice(0,num_cols_12)] = layout_zone.get('zone2') + + row_slice_56 = slice(num_rows_12, 2) + if (is_zone[4] and is_zone[5]): + gs[row_slice_56, slice(0,1)] = layout_zone.get('zone5') + gs[row_slice_56, slice(1,2)] = layout_zone.get('zone6') + elif (is_zone[4] and not is_zone[5]): + gs[row_slice_56, slice(0,num_cols_56)] = layout_zone.get('zone5') + elif (is_zone[5] and not is_zone[4]): + gs[row_slice_56, slice(0,num_cols_56)] = layout_zone.get('zone6') + + col_slice_37 = slice(num_cols_12,num_cols_12+1) + if is_zone[2] and is_zone[6]: + gs[slice(0, 1), col_slice_37] = layout_zone.get('zone3') + gs[slice(1, 2), col_slice_37] = layout_zone.get('zone7') + elif is_zone[2] and not is_zone[6]: + gs[slice(0, 2), col_slice_37] = layout_zone.get('zone3') + elif is_zone[6] and not is_zone[2]: + gs[slice(0, 2), col_slice_37] = layout_zone.get('zone7') + + col_slice_48 = slice(num_cols_12+num_cols_37,num_cols_12+num_cols_37+1) + if is_zone[3] and is_zone[7]: + gs[slice(0, 1), col_slice_48] = layout_zone.get('zone4') + gs[slice(1, 2), col_slice_48] = layout_zone.get('zone8') + elif is_zone[3] and not is_zone[7]: + gs[slice(0, 2), col_slice_48] = layout_zone.get('zone4') + elif is_zone[7] and not is_zone[3]: + gs[slice(0, 2), col_slice_48] = layout_zone.get('zone8') self.main_layout = gs From 3bfad5a6e002dc603c283e98116a3b7507ce4802 Mon Sep 17 00:00:00 2001 From: chrishalcrow Date: Mon, 11 Aug 2025 09:08:15 +0100 Subject: [PATCH 3/3] Add customization to README --- README.md | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3683413..1bb41a7 100644 --- a/README.md +++ b/README.md @@ -204,7 +204,55 @@ cd spikeinterface-gui pip install . ``` -## Cedits +## Custom layout + +You can create your own custom layout by specifying which views you'd like +to see, and where they go. The basic window layout supports eight "zones", +which are laid out as follows: + +``` ++-----------------+-----------------+ +| [zone1 zone2] | [zone3 | [zone4 | ++-----------------+ | + +| [zone5 zone6] | zone7] | zone8] | ++-----------------+-----------------+ +``` + +If zones are not included, the other zones take over their space. Hence if you'd +like to show waveforms as a long view, you can set zone3 to display waveforms +and then set zone7 to display nothing. The waveforms in zone3 will take over the +blank space from zone7. + +To specify your own layout, put the specification in a `.json` file. This should +be a list of zones, and which views should appear in which zones. An example: + + +**my_layout.json** +``` +{ + "zone1": ["unitlist", "spikelist"], + "zone2": ["spikeamplitude"], + "zone3": ["waveform", "waveformheatmap"], + "zone4": ["similarity"], + "zone5": ["spikedepth"], + "zone6": [], + "zone7": [], + "zone8": ["correlogram"] +} +``` + +When you open spikeinterface-gui, you can then point to the `my_layout.json` +using the `--layout_file` flag: + +``` +sigui --layout_file=path/to/my_layout.json path/to/sorting_analyzer +``` + +Find a list of available views [in this file](https://github.com/SpikeInterface/spikeinterface-gui/blob/main/spikeinterface_gui/viewlist.py). + + + +## Credits Original author : Samuel Garcia, CNRS, Lyon, France