Skip to content

Commit 6b47e02

Browse files
authored
Merge pull request #6438 from PrimozGodec/som-info-restart
[ENH] SOM - Warn user to restart optimization after parameter change
2 parents 3cc58b4 + 10b7a72 commit 6b47e02

File tree

2 files changed

+85
-11
lines changed

2 files changed

+85
-11
lines changed

Orange/widgets/unsupervised/owsom.py

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from collections import defaultdict, namedtuple
2+
from contextlib import contextmanager
23
from typing import Optional
34
from xml.sax.saxutils import escape
45

@@ -171,6 +172,15 @@ def paint(self, painter, _option, _index):
171172
painter.restore()
172173

173174

175+
@contextmanager
176+
def disconnected_spin(spin):
177+
spin.blockSignals(True)
178+
try:
179+
yield
180+
finally:
181+
spin.blockSignals(False)
182+
183+
174184
N_ITERATIONS = 200
175185

176186

@@ -209,6 +219,12 @@ class Outputs:
209219
("shape", "auto_dim", "spin_x", "spin_y", "initialization", "start")
210220
)
211221

222+
class Information(OWWidget.Information):
223+
modified = Msg(
224+
'The parameter settings have been changed. Press "Start" to '
225+
"rerun with the new settings."
226+
)
227+
212228
class Warning(OWWidget.Warning):
213229
ignoring_disc_variables = Msg("SOM ignores categorical variables.")
214230
missing_colors = \
@@ -243,34 +259,48 @@ def __init__(self):
243259
shape = gui.comboBox(
244260
box, self, "", items=("Hexagonal grid", "Square grid"))
245261
shape.setCurrentIndex(1 - self.hexagonal)
262+
shape.currentIndexChanged.connect(self.on_parameter_change)
246263

247264
box2 = gui.indentedBox(box, 10)
248265
auto_dim = gui.checkBox(
249266
box2, self, "auto_dimension", "Set dimensions automatically",
250267
callback=self.on_auto_dimension_changed)
251268
self.manual_box = box3 = gui.hBox(box2)
252269
spinargs = dict(
253-
value="", widget=box3, master=self, minv=5, maxv=100, step=5,
254-
alignment=Qt.AlignRight)
255-
spin_x = gui.spin(**spinargs)
256-
spin_x.setValue(self.size_x)
270+
value="",
271+
widget=box3,
272+
master=self,
273+
minv=5,
274+
maxv=100,
275+
step=5,
276+
alignment=Qt.AlignRight,
277+
callback=self.on_parameter_change,
278+
)
279+
self.spin_x = gui.spin(**spinargs)
280+
with disconnected_spin(self.spin_x):
281+
self.spin_x.setValue(self.size_x)
257282
gui.widgetLabel(box3, "×")
258-
spin_y = gui.spin(**spinargs)
259-
spin_y.setValue(self.size_y)
283+
self.spin_y = gui.spin(**spinargs)
284+
with disconnected_spin(self.spin_y):
285+
self.spin_y.setValue(self.size_y)
260286
gui.rubber(box3)
261287
self.manual_box.setEnabled(not self.auto_dimension)
262288

263289
initialization = gui.comboBox(
264-
box, self, "initialization",
265-
items=("Initialize with PCA", "Random initialization",
266-
"Replicable random"))
290+
box,
291+
self,
292+
"initialization",
293+
items=("Initialize with PCA", "Random initialization", "Replicable random"),
294+
callback=self.on_parameter_change,
295+
)
267296

268297
start = gui.button(
269298
box, self, "Restart", callback=self.restart_som_pressed,
270299
sizePolicy=(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed))
271300

272301
self.opt_controls = self.OptControls(
273-
shape, auto_dim, spin_x, spin_y, initialization, start)
302+
shape, auto_dim, self.spin_x, self.spin_y, initialization, start
303+
)
274304

275305
box = gui.vBox(self.controlArea, "Color")
276306
gui.comboBox(
@@ -366,7 +396,8 @@ def set_warnings():
366396
self.set_color_bins()
367397
self.create_legend()
368398
if invalidated:
369-
self.recompute_dimensions()
399+
with disconnected_spin(self.spin_x), disconnected_spin(self.spin_y):
400+
self.recompute_dimensions()
370401
self.start_som()
371402
else:
372403
self._redraw()
@@ -399,6 +430,7 @@ def on_auto_dimension_changed(self):
399430
dimy = int(5 * np.round(spin_y.value() / 5))
400431
spin_x.setValue(dimx)
401432
spin_y.setValue(dimy)
433+
self.on_parameter_change()
402434

403435
def on_attr_color_change(self):
404436
self.controls.pie_charts.setEnabled(self.attr_color is not None)
@@ -413,6 +445,9 @@ def on_attr_size_change(self):
413445
def on_pie_chart_change(self):
414446
self._redraw()
415447

448+
def on_parameter_change(self):
449+
self.Information.modified()
450+
416451
def clear_selection(self):
417452
self.selection = None
418453
self.redraw_selection()
@@ -498,6 +533,7 @@ def redraw_selection(self, marks=None):
498533
cell.setZValue(marked or sel_group)
499534

500535
def restart_som_pressed(self):
536+
self.Information.modified.clear()
501537
if self._optimizer_thread is not None:
502538
self.stop_optimization = True
503539
self._optimizer.stop_optimization = True

Orange/widgets/unsupervised/tests/test_owsom.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
import numpy as np
77
import scipy.sparse as sp
8+
from AnyQt.QtWidgets import QComboBox, QPushButton, QCheckBox
9+
from AnyQt.QtCore import Qt
810

911
from Orange.data import Table, Domain
1012
from Orange.widgets.tests.base import WidgetTest
@@ -646,6 +648,42 @@ def test_invalidated(self):
646648
self.send_signal(self.widget.Inputs.data, heart_with_less_features)
647649
self.widget._recompute_som.assert_called_once()
648650

651+
def test_modified_info(self):
652+
w = self.widget
653+
self.assertFalse(w.Information.modified.is_shown())
654+
self.send_signal(w.Inputs.data, self.iris)
655+
self.assertFalse(w.Information.modified.is_shown())
656+
restart_button = w.controlArea.findChild(QPushButton)
657+
658+
# modify grid
659+
simulate.combobox_activate_index(w.controlArea.findChild(QComboBox), 1)
660+
self.assertTrue(w.Information.modified.is_shown())
661+
restart_button.click()
662+
self.assertFalse(w.Information.modified.is_shown())
663+
664+
# modify set dimensions automatically
665+
w.controlArea.findChild(QCheckBox).setCheckState(Qt.Unchecked)
666+
self.assertTrue(w.Information.modified.is_shown())
667+
restart_button.click()
668+
self.assertFalse(w.Information.modified.is_shown())
669+
670+
# modify dimension spins
671+
w.spin_x.setValue(7)
672+
self.assertTrue(w.Information.modified.is_shown())
673+
restart_button.click()
674+
self.assertFalse(w.Information.modified.is_shown())
675+
676+
w.spin_y.setValue(7)
677+
self.assertTrue(w.Information.modified.is_shown())
678+
restart_button.click()
679+
self.assertFalse(w.Information.modified.is_shown())
680+
681+
# modify initialization
682+
simulate.combobox_activate_index(w.controlArea.findChildren(QComboBox)[1], 1)
683+
self.assertTrue(w.Information.modified.is_shown())
684+
restart_button.click()
685+
self.assertFalse(w.Information.modified.is_shown())
686+
649687

650688
if __name__ == "__main__":
651689
unittest.main()

0 commit comments

Comments
 (0)