Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ENH] SOM - Warn user to restart optimization after parameter change #6438

Merged
merged 1 commit into from
Apr 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 47 additions & 11 deletions Orange/widgets/unsupervised/owsom.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from collections import defaultdict, namedtuple
from contextlib import contextmanager
from typing import Optional
from xml.sax.saxutils import escape

Expand Down Expand Up @@ -171,6 +172,15 @@ def paint(self, painter, _option, _index):
painter.restore()


@contextmanager
def disconnected_spin(spin):
spin.blockSignals(True)
try:
yield
finally:
spin.blockSignals(False)


N_ITERATIONS = 200


Expand Down Expand Up @@ -209,6 +219,12 @@ class Outputs:
("shape", "auto_dim", "spin_x", "spin_y", "initialization", "start")
)

class Information(OWWidget.Information):
modified = Msg(
'The parameter settings have been changed. Press "Start" to '
"rerun with the new settings."
)

class Warning(OWWidget.Warning):
ignoring_disc_variables = Msg("SOM ignores categorical variables.")
missing_colors = \
Expand Down Expand Up @@ -243,34 +259,48 @@ def __init__(self):
shape = gui.comboBox(
box, self, "", items=("Hexagonal grid", "Square grid"))
shape.setCurrentIndex(1 - self.hexagonal)
shape.currentIndexChanged.connect(self.on_parameter_change)

box2 = gui.indentedBox(box, 10)
auto_dim = gui.checkBox(
box2, self, "auto_dimension", "Set dimensions automatically",
callback=self.on_auto_dimension_changed)
self.manual_box = box3 = gui.hBox(box2)
spinargs = dict(
value="", widget=box3, master=self, minv=5, maxv=100, step=5,
alignment=Qt.AlignRight)
spin_x = gui.spin(**spinargs)
spin_x.setValue(self.size_x)
value="",
widget=box3,
master=self,
minv=5,
maxv=100,
step=5,
alignment=Qt.AlignRight,
callback=self.on_parameter_change,
)
self.spin_x = gui.spin(**spinargs)
with disconnected_spin(self.spin_x):
self.spin_x.setValue(self.size_x)
gui.widgetLabel(box3, "×")
spin_y = gui.spin(**spinargs)
spin_y.setValue(self.size_y)
self.spin_y = gui.spin(**spinargs)
with disconnected_spin(self.spin_y):
self.spin_y.setValue(self.size_y)
gui.rubber(box3)
self.manual_box.setEnabled(not self.auto_dimension)

initialization = gui.comboBox(
box, self, "initialization",
items=("Initialize with PCA", "Random initialization",
"Replicable random"))
box,
self,
"initialization",
items=("Initialize with PCA", "Random initialization", "Replicable random"),
callback=self.on_parameter_change,
)

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

self.opt_controls = self.OptControls(
shape, auto_dim, spin_x, spin_y, initialization, start)
shape, auto_dim, self.spin_x, self.spin_y, initialization, start
)

box = gui.vBox(self.controlArea, "Color")
gui.comboBox(
Expand Down Expand Up @@ -366,7 +396,8 @@ def set_warnings():
self.set_color_bins()
self.create_legend()
if invalidated:
self.recompute_dimensions()
with disconnected_spin(self.spin_x), disconnected_spin(self.spin_y):
self.recompute_dimensions()
self.start_som()
else:
self._redraw()
Expand Down Expand Up @@ -399,6 +430,7 @@ def on_auto_dimension_changed(self):
dimy = int(5 * np.round(spin_y.value() / 5))
spin_x.setValue(dimx)
spin_y.setValue(dimy)
self.on_parameter_change()

def on_attr_color_change(self):
self.controls.pie_charts.setEnabled(self.attr_color is not None)
Expand All @@ -413,6 +445,9 @@ def on_attr_size_change(self):
def on_pie_chart_change(self):
self._redraw()

def on_parameter_change(self):
self.Information.modified()

def clear_selection(self):
self.selection = None
self.redraw_selection()
Expand Down Expand Up @@ -498,6 +533,7 @@ def redraw_selection(self, marks=None):
cell.setZValue(marked or sel_group)

def restart_som_pressed(self):
self.Information.modified.clear()
if self._optimizer_thread is not None:
self.stop_optimization = True
self._optimizer.stop_optimization = True
Expand Down
38 changes: 38 additions & 0 deletions Orange/widgets/unsupervised/tests/test_owsom.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

import numpy as np
import scipy.sparse as sp
from AnyQt.QtWidgets import QComboBox, QPushButton, QCheckBox
from AnyQt.QtCore import Qt

from Orange.data import Table, Domain
from Orange.widgets.tests.base import WidgetTest
Expand Down Expand Up @@ -646,6 +648,42 @@ def test_invalidated(self):
self.send_signal(self.widget.Inputs.data, heart_with_less_features)
self.widget._recompute_som.assert_called_once()

def test_modified_info(self):
w = self.widget
self.assertFalse(w.Information.modified.is_shown())
self.send_signal(w.Inputs.data, self.iris)
self.assertFalse(w.Information.modified.is_shown())
restart_button = w.controlArea.findChild(QPushButton)

# modify grid
simulate.combobox_activate_index(w.controlArea.findChild(QComboBox), 1)
self.assertTrue(w.Information.modified.is_shown())
restart_button.click()
self.assertFalse(w.Information.modified.is_shown())

# modify set dimensions automatically
w.controlArea.findChild(QCheckBox).setCheckState(Qt.Unchecked)
self.assertTrue(w.Information.modified.is_shown())
restart_button.click()
self.assertFalse(w.Information.modified.is_shown())

# modify dimension spins
w.spin_x.setValue(7)
self.assertTrue(w.Information.modified.is_shown())
restart_button.click()
self.assertFalse(w.Information.modified.is_shown())

w.spin_y.setValue(7)
self.assertTrue(w.Information.modified.is_shown())
restart_button.click()
self.assertFalse(w.Information.modified.is_shown())

# modify initialization
simulate.combobox_activate_index(w.controlArea.findChildren(QComboBox)[1], 1)
self.assertTrue(w.Information.modified.is_shown())
restart_button.click()
self.assertFalse(w.Information.modified.is_shown())


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