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