aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--dist.cpp4
-rw-r--r--distortionobserver.cpp27
-rw-r--r--distortionobserver.h29
-rw-r--r--interactionhandler.cpp5
-rw-r--r--interactionhandler.h6
-rw-r--r--main.cpp22
-rw-r--r--measures.cpp17
-rw-r--r--mp.h15
-rw-r--r--npdistortion.cpp14
-rw-r--r--npdistortion.h18
-rw-r--r--pm.pro5
-rw-r--r--scatterplot.cpp142
-rw-r--r--scatterplot.h23
13 files changed, 255 insertions, 72 deletions
diff --git a/dist.cpp b/dist.cpp
index aaa2167..a5620ad 100644
--- a/dist.cpp
+++ b/dist.cpp
@@ -5,14 +5,14 @@ double mp::euclidean(const arma::rowvec &x1, const arma::rowvec &x2)
return arma::norm(x1 - x2, 2);
}
-arma::mat mp::dist(const arma::mat &X, double (*distCalc)(const arma::rowvec &, const arma::rowvec &))
+arma::mat mp::dist(const arma::mat &X, mp::DistFunc dfunc)
{
arma::uword n = X.n_rows;
arma::mat D(n, n, arma::fill::zeros);
for (arma::uword i = 0; i < n; i++)
for (arma::uword j = 0; j < i; j++) {
- D(i, j) = distCalc(X.row(i), X.row(j));
+ D(i, j) = dfunc(X.row(i), X.row(j));
D(j, i) = D(i, j);
}
diff --git a/distortionobserver.cpp b/distortionobserver.cpp
new file mode 100644
index 0000000..a6c5ba4
--- /dev/null
+++ b/distortionobserver.cpp
@@ -0,0 +1,27 @@
+#include "distortionobserver.h"
+
+#include "mp.h"
+
+DistortionObserver::DistortionObserver(const arma::mat &X,
+ const arma::uvec &sampleIndices)
+ : m_X(X)
+ , m_sampleIndices(sampleIndices)
+{
+ m_distX = mp::dist(m_X);
+}
+
+DistortionObserver::~DistortionObserver()
+{
+}
+
+void DistortionObserver::setMap(const arma::mat &Y)
+{
+ arma::vec measures = measureFunc(m_distX, mp::dist(Y));
+
+ if (m_Y.n_elem != 0) {
+ emit mapChanged(measures - m_measures);
+ } else {
+ m_Y = Y;
+ m_measures = measures;
+ }
+}
diff --git a/distortionobserver.h b/distortionobserver.h
new file mode 100644
index 0000000..9141d25
--- /dev/null
+++ b/distortionobserver.h
@@ -0,0 +1,29 @@
+#ifndef DISTORTIONOBSERVER_H
+#define DISTORTIONOBSERVER_H
+
+#include <QObject>
+#include <armadillo>
+
+class DistortionObserver : public QObject
+{
+ Q_OBJECT
+public:
+ DistortionObserver(const arma::mat &X, const arma::uvec &sampleIndices);
+ virtual ~DistortionObserver();
+
+signals:
+ void mapChanged(const arma::vec &distortion);
+
+public slots:
+ void setMap(const arma::mat &Y);
+
+protected:
+ virtual arma::vec measureFunc(const arma::mat &distA, const arma::mat &distB) = 0;
+
+private:
+ arma::mat m_X, m_Y, m_distX;
+ arma::uvec m_sampleIndices;
+ arma::vec m_measures;
+};
+
+#endif // DISTORTIONOBSERVER_H
diff --git a/interactionhandler.cpp b/interactionhandler.cpp
index 50d0653..b79fdd8 100644
--- a/interactionhandler.cpp
+++ b/interactionhandler.cpp
@@ -3,15 +3,12 @@
#include "mp.h"
InteractionHandler::InteractionHandler(const arma::mat &X,
- const arma::vec &labels,
const arma::uvec &sampleIndices)
: m_X(X)
- , m_Y(X.n_rows, 3)
- , m_labels(labels)
+ , m_Y(X.n_rows, 2)
, m_sampleIndices(sampleIndices)
, m_technique(TECHNIQUE_LAMP)
{
- m_Y.col(2) = m_labels;
}
void InteractionHandler::setSubsample(const arma::mat &Ys)
diff --git a/interactionhandler.h b/interactionhandler.h
index 5468dab..0104d65 100644
--- a/interactionhandler.h
+++ b/interactionhandler.h
@@ -1,10 +1,9 @@
#ifndef INTERACTIONHANDLER_H
#define INTERACTIONHANDLER_H
+#include <QObject>
#include <armadillo>
-#include "scatterplot.h"
-
class InteractionHandler : public QObject
{
Q_OBJECT
@@ -15,7 +14,7 @@ public:
TECHNIQUE_LSP
};
- InteractionHandler(const arma::mat &X, const arma::vec &labels, const arma::uvec &sampleIndices);
+ InteractionHandler(const arma::mat &X, const arma::uvec &sampleIndices);
signals:
void subsampleChanged(const arma::mat &Y);
@@ -25,7 +24,6 @@ public slots:
private:
arma::mat m_X, m_Y;
- arma::vec m_labels;
arma::uvec m_sampleIndices;
InteractiveTechnique m_technique;
};
diff --git a/main.cpp b/main.cpp
index 84579b5..70499e7 100644
--- a/main.cpp
+++ b/main.cpp
@@ -8,6 +8,8 @@
#include "mp.h"
#include "scatterplot.h"
#include "interactionhandler.h"
+#include "distortionobserver.h"
+#include "npdistortion.h"
int main(int argc, char **argv)
{
@@ -35,21 +37,27 @@ int main(int argc, char **argv)
arma::uvec sampleIndices = arma::randi<arma::uvec>(subsampleSize, arma::distr_param(0, n-1));
arma::mat Ys(subsampleSize, 2, arma::fill::randn);
Ys = mp::forceScheme(mp::dist(X.rows(sampleIndices)), Ys);
- arma::mat subsampleData(subsampleSize, 3);
- subsampleData.cols(0, 1) = Ys;
- subsampleData.col(2) = labels(sampleIndices);
Scatterplot *subsamplePlot = engine.rootObjects()[0]->findChild<Scatterplot *>("subsamplePlot");
subsamplePlot->setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton | Qt::RightButton);
- subsamplePlot->setData(subsampleData);
+ subsamplePlot->setXY(Ys);
+ subsamplePlot->setColorData(labels(sampleIndices));
Scatterplot *plot = engine.rootObjects()[0]->findChild<Scatterplot *>("plot");
// connect both plots through interaction handler
- InteractionHandler interactionHandler(X, labels, sampleIndices);
- QObject::connect(subsamplePlot, SIGNAL(dataChanged(const arma::mat &)),
+ InteractionHandler interactionHandler(X, sampleIndices);
+ QObject::connect(subsamplePlot, SIGNAL(xyChanged(const arma::mat &)),
&interactionHandler, SLOT(setSubsample(const arma::mat &)));
QObject::connect(&interactionHandler, SIGNAL(subsampleChanged(const arma::mat &)),
- plot, SLOT(setData(const arma::mat &)));
+ plot, SLOT(setXY(const arma::mat &)));
+
+ // TODO: works; though it needs measures implementation
+ // std::unique_ptr<DistortionObserver> distortionObs(new NPDistortion(X, sampleIndices));
+ // QObject::connect(&interactionHandler, SIGNAL(subsampleChanged(const arma::mat &)),
+ // distortionObs.get(), SLOT(setMap(const arma::mat &)));
+ // QObject::connect(distortionObs.get(), SIGNAL(mapChanged(const arma::vec &)),
+ // plot, SLOT(setColorData(const arma::vec &)));
+
interactionHandler.setSubsample(Ys);
return app.exec();
diff --git a/measures.cpp b/measures.cpp
new file mode 100644
index 0000000..fe4077a
--- /dev/null
+++ b/measures.cpp
@@ -0,0 +1,17 @@
+#include "mp.h"
+
+arma::vec mp::neighborhoodPreservation(const arma::mat &distA,
+ const arma::mat &distB,
+ int k)
+{
+ // TODO
+ return arma::vec(distA.n_rows, arma::fill::zeros);
+}
+
+arma::vec mp::silhouette(const arma::mat &distA,
+ const arma::mat &distB,
+ const arma::vec &labels)
+{
+ // TODO
+ return arma::vec(distA.n_rows, arma::fill::zeros);
+}
diff --git a/mp.h b/mp.h
index 7d6b535..a9ddd38 100644
--- a/mp.h
+++ b/mp.h
@@ -1,10 +1,21 @@
+#ifndef MP_H
+#define MP_H
+
#include <armadillo>
namespace mp {
+// --- Distance-related
+typedef double (*DistFunc)(const arma::rowvec &, const arma::rowvec &);
double euclidean(const arma::rowvec &x1, const arma::rowvec &x2);
-arma::mat dist(const arma::mat &X, double (*distCalc)(const arma::rowvec &, const arma::rowvec &) = euclidean);
+arma::mat dist(const arma::mat &X, DistFunc dfunc = euclidean);
+
+// --- Evaluation measures
+typedef arma::vec (*MeasureFunc)(const arma::mat &distA, const arma::mat &distB);
+arma::vec neighborhoodPreservation(const arma::mat &distA, const arma::mat &distB, int k = 10);
+arma::vec silhouette(const arma::mat &distA, const arma::mat &distB, const arma::vec &labels);
+// --- Techniques
arma::mat lamp(const arma::mat &X, const arma::uvec &sampleIndices, const arma::mat &Ys);
void lamp(const arma::mat &X, const arma::uvec &sampleIndices, const arma::mat &Ys, arma::mat &Y);
@@ -14,3 +25,5 @@ arma::mat tSNE(const arma::mat &X, arma::uword k = 2, double perplexity = 30, ar
void tSNE(const arma::mat &X, arma::mat &Y, double perplexity = 30, arma::uword nIter = 1000);
} // namespace mp
+
+#endif // MP_H
diff --git a/npdistortion.cpp b/npdistortion.cpp
new file mode 100644
index 0000000..71db85b
--- /dev/null
+++ b/npdistortion.cpp
@@ -0,0 +1,14 @@
+#include "npdistortion.h"
+
+#include "mp.h"
+
+NPDistortion::NPDistortion(const arma::mat &X, const arma::uvec &sampleIndices, int k)
+ : DistortionObserver(X, sampleIndices)
+ , m_k(k)
+{
+}
+
+arma::vec NPDistortion::measureFunc(const arma::mat &distA, const arma::mat &distB)
+{
+ return mp::neighborhoodPreservation(distA, distB, m_k);
+}
diff --git a/npdistortion.h b/npdistortion.h
new file mode 100644
index 0000000..a84f63a
--- /dev/null
+++ b/npdistortion.h
@@ -0,0 +1,18 @@
+#ifndef NPDISTORTION_H
+#define NPDISTORTION_H
+
+#include "distortionobserver.h"
+
+class NPDistortion : public DistortionObserver
+{
+public:
+ NPDistortion(const arma::mat &X, const arma::uvec &sampleIndices, int k = 10);
+
+protected:
+ arma::vec measureFunc(const arma::mat &distA, const arma::mat &distB);
+
+private:
+ int m_k;
+};
+
+#endif // NPDISTORTION_H
diff --git a/pm.pro b/pm.pro
index 3ca2aaa..8856347 100644
--- a/pm.pro
+++ b/pm.pro
@@ -5,14 +5,19 @@ QMAKE_LIBS += -larmadillo -fopenmp
HEADERS += colorscale.h \
scatterplot.h \
interactionhandler.h \
+ distortionobserver.h \
+ npdistortion.h \
mp.h
SOURCES += main.cpp \
colorscale.cpp \
scatterplot.cpp \
interactionhandler.cpp \
+ distortionobserver.cpp \
+ npdistortion.cpp \
lamp.cpp \
forceScheme.cpp \
tsne.cpp \
+ measures.cpp \
dist.cpp
RESOURCES += pm.qrc
diff --git a/scatterplot.cpp b/scatterplot.cpp
index 0c2fbc1..47066fa 100644
--- a/scatterplot.cpp
+++ b/scatterplot.cpp
@@ -11,6 +11,8 @@ static const float PI = 3.1415f;
Scatterplot::Scatterplot(QQuickItem *parent)
: QQuickItem(parent)
+ , m_shouldUpdateGeometry(false)
+ , m_shouldUpdateMaterials(false)
, m_colorScale{
QColor("#1f77b4"),
QColor("#ff7f0e"),
@@ -27,24 +29,53 @@ Scatterplot::Scatterplot(QQuickItem *parent)
setFlag(QQuickItem::ItemHasContents);
}
-Scatterplot::~Scatterplot()
+void Scatterplot::setColorScale(const ColorScale &colorScale)
{
+ m_colorScale = colorScale;
+
+ if (m_colorData.n_elem > 0) {
+ m_colorScale.setExtents(m_colorData.min(), m_colorData.max());
+ updateMaterials();
+ }
}
-void Scatterplot::setData(const arma::mat &data)
+void Scatterplot::setXY(const arma::mat &xy)
{
- if (data.n_cols != 3)
+ if (xy.n_cols != 2)
return;
- m_data = data;
- m_xmin = data.col(0).min();
- m_xmax = data.col(0).max();
- m_ymin = data.col(1).min();
- m_ymax = data.col(1).max();
-
- m_colorScale.setExtents(m_data.col(2).min(), m_data.col(2).max());
+ m_xy = xy;
+ m_xmin = xy.col(0).min();
+ m_xmax = xy.col(0).max();
+ m_ymin = xy.col(1).min();
+ m_ymax = xy.col(1).max();
m_selectedGlyphs.clear();
+ emit xyChanged(m_xy);
+
+ updateGeometry();
+}
+
+void Scatterplot::setColorData(const arma::vec &colorData)
+{
+ if (colorData.n_elem != m_xy.n_rows)
+ return;
+
+ m_colorData = colorData;
+ m_colorScale.setExtents(m_colorData.min(), m_colorData.max());
+ emit colorDataChanged(m_colorData);
+
+ updateMaterials();
+}
+void Scatterplot::updateGeometry()
+{
+ m_shouldUpdateGeometry = true;
+ update();
+}
+
+void Scatterplot::updateMaterials()
+{
+ m_shouldUpdateMaterials = true;
update();
}
@@ -84,11 +115,12 @@ inline float Scatterplot::fromDataYToScreenY(float y)
return PADDING + (y - m_ymin) / (m_ymax - m_ymin) * (height() - 2*PADDING);
}
-QSGNode *Scatterplot::newGlyphNodeTree() {
+QSGNode *Scatterplot::createGlyphNodeTree()
+{
QSGNode *node = new QSGNode;
int vertexCount = calculateCircleVertexCount(GLYPH_SIZE / 2);
- for (arma::uword i = 0; i < m_data.n_rows; i++) {
+ for (arma::uword i = 0; i < m_xy.n_rows; i++) {
QSGGeometryNode *glyphNode = new QSGGeometryNode;
QSGGeometry *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), vertexCount);
@@ -97,7 +129,7 @@ QSGNode *Scatterplot::newGlyphNodeTree() {
glyphNode->setFlag(QSGNode::OwnsGeometry);
QSGFlatColorMaterial *material = new QSGFlatColorMaterial;
- material->setColor(m_colorScale.color(m_data(i, 2)));
+ material->setColor(QColor());
glyphNode->setMaterial(material);
glyphNode->setFlag(QSGNode::OwnsMaterial);
@@ -112,45 +144,56 @@ QSGNode *Scatterplot::newGlyphNodeTree() {
QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
- if (m_data.n_rows < 1)
+ if (m_xy.n_rows < 1)
return 0;
- qreal x, y, xt, yt, moveTranslationF;
+ qreal x, y, tx, ty, moveTranslationF;
QSGNode *root = 0;
if (!oldNode) {
root = new QSGNode;
- root->appendChildNode(newGlyphNodeTree());
- } else {
+ root->appendChildNode(createGlyphNodeTree());
+ } else
root = oldNode;
- }
- if (m_currentState != INTERACTION_MOVING)
- xt = yt = 0;
- else {
- xt = m_dragCurrentPos.x() - m_dragOriginPos.x();
- yt = m_dragCurrentPos.y() - m_dragOriginPos.y();
- }
+ if (m_currentState == INTERACTION_MOVING) {
+ tx = m_dragCurrentPos.x() - m_dragOriginPos.x();
+ ty = m_dragCurrentPos.y() - m_dragOriginPos.y();
+ } else
+ tx = ty = 0;
QSGNode *node = root->firstChild()->firstChild();
- for (arma::uword i = 0; i < m_data.n_rows; i++) {
- arma::rowvec row = m_data.row(i);
+ for (arma::uword i = 0; i < m_xy.n_rows; i++) {
+ arma::rowvec row = m_xy.row(i);
bool isSelected = m_selectedGlyphs.contains(i);
QSGOpacityNode *glyphOpacityNode = static_cast<QSGOpacityNode *>(node);
glyphOpacityNode->setOpacity(isSelected ? GLYPH_OPACITY_SELECTED : GLYPH_OPACITY);
QSGGeometryNode *glyphNode = static_cast<QSGGeometryNode *>(node->firstChild());
- QSGGeometry *geometry = glyphNode->geometry();
- moveTranslationF = isSelected ? 1.0 : 0.0;
- x = fromDataXToScreenX(row[0]) + xt * moveTranslationF;
- y = fromDataYToScreenY(row[1]) + yt * moveTranslationF;
- updateCircleGeometry(geometry, GLYPH_SIZE, x, y);
- glyphNode->markDirty(QSGNode::DirtyGeometry);
+ if (m_shouldUpdateGeometry) {
+ QSGGeometry *geometry = glyphNode->geometry();
+ moveTranslationF = isSelected ? 1.0 : 0.0;
+ x = fromDataXToScreenX(row[0]) + tx * moveTranslationF;
+ y = fromDataYToScreenY(row[1]) + ty * moveTranslationF;
+ updateCircleGeometry(geometry, GLYPH_SIZE, x, y);
+ glyphNode->markDirty(QSGNode::DirtyGeometry);
+ }
+ if (m_shouldUpdateMaterials) {
+ QSGFlatColorMaterial *material = static_cast<QSGFlatColorMaterial *>(glyphNode->material());
+ material->setColor(m_colorScale.color(m_colorData[i]));
+ glyphNode->setMaterial(material);
+ glyphNode->markDirty(QSGNode::DirtyMaterial);
+ }
node = node->nextSibling();
}
+ if (m_shouldUpdateGeometry)
+ m_shouldUpdateGeometry = false;
+ if (m_shouldUpdateMaterials)
+ m_shouldUpdateMaterials = false;
+
// Draw selection
if (m_currentState == INTERACTION_SELECTING) {
QSGSimpleRectNode *selectionNode = 0;
@@ -200,8 +243,8 @@ void Scatterplot::mouseMoveEvent(QMouseEvent *event)
break;
case INTERACTION_MOVING:
m_dragCurrentPos = event->localPos();
- updateData();
- update();
+ applyManipulation();
+ updateGeometry();
m_dragOriginPos = m_dragCurrentPos;
break;
case INTERACTION_NONE:
@@ -225,7 +268,7 @@ void Scatterplot::mouseReleaseEvent(QMouseEvent *event)
case INTERACTION_MOVING:
m_currentState = INTERACTION_SELECTED;
- update();
+ updateGeometry();
break;
case INTERACTION_NONE:
case INTERACTION_SELECTED:
@@ -242,8 +285,8 @@ bool Scatterplot::selectGlyphs(bool mergeSelection)
QRectF selectionRect(m_dragOriginPos, m_dragCurrentPos);
bool anySelected = false;
- for (arma::uword i = 0; i < m_data.n_rows; i++) {
- arma::rowvec row = m_data.row(i);
+ for (arma::uword i = 0; i < m_xy.n_rows; i++) {
+ arma::rowvec row = m_xy.row(i);
x = fromDataXToScreenX(row[0]);
y = fromDataYToScreenY(row[1]);
@@ -257,20 +300,23 @@ bool Scatterplot::selectGlyphs(bool mergeSelection)
return anySelected;
}
-void Scatterplot::updateData()
+void Scatterplot::applyManipulation()
{
- float xt = m_dragCurrentPos.x() - m_dragOriginPos.x();
- float yt = m_dragCurrentPos.y() - m_dragOriginPos.y();
+ float tx = m_dragCurrentPos.x() - m_dragOriginPos.x();
+ float ty = m_dragCurrentPos.y() - m_dragOriginPos.y();
+
+ tx /= (width() - PADDING);
+ ty /= (height() - PADDING);
+
+ float x_extent = m_xmax - m_xmin;
+ float y_extent = m_ymax - m_ymin;
- xt /= (width() - PADDING);
- yt /= (height() - PADDING);
for (auto it = m_selectedGlyphs.cbegin(); it != m_selectedGlyphs.cend(); it++) {
- arma::rowvec row = m_data.row(*it);
- row[0] = ((row[0] - m_xmin) / (m_xmax - m_xmin) + xt) * (m_xmax - m_xmin) + m_xmin;
- row[1] = ((row[1] - m_ymin) / (m_ymax - m_ymin) + yt) * (m_ymax - m_ymin) + m_ymin;
- m_data.row(*it) = row;
+ arma::rowvec row = m_xy.row(*it);
+ row[0] = ((row[0] - m_xmin) / x_extent + tx) * x_extent + m_xmin;
+ row[1] = ((row[1] - m_ymin) / y_extent + ty) * y_extent + m_ymin;
+ m_xy.row(*it) = row;
}
- // does not send last column (labels)
- emit dataChanged(m_data.cols(0, m_data.n_cols - 2));
+ emit xyChanged(m_xy);
}
diff --git a/scatterplot.h b/scatterplot.h
index a155daf..a942217 100644
--- a/scatterplot.h
+++ b/scatterplot.h
@@ -11,13 +11,16 @@ class Scatterplot : public QQuickItem
Q_OBJECT
public:
Scatterplot(QQuickItem *parent = 0);
- ~Scatterplot();
+
+ void setColorScale(const ColorScale &colorScale);
signals:
- void dataChanged(const arma::mat &data);
+ void xyChanged(const arma::mat &XY);
+ void colorDataChanged(const arma::vec &colorData);
public slots:
- void setData(const arma::mat &data);
+ void setXY(const arma::mat &xy);
+ void setColorData(const arma::vec &colorData);
protected:
QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *);
@@ -26,16 +29,24 @@ protected:
void mouseReleaseEvent(QMouseEvent *event);
private:
- QSGNode *newGlyphNodeTree();
+ QSGNode *createGlyphNodeTree();
bool selectGlyphs(bool mergeSelection);
- void updateData();
float fromDataXToScreenX(float x);
float fromDataYToScreenY(float y);
- arma::mat m_data;
+ void applyManipulation();
+
+ void updateGeometry();
+ void updateMaterials();
+
+ arma::mat m_xy;
float m_xmin, m_xmax, m_ymin, m_ymax;
+ bool m_shouldUpdateGeometry, m_shouldUpdateMaterials;
+
+ arma::vec m_colorData;
+
ColorScale m_colorScale;
enum InteractionState {