Skip to content

Commit 217ea10

Browse files
committed
implement qt splitting
1 parent 5796b94 commit 217ea10

File tree

4 files changed

+240
-85
lines changed

4 files changed

+240
-85
lines changed

spikeinterface_gui/backend_panel.py

Lines changed: 1 addition & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from copy import copy
55

66
from .viewlist import possible_class_views
7-
from .layout_presets import get_layout_description
7+
from .layout_presets import get_layout_description, get_size_bottom_row, get_size_top_row
88

99
# Used by views to emit/trigger signals
1010
class SignalNotifier(param.Parameterized):
@@ -183,49 +183,6 @@ def listen_setting_changes(view):
183183
for setting_data in view._settings:
184184
view.settings._parameterized.param.watch(view.on_settings_changed, setting_data["name"])
185185

186-
def get_size_top_row(initial_row, initial_col, is_zone_array, original_zone_array):
187-
188-
if original_zone_array[initial_row][initial_col] == False:
189-
return 0,0
190-
191-
num_rows = is_zone_array[initial_row][initial_col]*1
192-
num_cols = num_rows
193-
194-
num_rows += (not is_zone_array[1][initial_col])*1
195-
196-
if num_rows == 1:
197-
for zone in is_zone_array[0,1+initial_col:]:
198-
if zone == True:
199-
break
200-
num_cols += 1
201-
elif num_rows == 2:
202-
for zone1, zone2 in np.transpose(is_zone_array[:,1+initial_col:]):
203-
if zone1 == True or zone2 == True:
204-
break
205-
num_cols += 1
206-
207-
is_zone_array[initial_row:initial_row+num_rows,initial_col:initial_col+num_cols] = True
208-
209-
return num_rows, num_cols
210-
211-
def get_size_bottom_row(initial_row, initial_col, is_zone_array, original_zone_array):
212-
213-
if original_zone_array[initial_row][initial_col] == False:
214-
return 0,0
215-
216-
num_rows = is_zone_array[initial_row][initial_col]*1
217-
if num_rows == 0:
218-
return 0, 0
219-
num_cols = num_rows
220-
221-
for zone in is_zone_array[1,1+initial_col:]:
222-
if zone == True:
223-
break
224-
else:
225-
num_cols += 1
226-
227-
return num_rows, num_cols
228-
229186
class PanelMainWindow:
230187

231188
def __init__(self, controller, layout_preset=None, layout=None):

spikeinterface_gui/backend_qt.py

Lines changed: 194 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@
66
import weakref
77

88
from .viewlist import possible_class_views
9-
from .layout_presets import get_layout_description
9+
from .layout_presets import get_layout_description, get_size_bottom_row, get_size_top_row
1010

1111
from .utils_qt import qt_style, add_stretch_to_qtoolbar
1212

13-
1413
# Used by views to emit/trigger signals
1514
class SignalNotifier(QT.QObject):
1615
spike_selection_changed = QT.pyqtSignal()
@@ -137,7 +136,7 @@ def listen_setting_changes(view):
137136
class QtMainWindow(QT.QMainWindow):
138137
main_window_closed = QT.pyqtSignal(object)
139138

140-
def __init__(self, controller, parent=None, layout_preset=None, layout=None):
139+
def __init__(self, controller, parent=None, layout_preset=None, layout=None, screenshot_name=""):
141140
QT.QMainWindow.__init__(self, parent)
142141

143142
self.controller = controller
@@ -154,6 +153,15 @@ def __init__(self, controller, parent=None, layout_preset=None, layout=None):
154153
view._refresh()
155154
self.controller.signal_handler.activate()
156155

156+
self.screenshot_name = screenshot_name
157+
#QT.QTimer.singleShot(200, self.saveScreenshot)
158+
159+
def saveScreenshot(self):
160+
161+
screen = QT.QApplication.primaryScreen()
162+
screenshot = screen.grabWindow( self.winId() )
163+
screenshot.save(self.screenshot_name, 'jpg')
164+
157165
# TODO sam : all veiws are always refreshed at the moment so this is useless.
158166
# uncommen this when ViewBase.is_view_visible() work correctly
159167
# for view_name, dock in self.docks.items():
@@ -186,6 +194,42 @@ def make_views(self):
186194

