Skip to content

Commit f57c7d5

Browse files
committed
OWSOM: Reorganize gui
1 parent 4ef7614 commit f57c7d5

File tree

1 file changed

+70
-57
lines changed

1 file changed

+70
-57
lines changed

Orange/widgets/unsupervised/owsom.py

Lines changed: 70 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from collections import defaultdict
1+
from collections import defaultdict, namedtuple
22

33
import numpy as np
44
import scipy.sparse as sp
@@ -164,7 +164,7 @@ class Outputs:
164164
annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table)
165165

166166
settingsHandler = DomainContextHandler()
167-
manual_dimension = Setting(False)
167+
auto_dimension = Setting(True)
168168
size_x = Setting(10)
169169
size_y = Setting(10)
170170
hexagonal = Setting(1)
@@ -180,6 +180,11 @@ class Outputs:
180180
_grid_pen = QPen(QBrush(QColor(224, 224, 224)), 2)
181181
_grid_pen.setCosmetic(True)
182182

183+
OptControls = namedtuple(
184+
"OptControls",
185+
("shape", "auto_dim", "spin_x", "spin_y", "initialization", "start")
186+
)
187+
183188
class Warning(OWWidget.Warning):
184189
ignoring_disc_variables = Msg("SOM ignores discrete variables.")
185190
missing_colors = \
@@ -201,34 +206,38 @@ def __init__(self):
201206
self.selection = set()
202207
self.colors = self.thresholds = None
203208

204-
box = gui.vBox(self.controlArea, box=True)
205-
hbox = gui.hBox(box)
206-
self.restart_button = gui.button(
207-
hbox, self, "Restart", callback=self.restart_som_pressed,
208-
sizePolicy=(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed))
209-
gui.radioButtons(
210-
box, self, "initialization",
211-
("Initialize with PCA", "Random initialization",
212-
"Replicable random"))
209+
box = gui.vBox(self.controlArea, box="SOM")
210+
shape = gui.comboBox(
211+
box, self, "", items=("Hexagonal grid", "Square grid"))
212+
shape.setCurrentIndex(1 - self.hexagonal)
213213

214-
self.grid_box = box = gui.vBox(self.controlArea, "Geometry")
215-
gui.comboBox(
216-
box, self, "hexagonal", items=("Square grid", "Hexagonal grid"),
217-
callback=self.on_geometry_change)
218214
box2 = gui.indentedBox(box, 10)
219-
gui.checkBox(
220-
box2, self, "manual_dimension", "Set dimensions manually",
221-
callback=self.on_manual_dimension_change)
215+
auto_dim = gui.checkBox(
216+
box2, self, "auto_dimension", "Set dimensions automatically",
217+
callback=self.recompute_dimensions)
222218
self.manual_box = box3 = gui.hBox(box2)
223219
spinargs = dict(
224-
widget=box3, master=self, minv=5, maxv=100, step=5,
225-
alignment=Qt.AlignRight, callback=self.on_geometry_change)
226-
gui.spin(value="size_x", **spinargs)
220+
value="", widget=box3, master=self, minv=5, maxv=100, step=5,
221+
alignment=Qt.AlignRight)
222+
spin_x = gui.spin(**spinargs)
223+
spin_x.setValue(self.size_x)
227224
gui.widgetLabel(box3, "×")
228-
gui.spin(value="size_y", **spinargs)
229-
self.manual_box.setDisabled(not self.manual_dimension)
225+
spin_y = gui.spin(**spinargs)
226+
spin_x.setValue(self.size_y)
230227
gui.rubber(box3)
231228

