From 6a7b60784a44013156382843a5e72af272810674 Mon Sep 17 00:00:00 2001 From: Samuel Fadel Date: Fri, 22 May 2015 18:40:07 -0300 Subject: Improvements to UI and performance. --- interactionhandler.cpp | 11 ++++----- interactionhandler.h | 2 +- lamp.cpp | 34 +++++++++++++++----------- main_view.qml | 6 +---- mp.h | 1 + pm.pro | 4 ++-- scatterplot.cpp | 65 +++++++++++++++++++++++++------------------------- scatterplot.h | 14 +++++------ 8 files changed, 69 insertions(+), 68 deletions(-) diff --git a/interactionhandler.cpp b/interactionhandler.cpp index 217f4a5..50d0653 100644 --- a/interactionhandler.cpp +++ b/interactionhandler.cpp @@ -6,26 +6,23 @@ 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_sampleIndices(sampleIndices) , m_technique(TECHNIQUE_LAMP) { + m_Y.col(2) = m_labels; } void InteractionHandler::setSubsample(const arma::mat &Ys) { - arma::mat embedding(m_X.n_rows, Ys.n_cols); switch (m_technique) { case TECHNIQUE_PLMP: case TECHNIQUE_LSP: case TECHNIQUE_LAMP: - embedding = mp::lamp(m_X, m_sampleIndices, Ys); + mp::lamp(m_X, m_sampleIndices, Ys, m_Y); break; } - arma::mat Y(embedding.n_rows, embedding.n_cols + 1); - Y.cols(0, embedding.n_cols - 1) = embedding; - Y.col(Y.n_cols - 1) = m_labels; - - emit subsampleChanged(Y); + emit subsampleChanged(m_Y); } diff --git a/interactionhandler.h b/interactionhandler.h index 3dbeb8a..5468dab 100644 --- a/interactionhandler.h +++ b/interactionhandler.h @@ -24,7 +24,7 @@ public slots: void setSubsample(const arma::mat &Ys); private: - arma::mat m_X; + arma::mat m_X, m_Y; arma::vec m_labels; arma::uvec m_sampleIndices; InteractiveTechnique m_technique; diff --git a/lamp.cpp b/lamp.cpp index a9d7a8b..90be140 100644 --- a/lamp.cpp +++ b/lamp.cpp @@ -4,12 +4,19 @@ arma::mat mp::lamp(const arma::mat &X, const arma::uvec &sampleIndices, const arma::mat &Ys) { - arma::mat Xs = X.rows(sampleIndices); - arma::uword sampleSize = sampleIndices.n_elem; arma::mat projection(X.n_rows, 2); + lamp(X, sampleIndices, Ys, projection); + return projection; +} +void mp::lamp(const arma::mat &X, const arma::uvec &sampleIndices, const arma::mat &Ys, arma::mat &Y) +{ + const arma::mat &Xs = X.rows(sampleIndices); + arma::uword sampleSize = sampleIndices.n_elem; + + #pragma omp parallel for shared(X, Xs, sampleIndices, Ys, Y) for (arma::uword i = 0; i < X.n_rows; i++) { - arma::rowvec point = X.row(i); + const arma::rowvec &point = X.row(i); // calculate alphas arma::rowvec alphas(sampleSize); @@ -19,7 +26,6 @@ arma::mat mp::lamp(const arma::mat &X, const arma::uvec &sampleIndices, const ar } double alphas_sum = arma::accu(alphas); - arma::rowvec alphas_sqrt = arma::sqrt(alphas); // calculate \tilde{X} and \tilde{Y} arma::rowvec Xtil = arma::sum(alphas * Xs, 0) / alphas_sum; @@ -32,19 +38,19 @@ arma::mat mp::lamp(const arma::mat &X, const arma::uvec &sampleIndices, const ar Yhat.each_row() -= Ytil; // calculate A and B - arma::mat At = Xhat.t(); - At.each_row() %= alphas_sqrt; - arma::mat B = Yhat; - B.each_col() %= alphas_sqrt.t(); - - arma::mat U, V; - arma::vec s; + alphas = arma::sqrt(alphas); + arma::mat &At = Xhat; + inplace_trans(At); + At.each_row() %= alphas; + arma::mat &B = Yhat; + B.each_col() %= alphas.t(); + + arma::mat U(Ys.n_rows, Ys.n_cols), V(Ys.n_cols, Ys.n_cols); + arma::vec s(Ys.n_cols); arma::svd(U, s, V, At * B); arma::mat M = U.cols(0, 1) * V.t(); // the projection of point i - projection.row(i) = (point - Xtil) * M + Ytil; + Y(i, arma::span(0, 1)) = (point - Xtil) * M + Ytil; } - - return projection; } diff --git a/main_view.qml b/main_view.qml index 5d86c36..5afe270 100644 --- a/main_view.qml +++ b/main_view.qml @@ -10,16 +10,12 @@ ApplicationWindow { height: 600 menuBar: MenuBar { - Menu { - title: "Application" - MenuItem { action: quitAction } - } - Menu { title: "File" MenuItem { action: openAction } MenuItem { action: savePlotAction } MenuItem { action: saveDataAction } + MenuItem { action: quitAction } } } diff --git a/mp.h b/mp.h index 6508d9d..996da87 100644 --- a/mp.h +++ b/mp.h @@ -6,6 +6,7 @@ static const double EPSILON = 1e-3; 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 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); arma::mat forceScheme(const arma::mat &D, arma::mat &Y, size_t maxIter = 20, double tol = 1e-3, double fraction = 8); } // namespace mp diff --git a/pm.pro b/pm.pro index 9608b89..af9a10d 100644 --- a/pm.pro +++ b/pm.pro @@ -1,7 +1,7 @@ QT += qml quick widgets -QMAKE_CXXFLAGS += -std=c++11 -QMAKE_LIBS += -larmadillo +QMAKE_CXXFLAGS += -std=c++11 -fopenmp +QMAKE_LIBS += -larmadillo -fopenmp HEADERS += colorscale.h \ scatterplot.h \ interactionhandler.h \ diff --git a/scatterplot.cpp b/scatterplot.cpp index b0c8034..8fb0b56 100644 --- a/scatterplot.cpp +++ b/scatterplot.cpp @@ -1,12 +1,6 @@ #include "scatterplot.h" #include -#include -#include -#include -#include -#include -#include static const qreal GLYPH_OPACITY = 0.3; static const qreal GLYPH_OPACITY_SELECTED = 1.0; @@ -25,8 +19,8 @@ Scatterplot::Scatterplot(QQuickItem *parent) QColor("#9467bd"), QColor("#8c564b"), QColor("#e377c2"), - QColor("#7f7f7f"), QColor("#17becf"), + QColor("#7f7f7f"), } { setClip(true); @@ -49,10 +43,7 @@ void Scatterplot::setData(const arma::mat &data) m_ymax = data.col(1).max(); m_colorScale.setExtents(m_data.col(2).min(), m_data.col(2).max()); - m_selectedGlyphs.clear(); - for (arma::uword i = 0; i < m_data.n_rows; i++) - m_selectedGlyphs.append(false); update(); } @@ -151,21 +142,23 @@ QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) yt = m_dragCurrentPos.y() - m_dragOriginPos.y(); } - QSGNode *glyphOpacityNode = root->firstChild()->firstChild(); + QSGNode *node = root->firstChild()->firstChild(); for (arma::uword i = 0; i < m_data.n_rows; i++) { arma::rowvec row = m_data.row(i); - moveTranslationF = m_selectedGlyphs[i] ? 1.0 : 0.0; + bool isSelected = m_selectedGlyphs.contains(i); + + QSGOpacityNode *glyphOpacityNode = static_cast(node); + glyphOpacityNode->setOpacity(isSelected ? GLYPH_OPACITY_SELECTED : GLYPH_OPACITY); + + QSGGeometryNode *glyphNode = static_cast(node->firstChild()); + QSGGeometry *geometry = glyphNode->geometry(); + moveTranslationF = isSelected ? 1.0 : 0.0; x = fromDataXToScreenX(row[0]) + xt * moveTranslationF; y = fromDataYToScreenY(row[1]) + yt * moveTranslationF; - - QSGNode *glyphNode = glyphOpacityNode->firstChild(); - QSGGeometry *geometry = static_cast(glyphNode)->geometry(); updateCircleGeometry(geometry, GLYPH_SIZE, x, y); glyphNode->markDirty(QSGNode::DirtyGeometry); - static_cast(glyphOpacityNode)->setOpacity(m_selectedGlyphs[i] ? GLYPH_OPACITY_SELECTED : GLYPH_OPACITY); - - glyphOpacityNode = glyphOpacityNode->nextSibling(); + node = node->nextSibling(); } // Draw selection @@ -203,21 +196,28 @@ void Scatterplot::mousePressEvent(QMouseEvent *event) break; case INTERACTION_SELECTING: case INTERACTION_MOVING: - return; // should not be reached + event->ignore(); + return; } } void Scatterplot::mouseMoveEvent(QMouseEvent *event) { switch (m_currentState) { - case INTERACTION_NONE: - case INTERACTION_SELECTED: - return; case INTERACTION_SELECTING: + m_dragCurrentPos = event->localPos(); + update(); + break; case INTERACTION_MOVING: m_dragCurrentPos = event->localPos(); + updateData(); update(); + m_dragOriginPos = m_dragCurrentPos; break; + case INTERACTION_NONE: + case INTERACTION_SELECTED: + event->ignore(); + return; } } @@ -235,7 +235,6 @@ void Scatterplot::mouseReleaseEvent(QMouseEvent *event) case INTERACTION_MOVING: m_currentState = INTERACTION_SELECTED; - updateData(); update(); break; case INTERACTION_NONE: @@ -246,6 +245,9 @@ void Scatterplot::mouseReleaseEvent(QMouseEvent *event) bool Scatterplot::selectGlyphs(bool mergeSelection) { + if (!mergeSelection) + m_selectedGlyphs.clear(); + qreal x, y; QRectF selectionRect(m_dragOriginPos, m_dragCurrentPos); @@ -255,9 +257,11 @@ bool Scatterplot::selectGlyphs(bool mergeSelection) x = fromDataXToScreenX(row[0]); y = fromDataYToScreenY(row[1]); - bool contains = selectionRect.contains(x, y); - anySelected = anySelected || contains; - m_selectedGlyphs[i] = (mergeSelection && m_selectedGlyphs[i]) || contains; + if (selectionRect.contains(x, y)) { + m_selectedGlyphs.insert(i); + if (!anySelected) + anySelected = true; + } } return anySelected; @@ -270,14 +274,11 @@ void Scatterplot::updateData() xt /= (width() - PADDING); yt /= (height() - PADDING); - for (arma::uword i = 0; i < m_data.n_rows; i++) { - if (!m_selectedGlyphs[i]) - continue; - - arma::rowvec row = m_data.row(i); + 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(i) = row; + m_data.row(*it) = row; } // does not send last column (labels) diff --git a/scatterplot.h b/scatterplot.h index 2b5aed8..a155daf 100644 --- a/scatterplot.h +++ b/scatterplot.h @@ -2,8 +2,7 @@ #define SCATTERPLOT_H #include -#include -#include +#include #include "colorscale.h" @@ -34,6 +33,11 @@ private: float fromDataXToScreenX(float x); float fromDataYToScreenY(float y); + arma::mat m_data; + float m_xmin, m_xmax, m_ymin, m_ymax; + + ColorScale m_colorScale; + enum InteractionState { INTERACTION_NONE, INTERACTION_SELECTING, @@ -41,12 +45,8 @@ private: INTERACTION_MOVING } m_currentState; QPointF m_dragOriginPos, m_dragCurrentPos; - QList m_selectedGlyphs; - arma::mat m_data; - float m_xmin, m_xmax, m_ymin, m_ymax; - - ColorScale m_colorScale; + QSet m_selectedGlyphs; }; #endif // SCATTERPLOT_H -- cgit v1.2.3