diff options
-rw-r--r-- | main.cpp | 87 | ||||
-rw-r--r-- | main.h | 65 | ||||
-rw-r--r-- | main_view.qml | 34 | ||||
-rw-r--r-- | pm.pro | 3 |
4 files changed, 136 insertions, 53 deletions
@@ -1,10 +1,14 @@ #include <cmath> -#include <memory> #include <iostream> -#include <QSurfaceFormat> +#include <memory> +#include <string> + #include <QApplication> +#include <QtQml> #include <QQmlApplicationEngine> +#include <QSurfaceFormat> +#include "main.h" #include "mp.h" #include "continuouscolorscale.h" #include "scatterplot.h" @@ -15,12 +19,44 @@ #include "distortionobserver.h" #include "npdistortion.h" +static QObject *mainProvider(QQmlEngine *engine, QJSEngine *scriptEngine) +{ + Q_UNUSED(engine) + Q_UNUSED(scriptEngine) + + return Main::instance(); +} + int main(int argc, char **argv) { QApplication app(argc, argv); + app.setApplicationName("pm"); + app.setApplicationVersion("1.0"); qmlRegisterType<Scatterplot>("PM", 1, 0, "Scatterplot"); qmlRegisterType<HistoryGraph>("PM", 1, 0, "HistoryGraph"); + qmlRegisterSingletonType<Main>("PM", 1, 0, "Main", mainProvider); + + QCommandLineParser parser; + parser.setApplicationDescription("Interactive multidimensional projections."); + parser.addHelpOption(); + parser.addVersionOption(); + parser.addPositionalArgument("dataset", "Dataset filename (.tbl)"); + + QCommandLineOption indicesFileOutputOption(QStringList() << "i" << "indices", + "Filename to store the subsample indices.", + "indices_path"); + parser.addOption(indicesFileOutputOption); + QCommandLineOption subsampleFileOutputOption(QStringList() << "s" << "subsample", + "Filename to store subsample mapping.", + "subsample_path"); + parser.addOption(subsampleFileOutputOption); + + parser.process(app); + QStringList args = parser.positionalArguments(); + if (args.size() != 1) { + parser.showHelp(1); + } // Set up multisampling QSurfaceFormat fmt; @@ -28,19 +64,23 @@ int main(int argc, char **argv) QSurfaceFormat::setDefaultFormat(fmt); QQmlApplicationEngine engine(QUrl("qrc:///main_view.qml")); - arma::mat dataset; - if (argc > 1) { - dataset.load(argv[1], arma::raw_ascii); - } else { - dataset.load(std::cin, arma::raw_ascii); + Main *m = Main::instance(); + if (parser.isSet(indicesFileOutputOption)) { + m->setIndicesSavePath(parser.value(indicesFileOutputOption)); } + if (parser.isSet(subsampleFileOutputOption)) { + m->setSubsampleSavePath(parser.value(subsampleFileOutputOption)); + } + m->loadDataset(args[0].toStdString()); - arma::mat X = dataset.cols(0, dataset.n_cols - 2); - arma::vec labels = dataset.col(dataset.n_cols - 1); + arma::mat X = m->X(); + arma::vec labels = m->labels(); - arma::uword n = dataset.n_rows; + arma::uword n = X.n_rows; arma::uword subsampleSize = (arma::uword) n / 10.f; arma::uvec sampleIndices = arma::randi<arma::uvec>(subsampleSize, arma::distr_param(0, n-1)); + m->setSubsampleIndices(sampleIndices); + arma::mat Ys(subsampleSize, 2, arma::fill::randn); mp::forceScheme(mp::dist(X.rows(sampleIndices)), Ys); @@ -65,7 +105,13 @@ int main(int argc, char **argv) subsamplePlot->setColorScale(&colorScale); Scatterplot *plot = engine.rootObjects()[0]->findChild<Scatterplot *>("plot"); - // connect both plots through interaction handler + // Keep track of the current subsample (in order to save them later, if requested) + QObject::connect(subsamplePlot, SIGNAL(xyChanged(const arma::mat &)), + m, SLOT(setSubsample(const arma::mat &))); + QObject::connect(subsamplePlot, SIGNAL(xyInteractivelyChanged(const arma::mat &)), + m, SLOT(setSubsample(const arma::mat &))); + + // Update LAMP projection as the subsample is modified InteractionHandler interactionHandler(X, sampleIndices); QObject::connect(subsamplePlot, SIGNAL(xyChanged(const arma::mat &)), &interactionHandler, SLOT(setSubsample(const arma::mat &))); @@ -74,20 +120,21 @@ int main(int argc, char **argv) QObject::connect(&interactionHandler, SIGNAL(subsampleChanged(const arma::mat &)), plot, SLOT(setXY(const arma::mat &))); - // linking between selections in subsample plot and full dataset plot + // Linking between selections in subsample plot and full dataset plot SelectionHandler selectionHandler(sampleIndices); QObject::connect(subsamplePlot, SIGNAL(selectionChanged(const QSet<int> &)), &selectionHandler, SLOT(setSelection(const QSet<int> &))); QObject::connect(&selectionHandler, SIGNAL(selectionChanged(const QSet<int> &)), plot, SLOT(setSelection(const QSet<int> &))); + // Connections between history graph and subsample plot HistoryGraph *history = engine.rootObjects()[0]->findChild<HistoryGraph *>("history"); - // connections between history graph and subsample plot QObject::connect(subsamplePlot, SIGNAL(xyInteractivelyChanged(const arma::mat &)), history, SLOT(addHistoryItem(const arma::mat &))); QObject::connect(history, SIGNAL(currentItemChanged(const arma::mat &)), subsamplePlot, SLOT(setXY(const arma::mat &))); + // Map distortion as the glyph color DistortionObserver distortionObs(X, sampleIndices); std::unique_ptr<DistortionMeasure> distortionMeasure(new NPDistortion()); distortionObs.setMeasure(distortionMeasure.get()); @@ -96,13 +143,13 @@ int main(int argc, char **argv) QObject::connect(&distortionObs, SIGNAL(mapChanged(const arma::vec &)), plot, SLOT(setColorData(const arma::vec &))); - EffectiveInteractionEnforcer enforcer(sampleIndices); - QObject::connect(subsamplePlot, SIGNAL(selectionChanged(const QSet<int> &)), - &enforcer, SLOT(setSelection(const QSet<int> &))); - QObject::connect(plot, SIGNAL(colorDataChanged(const arma::vec &)), - &enforcer, SLOT(setMeasureDifference(const arma::vec &))); - QObject::connect(&enforcer, SIGNAL(effectivenessChanged(const arma::vec &)), - subsamplePlot, SLOT(setColorData(const arma::vec &))); + //EffectiveInteractionEnforcer enforcer(sampleIndices); + //QObject::connect(subsamplePlot, SIGNAL(selectionChanged(const QSet<int> &)), + // &enforcer, SLOT(setSelection(const QSet<int> &))); + //QObject::connect(plot, SIGNAL(colorDataChanged(const arma::vec &)), + // &enforcer, SLOT(setMeasureDifference(const arma::vec &))); + //QObject::connect(&enforcer, SIGNAL(effectivenessChanged(const arma::vec &)), + // subsamplePlot, SLOT(setColorData(const arma::vec &))); /* ContinuousColorScale ccolorScale = ContinuousColorScale::builtin(ContinuousColorScale::RED_GRAY_BLUE); @@ -0,0 +1,65 @@ +#ifndef MAIN_H +#define MAIN_H + +#include <QObject> +#include <armadillo> + +class Main : public QObject +{ + Q_OBJECT +public: + static Main *instance() + { + static Main *m = 0; + if (m == 0) { + m = new Main(); + } + + return m; + } + + Q_INVOKABLE bool saveData() const + { + bool ret = true; + if (m_subsample.n_elem > 0 && m_indicesSavePath.size() > 0) { + ret = ret && m_subsample.save(m_subsampleSavePath, arma::raw_ascii); + } + if (m_subsampleIndices.n_elem > 0 && m_subsampleSavePath.size() > 0) { + ret = ret && m_subsampleIndices.save(m_indicesSavePath, arma::raw_ascii); + } + + return ret; + } + + Q_INVOKABLE bool loadDataset(const std::string &path) { return m_dataset.load(path, arma::raw_ascii); } + + Q_INVOKABLE void setIndicesSavePath(const std::string &path) { m_indicesSavePath = path; } + Q_INVOKABLE void setIndicesSavePath(const QString &path) { setIndicesSavePath(path.toStdString()); } + Q_INVOKABLE void setSubsampleSavePath(const std::string &path) { m_subsampleSavePath = path; } + Q_INVOKABLE void setSubsampleSavePath(const QString &path) { setSubsampleSavePath(path.toStdString()); } + + arma::mat X() const { return m_dataset.cols(0, m_dataset.n_cols - 2); } + arma::vec labels() const { return m_dataset.col(m_dataset.n_cols - 1); } + +public slots: + void setSubsampleIndices(const arma::uvec &indices) { m_subsampleIndices = indices; } + void setSubsample(const arma::mat &subsample) { + if (subsample.n_cols != 2 + || subsample.n_rows != m_subsampleIndices.n_elem) { + return; + } + + m_subsample = subsample; + } + +private: + Main(QObject *parent = 0) : QObject(parent) {} + ~Main() {} + Q_DISABLE_COPY(Main) + + arma::mat m_dataset, m_subsample; + arma::uvec m_subsampleIndices; + std::string m_indicesSavePath, m_subsampleSavePath; +}; + +#endif // MAIN_H diff --git a/main_view.qml b/main_view.qml index 49448cb..de9ec64 100644 --- a/main_view.qml +++ b/main_view.qml @@ -14,7 +14,6 @@ ApplicationWindow { menuBar: MenuBar { Menu { title: "File" - MenuItem { action: openAction } MenuItem { action: savePlotAction } MenuItem { action: quitAction } } @@ -91,28 +90,6 @@ ApplicationWindow { } } - FileDialog { - id: fileOpenDialog - title: "Choose a data set to load..." - selectMultiple: false - selectExisting: true - - onAccepted: { - console.log("Loading data set: " + this.fileUrl) - } - } - - FileDialog { - id: fileSaveDialog - title: "Save subsample mapping..." - selectMultiple: false - selectExisting: false - - onAccepted: { - subsamplePlot.saveToFile(this.fileUrl) - } - } - Action { id: quitAction text: "&Quit" @@ -121,19 +98,12 @@ ApplicationWindow { } Action { - id: openAction - text: "&Open..." - shortcut: "Ctrl+O" - onTriggered: fileOpenDialog.open() - } - - Action { id: savePlotAction - text: "&Save subsample" + text: "&Save data" shortcut: "Ctrl+S" onTriggered: { console.log("Saving subsample mapping...") - fileSaveDialog.open() + Main.saveData() } } @@ -2,7 +2,8 @@ QT += qml quick widgets QMAKE_CXXFLAGS += -std=c++11 -fopenmp QMAKE_LIBS += -larmadillo -fopenmp -HEADERS += colorscale.h \ +HEADERS += main.h \ + colorscale.h \ continuouscolorscale.h \ geometry.h \ scale.h \ |