187195

188196
def create_main_layout(self):
197+
"""
198+
We rely on PyQt docks so that the user can adjust the size of each widget. These
199+
are made iteratively by splitting an existing dock. The algorithm to create the
200+
splits based on our layout dict is in three steps. It is complicated, so we provide
201+
an example. Let's "*" represent a zone with something in it and "o" a zone without.
202+
Consider the example
203+
204+
1 2 3 4
205+
* * o *
206+
* o * *
207+
208+
First, we determine which columns should be two rows long. Then split the docks
209+
around these. In this case, only column 2 should be long. So the split is
210+
211+
1 2 3 4
212+
* | * | o *
213+
* | o | * *
214+
215+
Next, for each sub-region, we split vertically. The second column cannot be split.
216+
217+
1 2 3 4
218+
* | * | o *
219+
- - -
220+
* | o | * *
221+
222+
Finally, in each sub-sub-region, we check how many times we need to split horizontally.
223+
Here the lower 3/4 row can be split
224+
225+
1 2 3 4
226+
* | * | o *
227+
- - -
228+
* | o | * | *
229+
230+
Each final region contains exactly one zone. All of our possible layouts can be split up
231+
in this way.
232+
"""
189233
import warnings
190234

191235
warnings.filterwarnings("ignore", category=RuntimeWarning, module="pyqtgraph")
@@ -200,47 +244,157 @@ def create_main_layout(self):
200244
view_names = [view_name for view_name in view_names if view_name in self.views.keys()]
201245
widgets_zone[zone] = view_names
202246

