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