12
12
from AnyQt .QtCore import (
13
13
Qt , QObject , QTimer , pyqtSignal as Signal , pyqtSlot as Slot
14
14
)
15
- from AnyQt .QtWidgets import QSlider , QCheckBox , QWidget
15
+ from AnyQt .QtWidgets import QSlider , QCheckBox , QWidget , QLabel
16
16
17
17
from Orange .clustering .louvain import table_to_knn_graph , Louvain
18
18
from Orange .data import Table , DiscreteVariable
40
40
_DEFAULT_K_NEIGHBORS = 30
41
41
42
42
43
- METRICS = [(' Euclidean' , 'l2' ), (' Manhattan' , 'l1' )]
43
+ METRICS = [(" Euclidean" , "l2" ), (" Manhattan" , "l1" )]
44
44
45
45
46
46
class OWLouvainClustering (widget .OWWidget ):
47
- name = ' Louvain Clustering'
48
- description = ' Detects communities in a network of nearest neighbors.'
49
- icon = ' icons/LouvainClustering.svg'
47
+ name = " Louvain Clustering"
48
+ description = " Detects communities in a network of nearest neighbors."
49
+ icon = " icons/LouvainClustering.svg"
50
50
priority = 2110
51
51
52
52
want_main_area = False
53
53
54
54
settingsHandler = DomainContextHandler ()
55
55
56
56
class Inputs :
57
- data = Input (' Data' , Table , default = True )
57
+ data = Input (" Data" , Table , default = True )
58
58
59
59
if Graph is not None :
60
60
class Outputs :
61
61
annotated_data = Output (ANNOTATED_DATA_SIGNAL_NAME , Table , default = True )
62
- graph = Output (' Network' , Graph )
62
+ graph = Output (" Network" , Graph )
63
63
else :
64
64
class Outputs :
65
65
annotated_data = Output (ANNOTATED_DATA_SIGNAL_NAME , Table , default = True )
@@ -75,8 +75,7 @@ class Information(widget.OWWidget.Information):
75
75
modified = Msg ("Press commit to recompute clusters and send new data" )
76
76
77
77
class Error (widget .OWWidget .Error ):
78
- empty_dataset = Msg ('No features in data' )
79
- general_error = Msg ('Error occured during clustering\n {}' )
78
+ empty_dataset = Msg ("No features in data" )
80
79
81
80
def __init__ (self ):
82
81
super ().__init__ ()
@@ -98,40 +97,44 @@ def __init__(self):
98
97
self .__commit_timer = QTimer (self , singleShot = True )
99
98
self .__commit_timer .timeout .connect (self .commit )
100
99
101
- pca_box = gui .vBox (self .controlArea , 'PCA Preprocessing' )
100
+ # Set up UI
101
+ info_box = gui .vBox (self .controlArea , "Info" )
102
+ self .info_label = gui .widgetLabel (info_box , "No data on input." ) # type: QLabel
103
+
104
+ pca_box = gui .vBox (self .controlArea , "PCA Preprocessing" )
102
105
self .apply_pca_cbx = gui .checkBox (
103
- pca_box , self , ' apply_pca' , label = ' Apply PCA preprocessing' ,
106
+ pca_box , self , " apply_pca" , label = " Apply PCA preprocessing" ,
104
107
callback = self ._invalidate_graph ,
105
108
) # type: QCheckBox
106
109
self .pca_components_slider = gui .hSlider (
107
- pca_box , self , ' pca_components' , label = ' Components: ' , minValue = 2 ,
110
+ pca_box , self , " pca_components" , label = " Components: " , minValue = 2 ,
108
111
maxValue = _MAX_PCA_COMPONENTS ,
109
112
callback = self ._invalidate_pca_projection , tracking = False
110
113
) # type: QSlider
111
114
112
- graph_box = gui .vBox (self .controlArea , ' Graph parameters' )
115
+ graph_box = gui .vBox (self .controlArea , " Graph parameters" )
113
116
self .metric_combo = gui .comboBox (
114
- graph_box , self , ' metric_idx' , label = ' Distance metric' ,
117
+ graph_box , self , " metric_idx" , label = " Distance metric" ,
115
118
items = [m [0 ] for m in METRICS ], callback = self ._invalidate_graph ,
116
119
orientation = Qt .Horizontal ,
117
120
) # type: gui.OrangeComboBox
118
121
self .k_neighbors_spin = gui .spin (
119
- graph_box , self , ' k_neighbors' , minv = 1 , maxv = _MAX_K_NEIGBOURS ,
120
- label = ' k neighbors' , controlWidth = 80 , alignment = Qt .AlignRight ,
122
+ graph_box , self , " k_neighbors" , minv = 1 , maxv = _MAX_K_NEIGBOURS ,
123
+ label = " k neighbors" , controlWidth = 80 , alignment = Qt .AlignRight ,
121
124
callback = self ._invalidate_graph ,
122
125
) # type: gui.SpinBoxWFocusOut
123
126
self .resolution_spin = gui .hSlider (
124
- graph_box , self , ' resolution' , minValue = 0 , maxValue = 5. , step = 1e-1 ,
125
- label = ' Resolution' , intOnly = False , labelFormat = ' %.1f' ,
127
+ graph_box , self , " resolution" , minValue = 0 , maxValue = 5. , step = 1e-1 ,
128
+ label = " Resolution" , intOnly = False , labelFormat = " %.1f" ,
126
129
callback = self ._invalidate_partition , tracking = False ,
127
130
) # type: QSlider
128
131
self .resolution_spin .parent ().setToolTip (
129
- ' The resolution parameter affects the number of clusters to find. '
130
- ' Smaller values tend to produce more clusters and larger values '
131
- ' retrieve less clusters.'
132
+ " The resolution parameter affects the number of clusters to find. "
133
+ " Smaller values tend to produce more clusters and larger values "
134
+ " retrieve less clusters."
132
135
)
133
136
self .apply_button = gui .auto_commit (
134
- self .controlArea , self , ' auto_commit' , ' Apply' , box = None ,
137
+ self .controlArea , self , " auto_commit" , " Apply" , box = None ,
135
138
commit = lambda : self .commit (),
136
139
callback = lambda : self ._on_auto_commit_changed (),
137
140
) # type: QWidget
@@ -248,6 +251,7 @@ def commit(self):
248
251
run_on_graph , graph , resolution = self .resolution , state = state
249
252
)
250
253
254
+ self .info_label .setText ("Running..." )
251
255
self .__set_state_busy ()
252
256
self .__start_task (task , state )
253
257
@@ -269,7 +273,7 @@ def __set_partial_results(self, result):
269
273
270
274
@Slot (object )
271
275
def __on_done (self , future ):
272
- # type: (Future[' Results' ]) -> None
276
+ # type: (Future[" Results" ]) -> None
273
277
assert future .done ()
274
278
assert self .__task is not None
275
279
assert self .__task .future is future
@@ -278,12 +282,9 @@ def __on_done(self, future):
278
282
task .deleteLater ()
279
283
280
284
self .__set_state_ready ()
281
- try :
282
- result = future .result ()
283
- except Exception as err : # pylint: disable=broad-except
284
- self .Error .general_error (str (err ), exc_info = True )
285
- else :
286
- self .__set_results (result )
285
+
286
+ result = future .result ()
287
+ self .__set_results (result )
287
288
288
289
@Slot (str )
289
290
def setStatusMessage (self , text ):
@@ -330,7 +331,7 @@ def __cancel_task(self, wait=True):
330
331
w .done .connect (state .deleteLater )
331
332
332
333
def __set_results (self , results ):
333
- # type: (' Results' ) -> None
334
+ # type: (" Results" ) -> None
334
335
# NOTE: All of these have already been set by __set_partial_results,
335
336
# we double check that they are aliases
336
337
if results .pca_projection is not None :
@@ -346,6 +347,11 @@ def __set_results(self, results):
346
347
assert results .resolution == self .resolution
347
348
assert self .partition is results .partition
348
349
self .partition = results .partition
350
+
351
+ # Display the number of found clusters in the UI
352
+ num_clusters = len (np .unique (self .partition ))
353
+ self .info_label .setText ("%d clusters found." % num_clusters )
354
+
349
355
self ._send_data ()
350
356
351
357
def _send_data (self ):
@@ -359,8 +365,8 @@ def _send_data(self):
359
365
new_partition = list (map (index_map .get , self .partition ))
360
366
361
367
cluster_var = DiscreteVariable (
362
- get_unique_names (domain , ' Cluster' ),
363
- values = [' C%d' % (i + 1 ) for i , _ in enumerate (np .unique (new_partition ))]
368
+ get_unique_names (domain , " Cluster" ),
369
+ values = [" C%d" % (i + 1 ) for i , _ in enumerate (np .unique (new_partition ))]
364
370
)
365
371
366
372
new_domain = add_columns (domain , metas = [cluster_var ])
@@ -406,6 +412,8 @@ def set_data(self, data):
406
412
self .k_neighbors_spin .setMaximum (min (_MAX_K_NEIGBOURS , len (data ) - 1 ))
407
413
self .k_neighbors_spin .setValue (min (_DEFAULT_K_NEIGHBORS , len (data ) - 1 ))
408
414
415
+ self .info_label .setText ("Clustering not yet run." )
416
+
409
417
self .commit ()
410
418
411
419
def clear (self ):
@@ -416,6 +424,7 @@ def clear(self):
416
424
self .partition = None
417
425
self .Error .clear ()
418
426
self .Information .modified .clear ()
427
+ self .info_label .setText ("No data on input." )
419
428
420
429
def onDeleteWidget (self ):
421
430
self .__cancel_task (wait = True )
@@ -427,13 +436,13 @@ def onDeleteWidget(self):
427
436
def send_report (self ):
428
437
pca = report .bool_str (self .apply_pca )
429
438
if self .apply_pca :
430
- pca += report .plural (' , {number} component{s}' , self .pca_components )
439
+ pca += report .plural (" , {number} component{s}" , self .pca_components )
431
440
432
441
self .report_items ((
433
- (' PCA preprocessing' , pca ),
434
- (' Metric' , METRICS [self .metric_idx ][0 ]),
435
- (' k neighbors' , self .k_neighbors ),
436
- (' Resolution' , self .resolution ),
442
+ (" PCA preprocessing" , pca ),
443
+ (" Metric" , METRICS [self .metric_idx ][0 ]),
444
+ (" k neighbors" , self .k_neighbors ),
445
+ (" Resolution" , self .resolution ),
437
446
))
438
447
439
448
@@ -614,5 +623,5 @@ def run_on_graph(graph, resolution, state):
614
623
return res
615
624
616
625
617
- if __name__ == ' __main__' : # pragma: no cover
626
+ if __name__ == " __main__" : # pragma: no cover
618
627
WidgetPreview (OWLouvainClustering ).run (Table ("iris" ))
0 commit comments