ADDED: The DBSCAN and OPTIX clustering algorithms (implementation by Eberle)
[mldemos:ashwini_shuklas-mldemos.git] / _AlgorithmsPlugins / DBSCAN / interfaceDBSCAN.cpp
1 /*********************************************************************
2 MLDemos: A User-Friendly visualization toolkit for machine learning
3 Copyright (C) 2010  Basilio Noris
4 Contact: mldemos@b4silio.com
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public License,
8 version 3 as published by the Free Software Foundation.
9
10 This library is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, write to the Free
17 Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 *********************************************************************/
19 #include "interfaceDBSCAN.h"
20 #include "drawUtils.h"
21 #include <QPixmap>
22 #include <QBitmap>
23 #include <QPainter>
24
25 using namespace std;
26
27 ClustDBSCAN::ClustDBSCAN()
28     : widget(new QWidget()), zoomWidget(new QWidget())
29 {
30     // we initialize the hyperparameter widget
31         params = new Ui::ParametersDBSCAN();
32         params->setupUi(widget);
33
34     // and the graph windows
35         graphzoom = new Ui::graphZoom();
36         graphzoom->setupUi(zoomWidget);
37         zoomWidget->setWindowTitle("OPTICS reachability-distance plot");
38         params->zoomButton->setVisible(false);
39
40       // connecting slots for opening the windows and changing the UI
41         connect(params->zoomButton, SIGNAL(clicked()), this, SLOT(showZoom()));
42         connect(params->typeCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(typeChanged(int)));
43 }
44
45 // Paint the graph on the new windows and show it. User may also click on the button to refresh the plot.
46 void ClustDBSCAN::showZoom()
47 {
48         QPixmap pixmap(graphzoom->graph->size());
49         QBitmap bitmap(graphzoom->graph->size());
50         pixmap.setMask(bitmap);
51         pixmap.fill(Qt::transparent);
52         QPainter dPainter(&pixmap);
53         DrawDendogram(dPainter,true);
54         graphzoom->graph->setPixmap(pixmap);
55         zoomWidget->show();
56 }
57
58 // DBSCAN and OPTICS do not have the same parameters and DBSCAN doesn't need the plotting zone.
59 void ClustDBSCAN::typeChanged(int ntype)
60 {
61     if (ntype==0) // DBSCAN
62     {
63        params->depthSpin->setVisible(false);
64        params->dendoGraph->setVisible(false);
65        params->label_10->setVisible(false);
66        params->zoomButton->setVisible(false);
67     }
68     else
69     {
70         params->depthSpin->setVisible(true);
71         params->dendoGraph->setVisible(true);
72         params->label_10->setVisible(true);
73     }
74 }
75
76 void ClustDBSCAN::SetParams(Clusterer *clusterer)
77 {
78     if(!clusterer) return;
79     // the dynamic cast ensures that the pointer we received is really a clusterDBSCAN
80     ClustererDBSCAN * dbscan = dynamic_cast<ClustererDBSCAN *>(clusterer);
81     // if it isnt, we return
82     if(!dbscan) return;
83
84     // here we gather the different hyperparameters from the interface
85     double minpts = params->minptsSpin->value();
86     double eps = params->epsSpin->value();
87     int metric = params->metricCombo->currentIndex();
88     int type = params->typeCombo->currentIndex();
89     double depth = params->depthSpin->value();
90
91
92     // and finally we set the parameters of the algorithm
93     dbscan->SetParams(minpts, eps, metric,depth,type);
94 }
95
96 Clusterer *ClustDBSCAN::GetClusterer()
97 {
98     // we instanciate the algorithm object
99     ClustererDBSCAN *clusterer = new ClustererDBSCAN();
100     // we set its parameters
101     SetParams(clusterer);
102     // we return it to the main program
103         return clusterer;
104 }
105
106 void ClustDBSCAN::DrawInfo(Canvas *canvas, QPainter &painter, Clusterer *clusterer)
107 {
108     if(!canvas || !clusterer) return;
109     painter.setRenderHint(QPainter::Antialiasing);
110
111     ClustererDBSCAN * dbscan = dynamic_cast<ClustererDBSCAN*>(clusterer);
112     if(!dbscan) return;
113
114     if(dbscan->_type==0) // DBSCAN: we draw the core point with a black border
115     {
116         // for every point in the current dataset
117         FOR(i, dbscan->_pointId_to_clusterId.size())
118         {
119             if (dbscan->_core[i] || dbscan->_noise[i])
120             {
121                 // we get the sample
122                 Point sample = dbscan->pts[i];
123                 // transform it again in fvec from Point...
124                 fvec pt;
125                 pt.resize(sample.size(),0);
126                 FOR(j, sample.size())
127                 {
128                     pt[j]=sample[j];
129                 }
130
131                 //... and we get the point in canvas coordinates (2D pixel by pixel) corresponding to the sample (N-dimensional in R)
132                 QPointF point = canvas->toCanvasCoords(pt);
133
134                 if (dbscan->_core[i])
135                 {
136                     // core points get a black edge
137             // set a black edge
138                     painter.setBrush(Qt::NoBrush);
139                     painter.setPen(QPen(QColor(0,0,0),2));
140             // and draw the sample itself
141                     painter.drawEllipse(point,6,6);
142                  }
143                 else
144                 {
145                     // noise get a grey one
146                     // set the color of the edge
147                             painter.setBrush(Qt::NoBrush);
148                             painter.setPen(QPen(Qt::gray,2));
149                     // and draw the sample itself
150                             painter.drawEllipse(point,3,3);
151                 }
152             }
153         }
154     }
155     else // OPTICS: we draw the position in the ordered list
156     {
157         FOR(i, dbscan->_optics_list.size())
158             {
159             // we get the sample
160             Point sample = dbscan->pts[dbscan->_optics_list[i]];
161
162             //transform the sample in fvec and we get the point in canvas coordinates (2D pixel by pixel) corresponding to the sample (N-dimensional in R)
163
164             fvec pt;
165             pt.resize(sample.size(),0);
166             FOR(j, sample.size())
167             {
168                 pt[j]=sample[j];
169             }
170
171             QPointF point = canvas->toCanvasCoords(pt);
172
173             // and we add a text indicating the position in the list
174             QFont font = painter.font();
175             font.setPointSize(8);
176             painter.setFont(font);
177             painter.setPen(Qt::gray); //not easy to choose the color without knowing what will be painted below
178             painter.drawText(point,QString("   %1").arg(i));
179         }
180     }
181 }
182
183 void ClustDBSCAN::DrawDendogram(QPainter &painter, bool legend) //draw the reachability plot
184 {
185     //what is the size of our painting area?
186     int w=painter.window().width();
187     int h=painter.window().height();
188     int pad = 0; // no padding between vertical bars
189
190     vector<double> val; // build the list we will actually plot
191
192     FOR(j,optics_list.size())
193     {
194         if (reachability[optics_list[j]] == -1)
195         {
196             val.push_back(params->epsSpin->value()); // if undefined, plot it at the maximum size
197         }
198         else
199         {
200             val.push_back(reachability[optics_list[j]]);
201         }
202     }
203
204     int dim = val.size();
205     float maxVal = params->epsSpin->value() * 1.1; // make the graph 10% bigger that the highest value
206     painter.setBrush(Qt::white);
207     painter.setPen(Qt::NoPen);
208     painter.drawRect(0,0,w,h); // plotting area
209     painter.setPen(Qt::gray);
210     painter.drawLine(QPointF(pad, h-2*pad), QPointF(w-pad, h-2*pad));// axis
211     painter.drawLine(QPointF(pad, pad), QPointF(pad, h-2*pad));// axis
212     painter.setRenderHint(QPainter::Antialiasing);
213     painter.setPen(Qt::NoPen);
214     painter.setBrush(Qt::black);
215     int rectW = (w-2*pad) / (dim-1); // width of the bar
216     FOR(i, dim)
217     {
218         // compute the position and size of the bar
219         int x = dim==1 ? w/2 : i * (w-2*pad) / dim + pad;
220         int y = (int)(val[i]/maxVal * (h-2*pad));
221         y = min(y, h-2*pad);
222         rectW = dim==1 ? rectW : (i+1) * (w-2*pad) / dim + pad -x;
223
224        // color it accordingly to its cluster or black for noise
225         int cid = pointId_to_clusterId[optics_list[i]];
226         if(cid == 0)
227         {
228             painter.setBrush(Qt::black);
229         }
230         else
231         {
232             float r = SampleColor[cid].red();
233             float g = SampleColor[cid].green();
234             float b = SampleColor[cid].blue();
235             painter.setBrush(QColor(r,g,b));
236         }
237         //let's draw it!
238         painter.drawRect(x,h-2*pad,rectW,-y);
239     }
240     // draw the depth threshold (even if it makes less sense for OPTICS WP)
241     painter.setPen(Qt::red);
242     painter.setBrush(Qt::NoBrush);
243     int y = (int)(params->depthSpin->value()/maxVal * (h-2*pad));
244     painter.drawLine(QPointF(0, h-2*pad-y), QPointF(w-2*pad, h-2*pad-y));
245     // draw the title
246     QFont font = painter.font();
247     font.setPointSize(8);
248     painter.setFont(font);
249     painter.setPen(Qt::gray);
250     painter.drawText(0,0,w,2*pad,Qt::AlignCenter, "Reachability-distance plot");
251     if(legend)  // I'm a legend
252     {
253
254         for(int i=0; i<9; i+=1)
255         {
256             //use a small trick to put the ticks on the scale
257             painter.drawText(0, h-(i*h) / 8, QString("_ %1").arg(i*maxVal/8.0));
258         }
259
260     }
261 }
262
263 void ClustDBSCAN::DrawModel(Canvas *canvas, QPainter &painter, Clusterer *clusterer)
264 {
265     ClustererDBSCAN * dbscan = dynamic_cast<ClustererDBSCAN*>(clusterer);
266     if(!dbscan) return;
267
268     // we start by making the painter paint things nicely
269     painter.setRenderHint(QPainter::Antialiasing);
270
271     // for every point in the current dataset
272     FOR(i, dbscan->_pointId_to_clusterId.size())
273         {
274         // we get the sample
275         Point sample = dbscan->pts[i];
276
277         // and we get the cluster id
278         int cid = dbscan->_pointId_to_clusterId[i];
279         // we get the point in canvas coordinates (2D pixel by pixel) corresponding to the sample (N-dimensional in R)
280
281         fvec pt;
282         pt.resize(sample.size(),0);
283         FOR(j, sample.size())
284         {
285             pt[j]=sample[j];
286         }
287
288         QPointF point = canvas->toCanvasCoords(pt);
289
290         // we combine together the contribution of each cluster to color the sample properly
291
292         float r = SampleColor[cid].red();
293         float g = SampleColor[cid].green();
294         float b = SampleColor[cid].blue();
295
296         // set the color of the brush and a black edge
297                 painter.setBrush(QColor(r,g,b));
298                 painter.setPen(Qt::black);
299         // and draw the sample itself
300                 painter.drawEllipse(point,5,5);
301         }
302
303     // we copy the data for zoom display
304     optics_list = dbscan->_optics_list;
305     reachability = dbscan->_reachability;
306     pointId_to_clusterId = dbscan->_pointId_to_clusterId;
307
308     // and plot the small graph
309     QPixmap pixmap(params->dendoGraph->size());
310     QBitmap bitmap(params->dendoGraph->size());
311     pixmap.setMask(bitmap);
312     pixmap.fill(Qt::transparent);
313     QPainter dPainter(&pixmap);
314     DrawDendogram(dPainter,false);
315     params->dendoGraph->setPixmap(pixmap);
316     params->zoomButton->setVisible(true); // now we are allowed to zoom
317 }
318
319 void ClustDBSCAN::SaveOptions(QSettings &settings)
320 {
321     // we save to the system registry each parameter value
322     settings.setValue("MinPts", params->minptsSpin->value());
323     settings.setValue("Eps", params->epsSpin->value());
324     settings.setValue("Metric", params->metricCombo->currentIndex());
325     settings.setValue("Type", params->typeCombo->currentIndex());
326     settings.setValue("Depth", params->depthSpin->value());
327  }
328
329 bool ClustDBSCAN::LoadOptions(QSettings &settings)
330 {
331     // we load the parameters from the registry so that when we launch the program we keep all values
332     if(settings.contains("MinPts")) params->minptsSpin->setValue(settings.value("MinPts").toFloat());
333     if(settings.contains("Eps")) params->epsSpin->setValue(settings.value("Eps").toFloat());
334     if(settings.contains("Metric")) params->metricCombo->setCurrentIndex(settings.value("Metric").toInt());
335     if(settings.contains("Type")) params->typeCombo->setCurrentIndex(settings.value("Type").toInt());
336     if(settings.contains("Depth")) params->depthSpin->setValue(settings.value("Depth").toFloat());
337     if(params->typeCombo->currentIndex()==0) // prepare also the interface by hidding unnecessary stuff
338     {
339         params->depthSpin->setVisible(false);
340         params->dendoGraph->setVisible(false);
341         params->label_10->setVisible(false);
342         params->zoomButton->setVisible(false);
343     }
344     return true;
345 }
346
347 void ClustDBSCAN::SaveParams(QTextStream &file)
348 {
349     // same as above but for files/string saving
350     file << "clusterOptions" << ":" << "MinPts" << " " << params->minptsSpin->value() << "\n";
351     file << "clusterOptions" << ":" << "Eps" << " " << params->epsSpin->value() << "\n";
352     file << "clusterOptions" << ":" << "Metric" << " " << params->metricCombo->currentIndex() << "\n";
353     file << "clusterOptions" << ":" << "Depth" << " " << params->depthSpin->value() << "\n";
354     file << "clusterOptions" << ":" << "Type" << " " << params->typeCombo->currentIndex() << "\n";
355 }
356
357 bool ClustDBSCAN::LoadParams(QString name, float value)
358 {
359     // same as above but for files/string saving
360     if(name.endsWith("MinPts")) params->minptsSpin->setValue(value);
361     if(name.endsWith("Eps")) params->epsSpin->setValue(value);
362     if(name.endsWith("Metric")) params->metricCombo->setCurrentIndex((int)value);
363     if(name.endsWith("Depth")) params->depthSpin->setValue(value);
364     if(name.endsWith("Type")) params->typeCombo->setCurrentIndex((int)value);
365     if(params->typeCombo->currentIndex()==0) // prepare also the interface by hidding unnecessary stuff
366     {
367         params->depthSpin->setVisible(false);
368         params->dendoGraph->setVisible(false);
369         params->label_10->setVisible(false);
370         params->zoomButton->setVisible(false);
371     }
372     return true;
373 }