Merge branch 'devel' of git://gitorious.org/mldemos/mldemos into devel
[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     SetParams(clusterer, GetParams());
79 }
80
81 fvec ClustDBSCAN::GetParams()
82 {
83     double minpts = params->minptsSpin->value();
84     double eps = params->epsSpin->value();
85     int metric = params->metricCombo->currentIndex();
86     int type = params->typeCombo->currentIndex();
87     double depth = params->depthSpin->value();
88
89     int i=0;
90     fvec par(5);
91     par[i++] = minpts;
92     par[i++] = eps;
93     par[i++] = metric;
94     par[i++] = type;
95     par[i++] = depth;
96     return par;
97 }
98
99 void ClustDBSCAN::SetParams(Clusterer *clusterer, fvec parameters)
100 {
101     if(!clusterer) return;
102     ClustererDBSCAN * dbscan = dynamic_cast<ClustererDBSCAN *>(clusterer);
103     if(!dbscan) return;
104
105     int i=0;
106     float minpts = parameters.size() > i ? parameters[i] : 0; i++;
107     float eps = parameters.size() > i ? parameters[i] : 0; i++;
108     int metric = parameters.size() > i ? parameters[i] : 0; i++;
109     int type = parameters.size() > i ? parameters[i] : 0; i++;
110     float depth = parameters.size() > i ? parameters[i] : 0; i++;
111
112     dbscan->SetParams(minpts, eps, metric,depth,type);
113 }
114
115 void ClustDBSCAN::GetParameterList(std::vector<QString> &parameterNames,
116                              std::vector<QString> &parameterTypes,
117                              std::vector< std::vector<QString> > &parameterValues)
118 {
119     parameterNames.clear();
120     parameterTypes.clear();
121     parameterValues.clear();
122     parameterNames.push_back("Min Points");
123     parameterNames.push_back("Epsilon");
124     parameterNames.push_back("Metric Type");
125     parameterNames.push_back("Algorithm");
126     parameterNames.push_back("Depth.");
127     parameterTypes.push_back("Integer");
128     parameterTypes.push_back("Real");
129     parameterTypes.push_back("List");
130     parameterTypes.push_back("List");
131     parameterTypes.push_back("Real");
132     parameterValues.push_back(vector<QString>());
133     parameterValues.back().push_back("1");
134     parameterValues.back().push_back("99999");
135     parameterValues.push_back(vector<QString>());
136     parameterValues.back().push_back("0.00000000001f");
137     parameterValues.back().push_back("99999999.f");
138     parameterValues.push_back(vector<QString>());
139     parameterValues.back().push_back("Cosine");
140     parameterValues.back().push_back("Euclidean");
141     parameterValues.push_back(vector<QString>());
142     parameterValues.back().push_back("DBSCAN");
143     parameterValues.back().push_back("OPTICS");
144     parameterValues.back().push_back("OPTICS WP");
145     parameterValues.push_back(vector<QString>());
146     parameterValues.back().push_back("0.00000000001f");
147     parameterValues.back().push_back("99999999.f");
148 }
149
150 Clusterer *ClustDBSCAN::GetClusterer()
151 {
152     // we instanciate the algorithm object
153     ClustererDBSCAN *clusterer = new ClustererDBSCAN();
154     // we set its parameters
155     SetParams(clusterer);
156     // we return it to the main program
157         return clusterer;
158 }
159
160 void ClustDBSCAN::DrawInfo(Canvas *canvas, QPainter &painter, Clusterer *clusterer)
161 {
162     if(!canvas || !clusterer) return;
163     painter.setRenderHint(QPainter::Antialiasing);
164
165     ClustererDBSCAN * dbscan = dynamic_cast<ClustererDBSCAN*>(clusterer);
166     if(!dbscan) return;
167
168     if(dbscan->_type==0) // DBSCAN: we draw the core point with a black border
169     {
170         // for every point in the current dataset
171         FOR(i, dbscan->_pointId_to_clusterId.size())
172         {
173             if (dbscan->_core[i] || dbscan->_noise[i])
174             {
175                 // we get the sample
176                 Point sample = dbscan->pts[i];
177                 // transform it again in fvec from Point...
178                 fvec pt;
179                 pt.resize(sample.size(),0);
180                 FOR(j, sample.size())
181                 {
182                     pt[j]=sample[j];
183                 }
184
185                 //... and we get the point in canvas coordinates (2D pixel by pixel) corresponding to the sample (N-dimensional in R)
186                 QPointF point = canvas->toCanvasCoords(pt);
187
188                 if (dbscan->_core[i])
189                 {
190                     // core points get a black edge
191             // set a black edge
192                     painter.setBrush(Qt::NoBrush);
193                     painter.setPen(QPen(QColor(0,0,0),2));
194             // and draw the sample itself
195                     painter.drawEllipse(point,6,6);
196                  }
197                 else
198                 {
199                     // noise get a grey one
200                     // set the color of the edge
201                             painter.setBrush(Qt::NoBrush);
202                             painter.setPen(QPen(Qt::gray,2));
203                     // and draw the sample itself
204                             painter.drawEllipse(point,3,3);
205                 }
206             }
207         }
208     }
209     else // OPTICS: we draw the position in the ordered list
210     {
211         FOR(i, dbscan->_optics_list.size())
212             {
213             // we get the sample
214             Point sample = dbscan->pts[dbscan->_optics_list[i]];
215
216             //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)
217
218             fvec pt;
219             pt.resize(sample.size(),0);
220             FOR(j, sample.size())
221             {
222                 pt[j]=sample[j];
223             }
224
225             QPointF point = canvas->toCanvasCoords(pt);
226
227             // and we add a text indicating the position in the list
228             QFont font = painter.font();
229             font.setPointSize(8);
230             painter.setFont(font);
231             painter.setPen(Qt::gray); //not easy to choose the color without knowing what will be painted below
232             painter.drawText(point,QString("   %1").arg(i));
233         }
234     }
235 }
236
237 void ClustDBSCAN::DrawDendogram(QPainter &painter, bool legend) //draw the reachability plot
238 {
239     //what is the size of our painting area?
240     int w=painter.window().width();
241     int h=painter.window().height();
242     int pad = 0; // no padding between vertical bars
243
244     vector<double> val; // build the list we will actually plot
245
246     FOR(j,optics_list.size())
247     {
248         if (reachability[optics_list[j]] == -1)
249         {
250             val.push_back(params->epsSpin->value()); // if undefined, plot it at the maximum size
251         }
252         else
253         {
254             val.push_back(reachability[optics_list[j]]);
255         }
256     }
257
258     int dim = val.size();
259     float maxVal = params->epsSpin->value() * 1.1; // make the graph 10% bigger that the highest value
260     painter.setBrush(Qt::white);
261     painter.setPen(Qt::NoPen);
262     painter.drawRect(0,0,w,h); // plotting area
263     painter.setPen(Qt::gray);
264     painter.drawLine(QPointF(pad, h-2*pad), QPointF(w-pad, h-2*pad));// axis
265     painter.drawLine(QPointF(pad, pad), QPointF(pad, h-2*pad));// axis
266     painter.setRenderHint(QPainter::Antialiasing);
267     painter.setPen(Qt::NoPen);
268     painter.setBrush(Qt::black);
269     int rectW = (w-2*pad) / (dim-1); // width of the bar
270     FOR(i, dim)
271     {
272         // compute the position and size of the bar
273         int x = dim==1 ? w/2 : i * (w-2*pad) / dim + pad;
274         int y = (int)(val[i]/maxVal * (h-2*pad));
275         y = min(y, h-2*pad);
276         rectW = dim==1 ? rectW : (i+1) * (w-2*pad) / dim + pad -x;
277
278        // color it accordingly to its cluster or black for noise
279         int cid = pointId_to_clusterId[optics_list[i]];
280         if(cid == 0)
281         {
282             painter.setBrush(Qt::black);
283         }
284         else
285         {
286             float r = SampleColor[cid].red();
287             float g = SampleColor[cid].green();
288             float b = SampleColor[cid].blue();
289             painter.setBrush(QColor(r,g,b));
290         }
291         //let's draw it!
292         painter.drawRect(x,h-2*pad,rectW,-y);
293     }
294     // draw the depth threshold (even if it makes less sense for OPTICS WP)
295     painter.setPen(Qt::red);
296     painter.setBrush(Qt::NoBrush);
297     int y = (int)(params->depthSpin->value()/maxVal * (h-2*pad));
298     painter.drawLine(QPointF(0, h-2*pad-y), QPointF(w-2*pad, h-2*pad-y));
299     // draw the title
300     QFont font = painter.font();
301     font.setPointSize(8);
302     painter.setFont(font);
303     painter.setPen(Qt::gray);
304     painter.drawText(0,0,w,2*pad,Qt::AlignCenter, "Reachability-distance plot");
305     if(legend)  // I'm a legend
306     {
307
308         for(int i=0; i<9; i+=1)
309         {
310             //use a small trick to put the ticks on the scale
311             painter.drawText(0, h-(i*h) / 8, QString("_ %1").arg(i*maxVal/8.0));
312         }
313
314     }
315 }
316
317 void ClustDBSCAN::DrawModel(Canvas *canvas, QPainter &painter, Clusterer *clusterer)
318 {
319     ClustererDBSCAN * dbscan = dynamic_cast<ClustererDBSCAN*>(clusterer);
320     if(!dbscan) return;
321
322     // we start by making the painter paint things nicely
323     painter.setRenderHint(QPainter::Antialiasing);
324
325     // for every point in the current dataset
326     FOR(i, dbscan->_pointId_to_clusterId.size())
327         {
328         // we get the sample
329         Point sample = dbscan->pts[i];
330
331         // and we get the cluster id
332         int cid = dbscan->_pointId_to_clusterId[i];
333         // we get the point in canvas coordinates (2D pixel by pixel) corresponding to the sample (N-dimensional in R)
334
335         fvec pt;
336         pt.resize(sample.size(),0);
337         FOR(j, sample.size())
338         {
339             pt[j]=sample[j];
340         }
341
342         QPointF point = canvas->toCanvasCoords(pt);
343
344         // we combine together the contribution of each cluster to color the sample properly
345
346         float r = SampleColor[cid].red();
347         float g = SampleColor[cid].green();
348         float b = SampleColor[cid].blue();
349
350         // set the color of the brush and a black edge
351                 painter.setBrush(QColor(r,g,b));
352                 painter.setPen(Qt::black);
353         // and draw the sample itself
354                 painter.drawEllipse(point,5,5);
355         }
356
357     // we copy the data for zoom display
358     optics_list = dbscan->_optics_list;
359     reachability = dbscan->_reachability;
360     pointId_to_clusterId = dbscan->_pointId_to_clusterId;
361
362     // and plot the small graph
363     QPixmap pixmap(params->dendoGraph->size());
364     QBitmap bitmap(params->dendoGraph->size());
365     pixmap.setMask(bitmap);
366     pixmap.fill(Qt::transparent);
367     QPainter dPainter(&pixmap);
368     DrawDendogram(dPainter,false);
369     params->dendoGraph->setPixmap(pixmap);
370     params->zoomButton->setVisible(true); // now we are allowed to zoom
371 }
372
373 void ClustDBSCAN::SaveOptions(QSettings &settings)
374 {
375     // we save to the system registry each parameter value
376     settings.setValue("MinPts", params->minptsSpin->value());
377     settings.setValue("Eps", params->epsSpin->value());
378     settings.setValue("Metric", params->metricCombo->currentIndex());
379     settings.setValue("Type", params->typeCombo->currentIndex());
380     settings.setValue("Depth", params->depthSpin->value());
381  }
382
383 bool ClustDBSCAN::LoadOptions(QSettings &settings)
384 {
385     // we load the parameters from the registry so that when we launch the program we keep all values
386     if(settings.contains("MinPts")) params->minptsSpin->setValue(settings.value("MinPts").toFloat());
387     if(settings.contains("Eps")) params->epsSpin->setValue(settings.value("Eps").toFloat());
388     if(settings.contains("Metric")) params->metricCombo->setCurrentIndex(settings.value("Metric").toInt());
389     if(settings.contains("Type")) params->typeCombo->setCurrentIndex(settings.value("Type").toInt());
390     if(settings.contains("Depth")) params->depthSpin->setValue(settings.value("Depth").toFloat());
391     if(params->typeCombo->currentIndex()==0) // prepare also the interface by hidding unnecessary stuff
392     {
393         params->depthSpin->setVisible(false);
394         params->dendoGraph->setVisible(false);
395         params->label_10->setVisible(false);
396         params->zoomButton->setVisible(false);
397     }
398     return true;
399 }
400
401 void ClustDBSCAN::SaveParams(QTextStream &file)
402 {
403     // same as above but for files/string saving
404     file << "clusterOptions" << ":" << "MinPts" << " " << params->minptsSpin->value() << "\n";
405     file << "clusterOptions" << ":" << "Eps" << " " << params->epsSpin->value() << "\n";
406     file << "clusterOptions" << ":" << "Metric" << " " << params->metricCombo->currentIndex() << "\n";
407     file << "clusterOptions" << ":" << "Depth" << " " << params->depthSpin->value() << "\n";
408     file << "clusterOptions" << ":" << "Type" << " " << params->typeCombo->currentIndex() << "\n";
409 }
410
411 bool ClustDBSCAN::LoadParams(QString name, float value)
412 {
413     // same as above but for files/string saving
414     if(name.endsWith("MinPts")) params->minptsSpin->setValue(value);
415     if(name.endsWith("Eps")) params->epsSpin->setValue(value);
416     if(name.endsWith("Metric")) params->metricCombo->setCurrentIndex((int)value);
417     if(name.endsWith("Depth")) params->depthSpin->setValue(value);
418     if(name.endsWith("Type")) params->typeCombo->setCurrentIndex((int)value);
419     if(params->typeCombo->currentIndex()==0) // prepare also the interface by hidding unnecessary stuff
420     {
421         params->depthSpin->setVisible(false);
422         params->dendoGraph->setVisible(false);
423         params->label_10->setVisible(false);
424         params->zoomButton->setVisible(false);
425     }
426     return true;
427 }