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