229+
initialization = gui.comboBox(
230+
box, self, "initialization",
231+
items=("Initialize with PCA", "Random initialization",
232+
"Replicable random"))
233+
234+
start = gui.button(
235+
box, self, "Restart", callback=self.restart_som_pressed,
236+
sizePolicy=(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed))
237+
238+
self.opt_controls = self.OptControls(
239+
shape, auto_dim, spin_x, spin_y, initialization, start)
240+
232241
box = gui.vBox(self.controlArea, "Color")
233242
gui.comboBox(
234243
box, self, "attr_color", maximumContentsLength=15,
@@ -261,7 +270,6 @@ def __init__(self):
261270
self.grid = None
262271
self.grid_cells = None
263272
self.legend = None
264-
self.redraw_grid()
265273

266274
@Inputs.data
267275
def set_data(self, data):
@@ -308,9 +316,8 @@ def set_data(self, data):
308316
self.set_color_bins()
309317
self.create_legend()
310318
self.recompute_dimensions()
311-
self.replot()
312319
self._set_input_summary(data and len(data))
313-
self.update_output()
320+
self.start_som()
314321

315322
def _set_input_summary(self, n_tot):
316323
if self.data is None:
@@ -343,29 +350,15 @@ def clear(self):
343350
self.Error.clear()
344351

345352
def recompute_dimensions(self):
346-
self.manual_box.setEnabled(self.manual_dimension)
347-
if not self.manual_dimension and self.cont_x is not None:
348-
self.size_x = self.size_y = \
353+
self.manual_box.setEnabled(not self.auto_dimension)
354+
if not self.auto_dimension and self.cont_x is not None:
355+
dimx = dimy = \
349356
max(5, int(np.ceil(np.sqrt(5 * np.sqrt(self.cont_x.shape[0])))))
350357
else:
351-
self.size_x = int(5 * np.round(self.size_x / 5))
352-
self.size_y = int(5 * np.round(self.size_y / 5))
353-
self.rescale()
354-
self.redraw_grid()
355-
self.set_legend_pos()
356-
357-
def on_manual_dimension_change(self):
358-
self.recompute_dimensions()
359-
self.replot()
360-
361-
def on_geometry_change(self):
362-
self.set_legend_pos()
363-
self.rescale()
364-
if self.elements: # Prevent having redrawn grid but with old elements
365-
self.scene.removeItem(self.elements)
366-
self.elements = None
367-
self.redraw_grid()
368-
self.replot()
358+
dimx = int(5 * np.round(self.size_x / 5))
359+
dimy = int(5 * np.round(self.size_y / 5))
360+
self.opt_controls.spin_x.setValue(dimx)
361+
self.opt_controls.spin_y.setValue(dimy)
369362

370363
def on_attr_color_change(self):
371364
self.controls.pie_charts.setEnabled(self.attr_color is not None)
@@ -425,6 +418,8 @@ def on_selection_mark_change(self, marks):
425418
self.redraw_selection(marks=marks)
426419

427420
def redraw_selection(self, marks=None):
421+
if self.grid_cells is None:
422+
return
428423
mark_brush = QBrush(QColor(224, 255, 255))
429424
brushes = [[QBrush(Qt.NoBrush), QBrush(QColor(240, 240, 255))],
430425
[mark_brush, mark_brush]]
@@ -443,17 +438,39 @@ def redraw_selection(self, marks=None):
443438
cell.setPen(pens[marked][selected])
444439
cell.setZValue(marked or selected)
445440

446-
def replot(self):
447-
self.clear_selection()
448-
self._recompute_som()
449-
450441
def restart_som_pressed(self):
451442
if self._optimizer_thread is not None:
452443
self.stop_optimization = True
453444
else:
445+
self.start_som()
446+
447+
def start_som(self):
448+
self.read_controls()
449+
self.enable_controls(False)
450+
self.update_layout()
454451
self.clear_selection()
455452
self._recompute_som()
456453

454+
def read_controls(self):
455+
c = self.opt_controls
456+
self.hexagonal = c.shape.currentIndex() == 0
457+
self.size_x = c.spin_x.value()
458+
self.size_y = c.spin_y.value()
459+
460+
def enable_controls(self, enable):
461+
c = self.opt_controls
462+
c.shape.setEnabled(enable)
463+
c.auto_dim.setEnabled(enable)
464+
c.start.setText("Start" if enable else "Stop")
465+
466+
def update_layout(self):
467+
self.set_legend_pos()
468+
if self.elements: # Prevent having redrawn grid but with old elements
469+
self.scene.removeItem(self.elements)
470+
self.elements = None
471+
self.redraw_grid()
472+
self.rescale()
473+
457474
def _redraw(self):
458475
self.Warning.missing_colors.clear()
459476
if self.elements:
@@ -620,7 +637,7 @@ def update(_progress, weights, ssum_weights):
620637
self._redraw()
621638

622639
def done(som):
623-
self.set_buttons(running=False)
640+
self.enable_controls(True)
624641
progressbar.finish()
625642
self._assign_instances(som.weights, som.ssum_weights)
626643
self._redraw()
@@ -629,13 +646,13 @@ def done(som):
629646
if self.__pending_selection is not None:
630647
self.on_selection_change(self.__pending_selection)
631648
self.__pending_selection = None
649+
self.update_output()
632650

633651
def thread_finished():
634652
self._optimizer = None
635653
self._optimizer_thread = None
636654

637655
progressbar = gui.ProgressBar(self, N_ITERATIONS)
638-
self.set_buttons(running=True)
639656

640657
self._optimizer = Optimizer(self.cont_x, self)
641658
self._optimizer_thread = QThread()
@@ -660,10 +677,6 @@ def onDeleteWidget(self):
660677
self.clear()
661678
super().onDeleteWidget()
662679

663-
def set_buttons(self, running):
664-
self.restart_button.setText("Stop" if running else "Restart")
665-
self.grid_box.setDisabled(running)
666-
667680
def _assign_instances(self, weights, ssum_weights):
668681
if self.cont_x is None:
669682
return # the widget is shutting down while signals still processed

0 commit comments

Comments
 (0)