From febe66a696ffa6b41f74b48fb086a7a1d793d56e Mon Sep 17 00:00:00 2001 From: =?utf8?q?Michal=20Mal=C3=BD?= Date: Fri, 22 Nov 2013 20:26:52 +0100 Subject: [PATCH] Add ability to export raw data to CSV file. --- Anyanka.pro | 15 +++- csvrawdatawriter.cpp | 21 ++++++ csvrawdatawriter.h | 18 +++++ datamanager.cpp | 35 +++++++++ datamanager.h | 1 + gui/exportrawdatadialog.cpp | 68 +++++++++++++++++ gui/exportrawdatadialog.h | 36 +++++++++ gui/exportrawdatadialog.ui | 142 ++++++++++++++++++++++++++++++++++++ gui/mainwindow.cpp | 6 ++ gui/mainwindow.h | 2 + gui/mainwindow.ui | 25 +++++++ main.cpp | 1 + rawdataexporter.cpp | 119 ++++++++++++++++++++++++++++++ rawdataexporter.h | 39 ++++++++++ rawdatawriter.cpp | 7 ++ rawdatawriter.h | 22 ++++++ sequence.cpp | 9 +++ sequence.h | 4 +- signal.cpp | 8 ++ signal.h | 1 + 20 files changed, 575 insertions(+), 4 deletions(-) create mode 100644 csvrawdatawriter.cpp create mode 100644 csvrawdatawriter.h create mode 100644 gui/exportrawdatadialog.cpp create mode 100644 gui/exportrawdatadialog.h create mode 100644 gui/exportrawdatadialog.ui create mode 100644 rawdataexporter.cpp create mode 100644 rawdataexporter.h create mode 100644 rawdatawriter.cpp create mode 100644 rawdatawriter.h diff --git a/Anyanka.pro b/Anyanka.pro index d5ec378..f398720 100644 --- a/Anyanka.pro +++ b/Anyanka.pro @@ -45,7 +45,11 @@ SOURCES += main.cpp\ integrator.cpp \ integrationtablemodel.cpp \ gui/aboutanyanka.cpp \ - globalinfo.cpp + globalinfo.cpp \ + gui/exportrawdatadialog.cpp \ + rawdataexporter.cpp \ + rawdatawriter.cpp \ + csvrawdatawriter.cpp HEADERS += \ datafilesloader.h \ @@ -71,12 +75,17 @@ HEADERS += \ integrationtablemodel.h \ gui/aboutanyanka.h \ globalinfo.h \ - windows_defines.h + windows_defines.h \ + gui/exportrawdatadialog.h \ + rawdataexporter.h \ + rawdatawriter.h \ + csvrawdatawriter.h FORMS += \ gui/mainwindow.ui \ gui/signalview.ui \ - gui/aboutanyanka.ui + gui/aboutanyanka.ui \ + gui/exportrawdatadialog.ui RESOURCES += \ imgresources.qrc diff --git a/csvrawdatawriter.cpp b/csvrawdatawriter.cpp new file mode 100644 index 0000000..8d464c6 --- /dev/null +++ b/csvrawdatawriter.cpp @@ -0,0 +1,21 @@ +#include "csvrawdatawriter.h" + +CSVRawDataWriter::CSVRawDataWriter(QObject* parent) : + RawDataWriter("csv", parent) +{ + locale = QLocale::system(); +} + +QString CSVRawDataWriter::filename(const QString& name, const QString& extra) const +{ + QString newname = name; + if (extra.compare("") != 0) + return newname + "_" + extra + "." + FILE_EXTENSION; + else + return newname + "." + FILE_EXTENSION; +} + +QString CSVRawDataWriter::line(const double time, const double value) const +{ + return locale.toString(time) + ";" + locale.toString(value); +} diff --git a/csvrawdatawriter.h b/csvrawdatawriter.h new file mode 100644 index 0000000..60307b9 --- /dev/null +++ b/csvrawdatawriter.h @@ -0,0 +1,18 @@ +#ifndef CSVRAWDATAWRITER_H +#define CSVRAWDATAWRITER_H + +#include "rawdatawriter.h" +#include + +class CSVRawDataWriter : public RawDataWriter +{ +public: + CSVRawDataWriter(QObject* parent = nullptr); + QString filename(const QString& name, const QString& extra) const; + QString line(const double time, const double value) const; + +private: + QLocale locale; +}; + +#endif // CSVRAWDATAWRITER_H diff --git a/datamanager.cpp b/datamanager.cpp index 3f6fdff..240a848 100644 --- a/datamanager.cpp +++ b/datamanager.cpp @@ -22,6 +22,8 @@ #include "datamanager.h" #include "logger.h" +#include "rawdataexporter.h" +#include "gui/exportrawdatadialog.h" #include #include @@ -282,6 +284,39 @@ void DataManager::showOneSignal(std::shared_ptr ctrl) /* Public slots */ +void DataManager::onExportRawData() +{ + RawDataExporter exporter(this); + ExportRawDataDialog dlg(RawDataExporter::SUPPORTED_FORMATS); + std::vector> sigs; + + if (m_activeSequence == nullptr) + return; + if (m_activeSequence->selectedRun() == nullptr) + return; + + for (int idx = 0; idx < m_activeSequence->selectedRun()->signalCount(); idx++) + dlg.addAvailableSignal(m_activeSequence->selectedRun()->signalAt(idx)->descriptiveName()); + + RawDataExporter::ReturnCode ret; + do { + int dret = dlg.exec(); + if (dret == QDialog::Rejected) + return; + else if (dret == QDialog::Accepted && dlg.selectedSignalsCount() < 1) { + QMessageBox::information(nullptr, "Data export error", "No signals selected"); + ret = RawDataExporter::ReturnCode::E_TRYAGAIN; + } else if (dret == QDialog::Accepted && dlg.selectedSignalsCount() >= 1) { + sigs.clear(); + for (int idx = 0; idx < dlg.signalsCount(); idx++) { + if (dlg.isSignalSelected(idx)) + sigs.push_back(m_activeSequence->selectedRun()->signalAt(idx)); + } + ret = exporter.exportData(sigs, dlg.destination(), dlg.format()); + } + } while (ret == RawDataExporter::ReturnCode::E_TRYAGAIN); +} + void DataManager::onLoadSequence(const QString& dir) { QDir rootDir(dir); diff --git a/datamanager.h b/datamanager.h index bb7143e..364621d 100644 --- a/datamanager.h +++ b/datamanager.h @@ -81,6 +81,7 @@ signals: void setSingleRunInfo(const QString& method, const QString& operatorname, const QString& sample, const QString& datetime); public slots: + void onExportRawData(); void onLoadSequence(const QString& dir); void onLoadSingleRun(const QString& dir); void onSequenceSelected(const QString& key); diff --git a/gui/exportrawdatadialog.cpp b/gui/exportrawdatadialog.cpp new file mode 100644 index 0000000..3a8e1c5 --- /dev/null +++ b/gui/exportrawdatadialog.cpp @@ -0,0 +1,68 @@ +#include "exportrawdatadialog.h" +#include "ui_exportrawdatadialog.h" +#include + +ExportRawDataDialog::ExportRawDataDialog(const QStringList& supportedFormats, QWidget* parent) : + QDialog(parent), + m_supportedFormats(supportedFormats), + ui(new Ui::ExportRawDataDialog) +{ + ui->setupUi(this); + + ui->qcbox_formats->addItems(supportedFormats); + + connect(ui->qpb_browse, SIGNAL(clicked()), this, SLOT(onBrowseClicked())); + connect(ui->qpb_ok, SIGNAL(clicked()), this, SLOT(accept())); + connect(ui->qpb_cancel, SIGNAL(clicked()), this, SLOT(reject())); +} + +/* Public functions */ + +void ExportRawDataDialog::addAvailableSignal(const QString& name) +{ + QCheckBox* cbox = new QCheckBox(name, this); + m_signalCheckboxes.push_back(cbox); + ui->form_availSigs->addWidget(cbox); +} + +QString ExportRawDataDialog::destination() const +{ + return ui->qle_destPath->text(); +} + +int ExportRawDataDialog::format() const +{ + return ui->qcbox_formats->currentIndex(); +} + +bool ExportRawDataDialog::isSignalSelected(int idx) const +{ + if (idx >= m_signalCheckboxes.size()) + return false; + else + return m_signalCheckboxes.at(idx)->isChecked(); +} + +int ExportRawDataDialog::selectedSignalsCount() const +{ + int cnt = 0; + for (const QCheckBox* cbox : m_signalCheckboxes) { + if (cbox->isChecked()) + cnt++; + } + return cnt; +} + +/* Private slots */ + +void ExportRawDataDialog::onBrowseClicked() +{ + QFileDialog fdlg(this, "Pick destination", QString()); + if (fdlg.exec() == QDialog::Accepted) + ui->qle_destPath->setText(fdlg.selectedFiles()[0]); +} + +ExportRawDataDialog::~ExportRawDataDialog() +{ + delete ui; +} diff --git a/gui/exportrawdatadialog.h b/gui/exportrawdatadialog.h new file mode 100644 index 0000000..ac4b17c --- /dev/null +++ b/gui/exportrawdatadialog.h @@ -0,0 +1,36 @@ +#ifndef EXPORTRAWDATADIALOG_H +#define EXPORTRAWDATADIALOG_H + +#include +#include +#include + +namespace Ui { + class ExportRawDataDialog; +} + +class ExportRawDataDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ExportRawDataDialog(const QStringList& supportedFormats, QWidget* parent = nullptr); + ~ExportRawDataDialog(); + void addAvailableSignal(const QString& name); + QString destination() const; + int format() const; + bool isSignalSelected(int idx) const; + int selectedSignalsCount() const; + int signalsCount() const { return m_signalCheckboxes.size(); } + +private: + std::vector m_signalCheckboxes; + QStringList m_supportedFormats; + Ui::ExportRawDataDialog* ui; + +private slots: + void onBrowseClicked(); + +}; + +#endif // EXPORTRAWDATADIALOG_H diff --git a/gui/exportrawdatadialog.ui b/gui/exportrawdatadialog.ui new file mode 100644 index 0000000..bd3c8ab --- /dev/null +++ b/gui/exportrawdatadialog.ui @@ -0,0 +1,142 @@ + + + ExportRawDataDialog + + + + 0 + 0 + 400 + 300 + + + + + 0 + 0 + + + + Export raw data + + + + + + + + Browse + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + + + + + + 0 + 0 + + + + Format: + + + + + + + + 0 + 0 + + + + + + + + + + + 0 + 0 + + + + + + + + 0 + 0 + + + + Available signals: + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + OK + + + + + + + Cancel + + + + + + + + + + diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 59ee7cc..7029504 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -66,9 +66,15 @@ void MainWindow::connectActions() /* Controls panel */ connect(ui->qpb_integrate, SIGNAL(pressed()), this, SLOT(onIntegrateSelected())); connect(ui->qpb_zoom, SIGNAL(pressed()), this, SLOT(onZoomSelected())); + + /* DATA menu */ connect(ui->actionLoad_single_run, SIGNAL(triggered()), this, SLOT(onLoadSingleRun())); connect(ui->actionLoad_sequence, SIGNAL(triggered()), this, SLOT(onLoadSequence())); + /* EXPORT menu */ + connect(ui->actionRaw_values, SIGNAL(triggered()), this, SLOT(onExportRawData())); + /* HELP menu */ connect(ui->actionAbout, SIGNAL(triggered()), this, SLOT(onAboutAnyanka())); + /* RUN/SEQ cboxes*/ connect(ui->qcbox_sequence, SIGNAL(currentIndexChanged(QString)), this, SLOT(onSequenceSelected(QString))); connect(ui->qcbox_singleRun, SIGNAL(currentIndexChanged(QString)), this, SLOT(onSingleRunSelected(QString))); } diff --git a/gui/mainwindow.h b/gui/mainwindow.h index a814086..71d8083 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -69,6 +69,7 @@ public slots: private slots: void onAboutAnyanka(); + void onExportRawData() { emit exportRawData(); } void onIntegrateSelected(); void onLoadSequence(); void onLoadSingleRun(); @@ -79,6 +80,7 @@ private slots: signals: void controlModeChanged(GraphControlModes mode); + void exportRawData(); void loadSequence(const QString& dir); void loadSingleRun(const QString& dir); void sequenceSelected(const QString& str); diff --git a/gui/mainwindow.ui b/gui/mainwindow.ui index 0dcb07f..f63e23a 100644 --- a/gui/mainwindow.ui +++ b/gui/mainwindow.ui @@ -222,7 +222,17 @@ + + + Export + + + + + + + @@ -245,6 +255,21 @@ About + + + Raw data + + + + + Integration results + + + + + Graph as image + + diff --git a/main.cpp b/main.cpp index bd8eea9..b676700 100644 --- a/main.cpp +++ b/main.cpp @@ -69,6 +69,7 @@ int main(int argc, char *argv[]) QObject::connect(dMgr.get(), SIGNAL(setActiveSequenceIndex(int)), mWin.get(), SLOT(onSetActiveSequenceIndex(int))); QObject::connect(dMgr.get(), SIGNAL(setActiveSingleRunIndex(int)), mWin.get(), SLOT(onSetActiveSingleRunIndex(int))); QObject::connect(dMgr.get(), SIGNAL(setSingleRunInfo(QString,QString,QString,QString)), mWin.get(), SLOT(onSetSingleRunInfo(QString,QString,QString,QString))); + QObject::connect(mWin.get(), SIGNAL(exportRawData()), dMgr.get(), SLOT(onExportRawData())); QObject::connect(mWin.get(), SIGNAL(loadSingleRun(QString)), dMgr.get(), SLOT(onLoadSingleRun(QString))); QObject::connect(mWin.get(), SIGNAL(loadSequence(QString)), dMgr.get(), SLOT(onLoadSequence(QString))); QObject::connect(mWin.get(), SIGNAL(sequenceSelected(QString)), dMgr.get(), SLOT(onSequenceSelected(QString))); diff --git a/rawdataexporter.cpp b/rawdataexporter.cpp new file mode 100644 index 0000000..08dd391 --- /dev/null +++ b/rawdataexporter.cpp @@ -0,0 +1,119 @@ +#include "rawdataexporter.h" +#include "csvrawdatawriter.h" +#include +#include +#include + +const QStringList RawDataExporter::SUPPORTED_FORMATS = QStringList() << "Comma separated values (CSV)"; + +RawDataExporter::RawDataExporter(QObject* parent) : + QObject(parent) +{ +} + +/* Public methods */ + +RawDataExporter::ReturnCode RawDataExporter::exportData(const std::vector> sigs, const QString& path, const int formatIdx) +{ + QDir dir(path); + QFile file(path); + QString plainName = file.fileName(); + OutputFormats format; + RawDataExporter::ReturnCode ret; + RawDataWriter* writer; + + if (formatIdx < 0 || formatIdx >= SUPPORTED_FORMATS.length()) { + QMessageBox::warning(nullptr, "Data export error", "Invalid format selected"); + return ReturnCode::E_TRYAGAIN; + } + + if (path.compare("") == 0) { + QMessageBox::information(nullptr, "Data export error", "No output file specified."); + return ReturnCode::E_TRYAGAIN; + } + if (dir.exists()) { + QMessageBox::information(nullptr, "Data export error", "Selected path points to a directory. Name of output file is required."); + return ReturnCode::E_TRYAGAIN; + } + + if (file.exists()) { + int ret = QMessageBox::question(nullptr, "Data export", "The selected file already exists. Do you wish to overwrite it?"); + if (ret == QMessageBox::No) + return ReturnCode::E_TRYAGAIN; + else + file.remove(); + } + + format = formatIdxToFormat(formatIdx); + switch (format) { + case OutputFormats::CSV: + writer = new CSVRawDataWriter(this); + break; + } + + if (sigs.size() > 1) + file.setFileName(writer->filename(plainName, sigs.at(0)->descriptiveName())); + else + file.setFileName(writer->filename(plainName)); + + /* Export first signal to file */ + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(nullptr, "Data export error", "Cannot open output file for writing."); + delete writer; + return ReturnCode::E_FAILED; + } + + ret = writeToFile(file, writer, sigs.at(0)); + if (ret != ReturnCode::SUCCESS) { + file.close(); + delete writer; + return ret; + } + + for (int idx = 1; idx < sigs.size(); idx++) { + file.setFileName(writer->filename(plainName, sigs.at(idx)->descriptiveName())); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(nullptr, "Data export error", "Cannot open output file '" + file.fileName() + "' for writing."); + return ReturnCode::E_FAILED; + } + ret = writeToFile(file, writer, sigs.at(idx)); + file.close(); + if (ret != ReturnCode::SUCCESS) + break; + } + + delete writer; + return ret; +} + +/* Private methods */ + +RawDataExporter::OutputFormats RawDataExporter::formatIdxToFormat(const int idx) +{ + switch (idx) { + case 0: + return OutputFormats::CSV; + } +} + +RawDataExporter::ReturnCode RawDataExporter::writeToFile(QFile& file, const RawDataWriter* writer, const std::shared_ptr signal) +{ + QString line; + QByteArray bytes; + qint64 w; + + bytes = QString("time;value\n").toUtf8(); + w = file.write(bytes); + if (w < bytes.size()) + return ReturnCode::E_FAILED; + + for (int idx = 0; idx < signal->valuesCount(); idx++) { + line = writer->line(signal->timeAt(idx), signal->valueAt(idx)); + bytes = line.toUtf8(); + w = file.write(bytes); + if (w < bytes.size()) + return ReturnCode::E_FAILED; + } + + return ReturnCode::SUCCESS; +} diff --git a/rawdataexporter.h b/rawdataexporter.h new file mode 100644 index 0000000..00bff2f --- /dev/null +++ b/rawdataexporter.h @@ -0,0 +1,39 @@ +#ifndef RAWDATAEXPORTER_H +#define RAWDATAEXPORTER_H + +#include "rawdatawriter.h" +#include "signal.h" +#include +#include +#include + +class RawDataExporter : public QObject +{ + Q_OBJECT +public: + enum class ReturnCode { + SUCCESS, + E_TRYAGAIN, + E_FAILED + }; + + explicit RawDataExporter(QObject* parent = nullptr); + ReturnCode exportData(std::vector> sigs, const QString& path, const int formatIdx); + + static const QStringList SUPPORTED_FORMATS; +private: + enum class OutputFormats { + CSV + }; + + OutputFormats formatIdxToFormat(const int idx); + QString toCSVLine(double time, double value); + ReturnCode writeToFile(QFile& file, const RawDataWriter* writer, const std::shared_ptr signal); + +signals: + +public slots: + +}; + +#endif // RAWDATAEXPORTER_H diff --git a/rawdatawriter.cpp b/rawdatawriter.cpp new file mode 100644 index 0000000..d3dbd4c --- /dev/null +++ b/rawdatawriter.cpp @@ -0,0 +1,7 @@ +#include "rawdatawriter.h" + +RawDataWriter::RawDataWriter(const QString& extension, QObject* parent) : + QObject(parent), + FILE_EXTENSION(extension) +{ +} diff --git a/rawdatawriter.h b/rawdatawriter.h new file mode 100644 index 0000000..59daa97 --- /dev/null +++ b/rawdatawriter.h @@ -0,0 +1,22 @@ +#ifndef RAWDATAWRITER_H +#define RAWDATAWRITER_H + +#include + +class RawDataWriter : public QObject +{ + Q_OBJECT +public: + explicit RawDataWriter(const QString& extension, QObject* parent = nullptr); + virtual QString filename(const QString& name, const QString& extra = QString()) const = 0; + virtual QString line(const double time, const double value) const = 0; + + const QString FILE_EXTENSION; + +signals: + +public slots: + +}; + +#endif // RAWDATAWRITER_H diff --git a/sequence.cpp b/sequence.cpp index 52b415d..2c4e582 100644 --- a/sequence.cpp +++ b/sequence.cpp @@ -30,6 +30,7 @@ Sequence::Sequence(QObject* parent) : Sequence::Sequence(std::vector& singleRuns, QObject* parent) : QObject(parent), m_selectedRunKey(""), + m_selectedRunIdx(-1), m_singleRuns(singleRuns) { } @@ -87,6 +88,14 @@ void Sequence::removeAt(const size_t idx) m_singleRuns.erase(m_singleRuns.begin()+idx); } +std::shared_ptr Sequence::selectedRun() +{ + if (m_selectedRunKey.compare("") == 0 || m_selectedRunIdx == -1) + return nullptr; + + return m_singleRuns.at(m_selectedRunIdx).second; +} + int Sequence::selectedRunIdx() const { int idx = 0; diff --git a/sequence.h b/sequence.h index c8f986e..1f3f85f 100644 --- a/sequence.h +++ b/sequence.h @@ -52,12 +52,14 @@ public: void remove(const QString& key); void removeAt(const size_t idx); const QString& selectedRunKey() const { return m_selectedRunKey; } + std::shared_ptr selectedRun(); int selectedRunIdx() const; int singleRunToIdx(const QString& key) const; - void setSelectedRunKey(const QString& key) { m_selectedRunKey = key; } + void setSelectedRunKey(const QString& key) { m_selectedRunKey = key; m_selectedRunIdx = singleRunToIdx(key); } private: QString m_selectedRunKey; + int m_selectedRunIdx; std::unordered_map> m_signalCtrls; std::vector m_singleRuns; diff --git a/signal.cpp b/signal.cpp index e6f99ac..e8e4d0c 100644 --- a/signal.cpp +++ b/signal.cpp @@ -54,6 +54,14 @@ Signal::Signal(const Equipment equipment, const Resource resource, const double } } +QString Signal::descriptiveName() const +{ + QString name = resourceToString(); + if (m_resource == Resource::CE_DAD) + name += "_SIG" + QString::number(m_wavelengthAbs) + "_REF" + QString::number(m_wavelengthRef); + return name; +} + std::vector::const_iterator Signal::iteratorFrom(const size_t idx) const { return m_values.cbegin()+idx-1; diff --git a/signal.h b/signal.h index 3034a7e..fd30f17 100644 --- a/signal.h +++ b/signal.h @@ -55,6 +55,7 @@ public: explicit Signal(const Equipment equipment, const Resource resource, const double samplingRate, const YUnit yunit, const uint16_t wavelengthAbs, const uint16_t wavelengthRef, const std::vector& values, QObject* parent = nullptr); + QString descriptiveName() const; Equipment equipment() const { return m_equipment; } std::vector::const_iterator iteratorFrom(const size_t idx) const; double maximum() const { return m_max; } -- 2.43.5