1- from collections import defaultdict
1+ from collections import defaultdict , namedtuple
22
33import numpy as np
44import 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