203-
## Handle left
204-
first_left = None
205-
for zone in ['zone1', 'zone5', 'zone2', 'zone6']:
206-
if len(widgets_zone[zone]) == 0:
207-
continue
208-
view_name = widgets_zone[zone][0]
247+
import numpy as np
248+
from copy import copy
249+
250+
all_zones = [f'zone{a}' for a in range(1,9)]
251+
all_zones_array = np.transpose(np.reshape(all_zones, (2,4)))
252+
is_zone = np.array([(widgets_zone.get(zone) is not None) and (len(widgets_zone.get(zone)) > 0) for zone in all_zones])
253+
is_zone_array = np.reshape(is_zone, (2,4))
254+
original_zone_array = copy(is_zone_array)
255+
256+
first_nonzero_column = np.arange(4)[np.any(original_zone_array, axis=0)][0]
257+
first_is_top = original_zone_array[:,first_nonzero_column][0]
258+
if not first_is_top:
259+
top_zone = f"zone{first_nonzero_column+1}"
260+
bottom_zone = f"zone{first_nonzero_column+5}"
261+
widgets_zone[top_zone] = widgets_zone[bottom_zone]
262+
widgets_zone[bottom_zone] = []
263+
264+
is_zone = np.array([(widgets_zone.get(zone) is not None) and (len(widgets_zone.get(zone)) > 0) for zone in all_zones])
265+
is_zone_array = np.reshape(is_zone, (2,4))
266+
original_zone_array = copy(is_zone_array)
267+
268+
num_rows_1, num_cols_1 = get_size_top_row(0,0, is_zone_array, original_zone_array)
269+
num_rows_2, num_cols_2 = get_size_top_row(0,1, is_zone_array, original_zone_array)
270+
num_rows_3, num_cols_3 = get_size_top_row(0,2, is_zone_array, original_zone_array)
271+
num_rows_4, num_cols_4 = get_size_top_row(0,3, is_zone_array, original_zone_array)
272+
273+
num_rows_5, num_cols_5 = get_size_bottom_row(1,0, is_zone_array, original_zone_array)
274+
num_rows_6, num_cols_6 = get_size_bottom_row(1,1, is_zone_array, original_zone_array)
275+
num_rows_7, num_cols_7 = get_size_bottom_row(1,2, is_zone_array, original_zone_array)
276+
num_rows_8, num_cols_8 = get_size_bottom_row(1,3, is_zone_array, original_zone_array)
277+
278+
num_rows = np.array([num_rows_1, num_rows_2, num_rows_3, num_rows_4, num_rows_5, num_rows_6, num_rows_7, num_rows_8])
279+
num_cols = np.array([num_cols_1, num_cols_2, num_cols_3, num_cols_4, num_cols_5, num_cols_6, num_cols_7, num_cols_8])
280+
281+
num_rows_array = np.reshape(num_rows, (2,4))
282+
num_cols_array = np.reshape(num_cols, (2,4))
283+
284+
group = []
285+
all_groups = []
286+
for num_row, num_col, is_a_zone, zones in zip(np.transpose(num_rows_array), np.transpose(num_cols_array), np.transpose(original_zone_array), all_zones_array):
287+
if num_row[0] == 2:
288+
if len(group) > 0:
289+
all_groups.append(group)
290+
group = []
291+
allowed_zones = zones[is_a_zone]
292+
all_groups.append(allowed_zones)
293+
else:
294+
for zone in zones[is_a_zone]:
295+
group.append(zone)
296+
297+
if len(group) > 0:
298+
all_groups.append(group)
299+
300+
first_dock = None
301+
print(f"{all_groups}")
302+
first_zone = all_groups[0][0]
303+
print(f"{first_zone=}")
304+
if first_zone in ['zone5', 'zone6', 'zone7', 'zone8']:
305+
above_zone = f'zone{int(first_zone[-1])-4}'
306+
widgets_zone[above_zone] = widgets_zone[first_zone]
307+
first_zone = above_zone
308+
first_dock = widgets_zone[first_zone][0]
309+
dock = self.docks[first_dock]
310+
self.addDockWidget(areas['left'], dock)
311+
312+
for group in reversed(all_groups[1:]):
313+
digits = np.array([int(s[-1]) for s in group])
314+
sorted_indices = np.argsort(digits)
315+
sorted_arr = np.array(group)[sorted_indices]
316+
view_name = widgets_zone[sorted_arr[0]][0]
209317
dock = self.docks[view_name]
210-
if len(widgets_zone[zone]) > 0 and first_left is None:
211-
self.addDockWidget(areas['left'], dock)
212-
first_left = view_name
213-
elif zone == 'zone5':
214-
self.splitDockWidget(self.docks[first_left], dock, orientations['vertical'])
215-
elif zone == 'zone2':
216-
self.splitDockWidget(self.docks[first_left], dock, orientations['horizontal'])
217-
elif zone == 'zone6':
218-
if len(widgets_zone['zone5']) > 0:
219-
z = widgets_zone['zone5'][0]
220-
self.splitDockWidget(self.docks[z], dock, orientations['horizontal'])
221-
else:
222-
self.splitDockWidget(self.docks[first_left], dock, orientations['vertical'])
318+
print(f"Splitting {first_dock}, {view_name}")
319+
self.splitDockWidget(self.docks[first_dock], dock, orientations['horizontal'])
223320

