From a1956a4ff879eb2d34c7a0ca448f48ee6e64ce99 Mon Sep 17 00:00:00 2001 From: Samuel Fadel Date: Tue, 1 Sep 2015 20:45:02 -0300 Subject: Improvements related to visual representation of distortions. - New continuous color scale class; - Improvements in signal handler for calculating distortions; - Implementation of the NP(k) measure. --- colorscale.h | 6 +- continuouscolorscale.cpp | 285 +++++++++++++++++++++++++++++++++++++++++++++++ continuouscolorscale.h | 14 +++ distortionmeasure.h | 12 ++ distortionobserver.cpp | 10 +- distortionobserver.h | 8 +- main.cpp | 19 ++-- main_view.qml | 57 ++++++++++ measures.cpp | 56 +++++++++- mp.h | 8 +- npdistortion.cpp | 7 +- npdistortion.h | 10 +- pm.pro | 3 + scatterplot.cpp | 20 ++-- scatterplot.h | 4 +- 15 files changed, 475 insertions(+), 44 deletions(-) create mode 100644 continuouscolorscale.cpp create mode 100644 continuouscolorscale.h create mode 100644 distortionmeasure.h diff --git a/colorscale.h b/colorscale.h index 6e1212d..6e65012 100644 --- a/colorscale.h +++ b/colorscale.h @@ -11,12 +11,12 @@ public: ColorScale(const QColor &firstColor, const QColor &lastColor); ColorScale(std::initializer_list colors); ColorScale(const QList &colors); - ~ColorScale(); + virtual ~ColorScale(); + virtual QColor color(qreal t) const; void setExtents(qreal min, qreal max); - QColor color(qreal t) const; -private: +protected: qreal m_min, m_max; QList m_colors; }; diff --git a/continuouscolorscale.cpp b/continuouscolorscale.cpp new file mode 100644 index 0000000..c12db80 --- /dev/null +++ b/continuouscolorscale.cpp @@ -0,0 +1,285 @@ +#include "continuouscolorscale.h" + +#include + +ContinuousColorScale::ContinuousColorScale() + : ColorScale{ + QColor( 0, 0, 0), + QColor( 35, 0, 0), + QColor( 52, 0, 0), + QColor( 60, 0, 0), + QColor( 63, 1, 0), + QColor( 64, 2, 0), + QColor( 68, 5, 0), + QColor( 69, 6, 0), + QColor( 72, 8, 0), + QColor( 74, 10, 0), + QColor( 77, 12, 0), + QColor( 78, 14, 0), + QColor( 81, 16, 0), + QColor( 83, 17, 0), + QColor( 85, 19, 0), + QColor( 86, 20, 0), + QColor( 89, 22, 0), + QColor( 91, 24, 0), + QColor( 92, 25, 0), + QColor( 94, 26, 0), + QColor( 95, 28, 0), + QColor( 98, 30, 0), + QColor(100, 31, 0), + QColor(102, 33, 0), + QColor(103, 34, 0), + QColor(105, 35, 0), + QColor(106, 36, 0), + QColor(108, 38, 0), + QColor(109, 39, 0), + QColor(111, 40, 0), + QColor(112, 42, 0), + QColor(114, 43, 0), + QColor(115, 44, 0), + QColor(117, 45, 0), + QColor(119, 47, 0), + QColor(119, 47, 0), + QColor(120, 48, 0), + QColor(122, 49, 0), + QColor(123, 51, 0), + QColor(125, 52, 0), + QColor(125, 52, 0), + QColor(126, 53, 0), + QColor(128, 54, 0), + QColor(129, 56, 0), + QColor(129, 56, 0), + QColor(131, 57, 0), + QColor(132, 58, 0), + QColor(134, 59, 0), + QColor(134, 59, 0), + QColor(136, 61, 0), + QColor(137, 62, 0), + QColor(137, 62, 0), + QColor(139, 63, 0), + QColor(139, 63, 0), + QColor(140, 65, 0), + QColor(142, 66, 0), + QColor(142, 66, 0), + QColor(143, 67, 0), + QColor(143, 67, 0), + QColor(145, 68, 0), + QColor(145, 68, 0), + QColor(146, 70, 0), + QColor(146, 70, 0), + QColor(148, 71, 0), + QColor(148, 71, 0), + QColor(149, 72, 0), + QColor(149, 72, 0), + QColor(151, 73, 0), + QColor(151, 73, 0), + QColor(153, 75, 0), + QColor(153, 75, 0), + QColor(154, 76, 0), + QColor(154, 76, 0), + QColor(154, 76, 0), + QColor(156, 77, 0), + QColor(156, 77, 0), + QColor(157, 79, 0), + QColor(157, 79, 0), + QColor(159, 80, 0), + QColor(159, 80, 0), + QColor(159, 80, 0), + QColor(160, 81, 0), + QColor(160, 81, 0), + QColor(162, 82, 0), + QColor(162, 82, 0), + QColor(163, 84, 0), + QColor(163, 84, 0), + QColor(165, 85, 0), + QColor(165, 85, 0), + QColor(166, 86, 0), + QColor(166, 86, 0), + QColor(166, 86, 0), + QColor(168, 87, 0), + QColor(168, 87, 0), + QColor(170, 89, 0), + QColor(170, 89, 0), + QColor(171, 90, 0), + QColor(171, 90, 0), + QColor(173, 91, 0), + QColor(173, 91, 0), + QColor(174, 93, 0), + QColor(174, 93, 0), + QColor(176, 94, 0), + QColor(176, 94, 0), + QColor(177, 95, 0), + QColor(177, 95, 0), + QColor(179, 96, 0), + QColor(179, 96, 0), + QColor(180, 98, 0), + QColor(182, 99, 0), + QColor(182, 99, 0), + QColor(183, 100, 0), + QColor(183, 100, 0), + QColor(185, 102, 0), + QColor(185, 102, 0), + QColor(187, 103, 0), + QColor(187, 103, 0), + QColor(188, 104, 0), + QColor(188, 104, 0), + QColor(190, 105, 0), + QColor(191, 107, 0), + QColor(191, 107, 0), + QColor(193, 108, 0), + QColor(193, 108, 0), + QColor(194, 109, 0), + QColor(196, 110, 0), + QColor(196, 110, 0), + QColor(197, 112, 0), + QColor(197, 112, 0), + QColor(199, 113, 0), + QColor(200, 114, 0), + QColor(200, 114, 0), + QColor(202, 116, 0), + QColor(202, 116, 0), + QColor(204, 117, 0), + QColor(205, 118, 0), + QColor(205, 118, 0), + QColor(207, 119, 0), + QColor(208, 121, 0), + QColor(208, 121, 0), + QColor(210, 122, 0), + QColor(211, 123, 0), + QColor(211, 123, 0), + QColor(213, 124, 0), + QColor(214, 126, 0), + QColor(214, 126, 0), + QColor(216, 127, 0), + QColor(217, 128, 0), + QColor(217, 128, 0), + QColor(219, 130, 0), + QColor(221, 131, 0), + QColor(221, 131, 0), + QColor(222, 132, 0), + QColor(224, 133, 0), + QColor(224, 133, 0), + QColor(225, 135, 0), + QColor(227, 136, 0), + QColor(227, 136, 0), + QColor(228, 137, 0), + QColor(230, 138, 0), + QColor(230, 138, 0), + QColor(231, 140, 0), + QColor(233, 141, 0), + QColor(233, 141, 0), + QColor(234, 142, 0), + QColor(236, 144, 0), + QColor(236, 144, 0), + QColor(238, 145, 0), + QColor(239, 146, 0), + QColor(241, 147, 0), + QColor(241, 147, 0), + QColor(242, 149, 0), + QColor(244, 150, 0), + QColor(244, 150, 0), + QColor(245, 151, 0), + QColor(247, 153, 0), + QColor(247, 153, 0), + QColor(248, 154, 0), + QColor(250, 155, 0), + QColor(251, 156, 0), + QColor(251, 156, 0), + QColor(253, 158, 0), + QColor(255, 159, 0), + QColor(255, 159, 0), + QColor(255, 160, 0), + QColor(255, 161, 0), + QColor(255, 163, 0), + QColor(255, 163, 0), + QColor(255, 164, 0), + QColor(255, 165, 0), + QColor(255, 167, 0), + QColor(255, 167, 0), + QColor(255, 168, 0), + QColor(255, 169, 0), + QColor(255, 169, 0), + QColor(255, 170, 0), + QColor(255, 172, 0), + QColor(255, 173, 0), + QColor(255, 173, 0), + QColor(255, 174, 0), + QColor(255, 175, 0), + QColor(255, 177, 0), + QColor(255, 178, 0), + QColor(255, 179, 0), + QColor(255, 181, 0), + QColor(255, 181, 0), + QColor(255, 182, 0), + QColor(255, 183, 0), + QColor(255, 184, 0), + QColor(255, 187, 7), + QColor(255, 188, 10), + QColor(255, 189, 14), + QColor(255, 191, 18), + QColor(255, 192, 21), + QColor(255, 193, 25), + QColor(255, 195, 29), + QColor(255, 197, 36), + QColor(255, 198, 40), + QColor(255, 200, 43), + QColor(255, 202, 51), + QColor(255, 204, 54), + QColor(255, 206, 61), + QColor(255, 207, 65), + QColor(255, 210, 72), + QColor(255, 211, 76), + QColor(255, 214, 83), + QColor(255, 216, 91), + QColor(255, 219, 98), + QColor(255, 221, 105), + QColor(255, 223, 109), + QColor(255, 225, 116), + QColor(255, 228, 123), + QColor(255, 232, 134), + QColor(255, 234, 142), + QColor(255, 237, 149), + QColor(255, 239, 156), + QColor(255, 240, 160), + QColor(255, 243, 167), + QColor(255, 246, 174), + QColor(255, 248, 182), + QColor(255, 249, 185), + QColor(255, 252, 193), + QColor(255, 253, 196), + QColor(255, 255, 204), + QColor(255, 255, 207), + QColor(255, 255, 211), + QColor(255, 255, 218), + QColor(255, 255, 222), + QColor(255, 255, 225), + QColor(255, 255, 229), + QColor(255, 255, 233), + QColor(255, 255, 236), + QColor(255, 255, 240), + QColor(255, 255, 244), + QColor(255, 255, 247), + QColor(255, 255, 255)} +{ +} + +QColor ContinuousColorScale::color(qreal t) const +{ + if (t < m_min || t > m_max) + return QColor(); + + // normalize t + t = (t - m_min) / (m_max - m_min); + + // find which colors in the scale are adjacent to ours + qreal step = 1.0 / m_colors.size(); + int i = (int) (t / step); + + if (i >= m_colors.size() - 1) + return m_colors.last(); + + // normalize t between the two colors + int j = i + 1; + t = (t - i*step) / (j*step - i*step); + return m_colors[i + round(t)]; +} diff --git a/continuouscolorscale.h b/continuouscolorscale.h new file mode 100644 index 0000000..1ca2092 --- /dev/null +++ b/continuouscolorscale.h @@ -0,0 +1,14 @@ +#ifndef CONTINUOUSCOLORSCALE_H +#define CONTINUOUSCOLORSCALE_H + +#include "colorscale.h" + +class ContinuousColorScale : public ColorScale +{ +public: + ContinuousColorScale(); + + QColor color(qreal t) const; +}; + +#endif // CONTINUOUSCOLORSCALE_H diff --git a/distortionmeasure.h b/distortionmeasure.h new file mode 100644 index 0000000..523f62b --- /dev/null +++ b/distortionmeasure.h @@ -0,0 +1,12 @@ +#ifndef DISTORTIONMEASURE_H +#define DISTORTIONMEASURE_H + +#include + +class DistortionMeasure +{ +public: + virtual arma::vec measure(const arma::mat &distA, const arma::mat &distB) = 0; +}; + +#endif // DISTORTIONMEASURE_H diff --git a/distortionobserver.cpp b/distortionobserver.cpp index a6c5ba4..29d7e6f 100644 --- a/distortionobserver.cpp +++ b/distortionobserver.cpp @@ -6,17 +6,23 @@ DistortionObserver::DistortionObserver(const arma::mat &X, const arma::uvec &sampleIndices) : m_X(X) , m_sampleIndices(sampleIndices) + , m_distortionMeasure(0) { m_distX = mp::dist(m_X); } -DistortionObserver::~DistortionObserver() +void DistortionObserver::setMeasure(DistortionMeasure *measure) { + m_distortionMeasure = measure; } void DistortionObserver::setMap(const arma::mat &Y) { - arma::vec measures = measureFunc(m_distX, mp::dist(Y)); + if (!m_distortionMeasure) { + return; + } + + arma::vec measures = m_distortionMeasure->measure(m_distX, mp::dist(Y)); if (m_Y.n_elem != 0) { emit mapChanged(measures - m_measures); diff --git a/distortionobserver.h b/distortionobserver.h index 9141d25..101c53d 100644 --- a/distortionobserver.h +++ b/distortionobserver.h @@ -4,12 +4,14 @@ #include #include +#include "distortionmeasure.h" + class DistortionObserver : public QObject { Q_OBJECT public: DistortionObserver(const arma::mat &X, const arma::uvec &sampleIndices); - virtual ~DistortionObserver(); + void setMeasure(DistortionMeasure *measure); signals: void mapChanged(const arma::vec &distortion); @@ -17,12 +19,10 @@ signals: 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; + DistortionMeasure *m_distortionMeasure; arma::vec m_measures; }; diff --git a/main.cpp b/main.cpp index a67ad33..233d22b 100644 --- a/main.cpp +++ b/main.cpp @@ -6,6 +6,7 @@ #include #include "mp.h" +#include "continuouscolorscale.h" #include "scatterplot.h" #include "interactionhandler.h" #include "distortionobserver.h" @@ -51,17 +52,21 @@ int main(int argc, char **argv) QObject::connect(&interactionHandler, SIGNAL(subsampleChanged(const arma::mat &)), plot, SLOT(setXY(const arma::mat &))); - // TODO: works; though it needs measures implementation - // std::unique_ptr 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 &))); + DistortionObserver distortionObs(X, sampleIndices); + std::unique_ptr distortionMeasure(new NPDistortion()); + distortionObs.setMeasure(distortionMeasure.get()); + QObject::connect(&interactionHandler, SIGNAL(subsampleChanged(const arma::mat &)), + &distortionObs, SLOT(setMap(const arma::mat &))); + QObject::connect(&distortionObs, SIGNAL(mapChanged(const arma::vec &)), + plot, SLOT(setColorData(const arma::vec &))); + ContinuousColorScale colorScale; + colorScale.setExtents(-1, 1); + plot->setColorScale(&colorScale); interactionHandler.setSubsample(Ys); // TODO: remove when proper measure coloring is done - plot->setColorData(labels); + // plot->setColorData(labels); return app.exec(); } diff --git a/main_view.qml b/main_view.qml index 5afe270..19b4efc 100644 --- a/main_view.qml +++ b/main_view.qml @@ -17,6 +17,26 @@ ApplicationWindow { MenuItem { action: saveDataAction } MenuItem { action: quitAction } } + + Menu { + title: "View" + MenuItem { + action: noneColorAction + exclusiveGroup: coloringGroup + } + MenuItem { + action: npColorAction + exclusiveGroup: coloringGroup + } + MenuItem { + action: stressColorAction + exclusiveGroup: coloringGroup + } + MenuItem { + action: silhouetteColorAction + exclusiveGroup: coloringGroup + } + } } Item { @@ -92,4 +112,41 @@ ApplicationWindow { shortcut: "Ctrl+D" onTriggered: console.log("Save data") } + + ExclusiveGroup { + id: coloringGroup + + Action { + id: noneColorAction + text: "None" + shortcut: "Shift+O" + checked: true + checkable: true + onTriggered: console.log("None") + } + + Action { + id: npColorAction + text: "Neighborhood Preservation" + shortcut: "Shift+N" + checkable: true + onTriggered: console.log("NP") + } + + Action { + id: stressColorAction + text: "Stress" + shortcut: "Shift+S" + checkable: true + onTriggered: console.log("Stress") + } + + Action { + id: silhouetteColorAction + text: "Silhouette" + shortcut: "Shift+T" + checkable: true + onTriggered: console.log("Silhouette") + } + } } diff --git a/measures.cpp b/measures.cpp index fe4077a..bee3515 100644 --- a/measures.cpp +++ b/measures.cpp @@ -1,11 +1,61 @@ #include "mp.h" +#include + +static +void knn(const arma::mat &dmat, arma::uword i, arma::uword k, arma::uvec &nn, arma::vec &dist) +{ + arma::uword n = dist.n_rows; + if (k > n) { + return; + } + + double dmax = arma::datum::inf; + nn.fill(i); + dist.fill(dmax); + for (arma::uword j = 0; j < n; j++) { + if (j == i) { + continue; + } + + if (dmat(i, j) < dmax) { + arma::uword l; + for (l = 0; dmat(i, j) > dist[l] && l < k; l++); + for (arma::uword m = l + 1; m < k; m++) { + nn[m] = nn[m - 1]; + dist[m] = dist[m - 1]; + } + + nn[l] = j; + dist[l] = dmat(i, j); + } + } +} + arma::vec mp::neighborhoodPreservation(const arma::mat &distA, const arma::mat &distB, - int k) + arma::uword k) { - // TODO - return arma::vec(distA.n_rows, arma::fill::zeros); + arma::uword n = distA.n_rows; + arma::vec np(n); + + for (arma::uword i = 0; i < n; i++) { + arma::uvec nnA(k); + arma::uvec nnB(k); + arma::vec dist(k); + + knn(distA, i, k, nnA, dist); + knn(distB, i, k, nnB, dist); + + std::sort(nnA.begin(), nnA.end()); + std::sort(nnB.begin(), nnB.end()); + + arma::uword l; + for (l = 0; nnA[l] == nnB[l] && l < k; l++); + np[i] = ((double) l) / k; + } + + return np; } arma::vec mp::silhouette(const arma::mat &distA, diff --git a/mp.h b/mp.h index a9ddd38..192f106 100644 --- a/mp.h +++ b/mp.h @@ -5,17 +5,17 @@ namespace mp { -// --- Distance-related +// 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, DistFunc dfunc = euclidean); -// --- Evaluation measures +// 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 neighborhoodPreservation(const arma::mat &distA, const arma::mat &distB, arma::uword k = 10); arma::vec silhouette(const arma::mat &distA, const arma::mat &distB, const arma::vec &labels); -// --- Techniques +// 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); diff --git a/npdistortion.cpp b/npdistortion.cpp index 71db85b..1994c74 100644 --- a/npdistortion.cpp +++ b/npdistortion.cpp @@ -2,13 +2,12 @@ #include "mp.h" -NPDistortion::NPDistortion(const arma::mat &X, const arma::uvec &sampleIndices, int k) - : DistortionObserver(X, sampleIndices) - , m_k(k) +NPDistortion::NPDistortion(int k) + : m_k(k) { } -arma::vec NPDistortion::measureFunc(const arma::mat &distA, const arma::mat &distB) +arma::vec NPDistortion::measure(const arma::mat &distA, const arma::mat &distB) { return mp::neighborhoodPreservation(distA, distB, m_k); } diff --git a/npdistortion.h b/npdistortion.h index a84f63a..a70616d 100644 --- a/npdistortion.h +++ b/npdistortion.h @@ -1,15 +1,13 @@ #ifndef NPDISTORTION_H #define NPDISTORTION_H -#include "distortionobserver.h" +#include "distortionmeasure.h" -class NPDistortion : public DistortionObserver +class NPDistortion : public DistortionMeasure { 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); + NPDistortion(int k = 10); + arma::vec measure(const arma::mat &distA, const arma::mat &distB); private: int m_k; diff --git a/pm.pro b/pm.pro index 8856347..de669cd 100644 --- a/pm.pro +++ b/pm.pro @@ -3,13 +3,16 @@ QT += qml quick widgets QMAKE_CXXFLAGS += -std=c++11 -fopenmp QMAKE_LIBS += -larmadillo -fopenmp HEADERS += colorscale.h \ + continuouscolorscale.h \ scatterplot.h \ interactionhandler.h \ distortionobserver.h \ + distortionmeasure.h \ npdistortion.h \ mp.h SOURCES += main.cpp \ colorscale.cpp \ + continuouscolorscale.cpp \ scatterplot.cpp \ interactionhandler.cpp \ distortionobserver.cpp \ diff --git a/scatterplot.cpp b/scatterplot.cpp index c43b625..0a252e1 100644 --- a/scatterplot.cpp +++ b/scatterplot.cpp @@ -2,7 +2,7 @@ #include -static const qreal GLYPH_OPACITY = 0.3; +static const qreal GLYPH_OPACITY = 0.4; static const qreal GLYPH_OPACITY_SELECTED = 1.0; static const QColor SELECTION_COLOR(QColor(128, 128, 128, 96)); static const int GLYPH_SIZE = 8; @@ -14,7 +14,8 @@ Scatterplot::Scatterplot(QQuickItem *parent) , m_currentInteractionState(INTERACTION_NONE) , m_shouldUpdateGeometry(false) , m_shouldUpdateMaterials(false) - , m_colorScale{ +{ + m_colorScale = new ColorScale{ QColor("#1f77b4"), QColor("#ff7f0e"), QColor("#2ca02c"), @@ -24,18 +25,20 @@ Scatterplot::Scatterplot(QQuickItem *parent) QColor("#e377c2"), QColor("#17becf"), QColor("#7f7f7f"), - } -{ + }; + setClip(true); setFlag(QQuickItem::ItemHasContents); } -void Scatterplot::setColorScale(const ColorScale &colorScale) +void Scatterplot::setColorScale(ColorScale *colorScale) { - m_colorScale = colorScale; + if (!colorScale) { + return; + } + m_colorScale = colorScale; if (m_colorData.n_elem > 0) { - m_colorScale.setExtents(m_colorData.min(), m_colorData.max()); updateMaterials(); } } @@ -62,7 +65,6 @@ void Scatterplot::setColorData(const arma::vec &colorData) return; m_colorData = colorData; - m_colorScale.setExtents(m_colorData.min(), m_colorData.max()); emit colorDataChanged(m_colorData); updateMaterials(); @@ -182,7 +184,7 @@ QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) } if (m_shouldUpdateMaterials) { QSGFlatColorMaterial *material = static_cast(glyphNode->material()); - material->setColor(m_colorScale.color(m_colorData[i])); + material->setColor(m_colorScale->color(m_colorData[i])); glyphNode->setMaterial(material); glyphNode->markDirty(QSGNode::DirtyMaterial); } diff --git a/scatterplot.h b/scatterplot.h index a115755..7ed58c8 100644 --- a/scatterplot.h +++ b/scatterplot.h @@ -12,7 +12,7 @@ class Scatterplot : public QQuickItem public: Scatterplot(QQuickItem *parent = 0); - void setColorScale(const ColorScale &colorScale); + void setColorScale(ColorScale *colorScale); signals: void xyChanged(const arma::mat &XY); @@ -58,7 +58,7 @@ private: arma::vec m_colorData; - ColorScale m_colorScale; + ColorScale *m_colorScale; }; #endif // SCATTERPLOT_H -- cgit v1.2.3