224-
## Handle right
225-
first_top = None
226-
for zone in ['zone3', 'zone4', 'zone7', 'zone8']:
227-
if len(widgets_zone[zone]) == 0:
321+
new_all_groups = []
322+
for group in all_groups:
323+
if len(group) == 1:
228324
continue
229-
view_name = widgets_zone[zone][0]
230-
dock = self.docks[view_name]
231-
if len(widgets_zone[zone]) > 0 and first_top is None:
232-
self.addDockWidget(areas['right'], dock)
233-
first_top = view_name
234-
elif zone == 'zone4':
235-
self.splitDockWidget(self.docks[first_top], dock, orientations['horizontal'])
236-
elif zone == 'zone7':
237-
self.splitDockWidget(self.docks[first_top], dock, orientations['vertical'])
238-
elif zone == 'zone8':
239-
if len(widgets_zone['zone4']) > 0:
240-
z = widgets_zone['zone4'][0]
241-
self.splitDockWidget(self.docks[z], dock, orientations['vertical'])
325+
else:
326+
top_zones = [zone for zone in group if zone in ['zone1', 'zone2', 'zone3', 'zone4']]
327+
bottom_zones = [zone for zone in group if zone in ['zone5', 'zone6', 'zone7', 'zone8']]
328+
new_all_groups.append([top_zones, bottom_zones])
329+
330+
for top_and_bottom_groups in new_all_groups:
331+
332+
top_groups, bottom_groups = top_and_bottom_groups
333+
334+
if len(top_groups) > 0 and len(bottom_groups) > 0:
335+
336+
top_view_name = widgets_zone[top_groups[0]][0]
337+
top_dock = self.docks[top_view_name]
338+
339+
bottom_view_name = widgets_zone[bottom_groups[0]][0]
340+
bottom_dock = self.docks[bottom_view_name]
341+
342+
print(f"Splitting {top_view_name}, {bottom_view_name}")
343+
self.splitDockWidget(top_dock, bottom_dock, orientations['vertical'])
344+
345+
for top_bottom_groups in new_all_groups:
346+
for group in top_bottom_groups:
347+
if len(group) <= 1:
348+
continue
242349
else:
243-
self.splitDockWidget(self.docks[first_top], dock, orientations['horizontal'])
350+
first_zone_name = widgets_zone[group[0]][0]
351+
352+
for zone in reversed(group[1:]):
353+
zone_name = widgets_zone[zone][0]
354+
print(f"Splitting {first_zone_name}, {zone_name}")
355+
self.splitDockWidget(self.docks[first_zone_name], self.docks[zone_name], orientations['horizontal'])
356+
357+
# ## Handle left
358+
# first_left = None
359+
# for zone in ['zone1', 'zone5', 'zone2', 'zone6']:
360+
# if len(widgets_zone[zone]) == 0:
361+
# continue
362+
# view_name = widgets_zone[zone][0]
363+
# dock = self.docks[view_name]
364+
# if len(widgets_zone[zone]) > 0 and first_left is None:
365+
# self.addDockWidget(areas['left'], dock)
366+
# first_left = view_name
367+
# elif zone == 'zone5':
368+
# self.splitDockWidget(self.docks[first_left], dock, orientations['vertical'])
369+
# elif zone == 'zone2':
370+
# self.splitDockWidget(self.docks[first_left], dock, orientations['horizontal'])
371+
# elif zone == 'zone6':
372+
# if len(widgets_zone['zone5']) > 0:
373+
# z = widgets_zone['zone5'][0]
374+
# self.splitDockWidget(self.docks[z], dock, orientations['horizontal'])
375+
# else:
376+
# self.splitDockWidget(self.docks[first_left], dock, orientations['vertical'])
377+
378+
# ## Handle right
379+
# first_top = None
380+
# for zone in ['zone3', 'zone4', 'zone7', 'zone8']:
381+
# if len(widgets_zone[zone]) == 0:
382+
# continue
383+
# view_name = widgets_zone[zone][0]
384+
# dock = self.docks[view_name]
385+
# if len(widgets_zone[zone]) > 0 and first_top is None:
386+
# self.addDockWidget(areas['right'], dock)
387+
# first_top = view_name
388+
# elif zone == 'zone4':
389+
# self.splitDockWidget(self.docks[first_top], dock, orientations['horizontal'])
390+
# elif zone == 'zone7':
391+
# self.splitDockWidget(self.docks[first_top], dock, orientations['vertical'])
392+
# elif zone == 'zone8':
393+
# if len(widgets_zone['zone4']) > 0:
394+
# z = widgets_zone['zone4'][0]
395+
# self.splitDockWidget(self.docks[z], dock, orientations['vertical'])
396+
# else:
397+
# self.splitDockWidget(self.docks[first_top], dock, orientations['horizontal'])
244398

245399
# make tabs
246400
for zone, view_names in widgets_zone.items():

spikeinterface_gui/curation_tools.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"manual_labels": [],
1515
"merges": [],
1616
"splits": [],
17-
"removes": []
17+
"removed": []
1818
}
1919

2020
def add_merge(previous_merges, new_merge_unit_ids):

0 commit comments

Comments
 (0)