# HG changeset patch # User Hasan Yavuz Ă–ZDERYA # Date 2018-07-22 08:42:42 # Node ID c3194fb3b7ea2b9ed7cc8eca80e3c011b2573c43 # Parent 85030785d3fc76a187e2e0c7e3be385a571d98f2 # Parent 6b14ef397a9884d179a1f2ff615298ed0a6237ab Merge with gain-offset diff --git a/src/channelinfomodel.cpp b/src/channelinfomodel.cpp --- a/src/channelinfomodel.cpp +++ b/src/channelinfomodel.cpp @@ -68,6 +68,7 @@ ChannelInfoModel::ChannelInfoModel(unsig ChannelInfoModel::ChannelInfoModel(const ChannelInfoModel& other) : ChannelInfoModel(other.rowCount(), other.parent()) { + // TODO: why not set (copy) info list directly instead? for (int i = 0; i < other.rowCount(); i++) { setData(index(i, COLUMN_NAME), @@ -76,9 +77,24 @@ ChannelInfoModel::ChannelInfoModel(const setData(index(i, COLUMN_NAME), other.data(other.index(i, COLUMN_NAME), Qt::ForegroundRole), Qt::ForegroundRole); + setData(index(i, COLUMN_VISIBILITY), other.data(other.index(i, COLUMN_VISIBILITY), Qt::CheckStateRole), Qt::CheckStateRole); + + setData(index(i, COLUMN_GAIN), + other.data(other.index(i, COLUMN_GAIN), Qt::CheckStateRole), + Qt::CheckStateRole); + setData(index(i, COLUMN_GAIN), + other.data(other.index(i, COLUMN_GAIN), Qt::EditRole), + Qt::EditRole); + + setData(index(i, COLUMN_OFFSET), + other.data(other.index(i, COLUMN_OFFSET), Qt::CheckStateRole), + Qt::CheckStateRole); + setData(index(i, COLUMN_OFFSET), + other.data(other.index(i, COLUMN_OFFSET), Qt::EditRole), + Qt::EditRole); } } @@ -96,6 +112,10 @@ ChannelInfoModel::ChannelInfo::ChannelIn name = tr("Channel %1").arg(index + 1); visibility = true; color = colors[index % NUMOF_COLORS]; + gain = 1.0; + offset = 0.0; + gainEn = false; + offsetEn = false; } QString ChannelInfoModel::name(unsigned i) const @@ -113,6 +133,26 @@ bool ChannelInfoModel::isVisible(unsigne return infos[i].visibility; } +bool ChannelInfoModel::gainEn (unsigned i) const +{ + return infos[i].gainEn; +} + +double ChannelInfoModel::gain (unsigned i) const +{ + return infos[i].gain; +} + +bool ChannelInfoModel::offsetEn (unsigned i) const +{ + return infos[i].offsetEn; +} + +double ChannelInfoModel::offset (unsigned i) const +{ + return infos[i].offset; +} + QStringList ChannelInfoModel::channelNames() const { QStringList r; @@ -143,6 +183,10 @@ Qt::ItemFlags ChannelInfoModel::flags(co { return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren | Qt::ItemIsSelectable; } + else if (index.column() == COLUMN_GAIN || index.column() == COLUMN_OFFSET) + { + return Qt::ItemIsEditable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren | Qt::ItemIsSelectable; + } return Qt::NoItemFlags; } @@ -155,27 +199,51 @@ QVariant ChannelInfoModel::data(const QM return QVariant(); } + auto &info = infos[index.row()]; + // get color if (role == Qt::ForegroundRole) { - return infos[index.row()].color; + return info.color; } - // get name + // name if (index.column() == COLUMN_NAME) { if (role == Qt::DisplayRole || role == Qt::EditRole) { - return QVariant(infos[index.row()].name); + return QVariant(info.name); } - } // get visibility + } // visibility else if (index.column() == COLUMN_VISIBILITY) { if (role == Qt::CheckStateRole) { - bool visible = infos[index.row()].visibility; + bool visible = info.visibility; return visible ? Qt::Checked : Qt::Unchecked; } + } // gain + else if (index.column() == COLUMN_GAIN) + { + if (role == Qt::CheckStateRole) + { + return info.gainEn ? Qt::Checked : Qt::Unchecked; + } + else if (role == Qt::DisplayRole || role == Qt::EditRole) + { + return QVariant(info.gain); + } + } // offset + else if (index.column() == COLUMN_OFFSET) + { + if (role == Qt::CheckStateRole) + { + return info.offsetEn ? Qt::Checked : Qt::Unchecked; + } + else if (role == Qt::DisplayRole || role == Qt::EditRole) + { + return QVariant(info.offset); + } } return QVariant(); @@ -195,6 +263,14 @@ QVariant ChannelInfoModel::headerData(in { return tr("Visible"); } + else if (section == COLUMN_GAIN) + { + return tr("Gain"); + } + else if (section == COLUMN_OFFSET) + { + return tr("Offset"); + } } } else // vertical @@ -216,22 +292,24 @@ bool ChannelInfoModel::setData(const QMo return false; } + auto &info = infos[index.row()]; + // set color if (role == Qt::ForegroundRole) { - infos[index.row()].color = value.value(); + info.color = value.value(); emit dataChanged(index, index, QVector({Qt::ForegroundRole})); return true; } // set name + bool r = false; if (index.column() == COLUMN_NAME) { if (role == Qt::DisplayRole || role == Qt::EditRole) { - infos[index.row()].name = value.toString(); - emit dataChanged(index, index, QVector({role})); - return true; + info.name = value.toString(); + r = true; } } // set visibility else if (index.column() == COLUMN_VISIBILITY) @@ -239,14 +317,47 @@ bool ChannelInfoModel::setData(const QMo if (role == Qt::CheckStateRole) { bool checked = value.toInt() == Qt::Checked; - infos[index.row()].visibility = checked; - emit dataChanged(index, index, QVector({role})); - return true; + info.visibility = checked; + r = true; + } + } + else if (index.column() == COLUMN_GAIN) + { + if (role == Qt::DisplayRole || role == Qt::EditRole) + { + info.gain = value.toDouble(); + r = true; + } + else if (role == Qt::CheckStateRole) + { + bool checked = value.toInt() == Qt::Checked; + info.gainEn = checked; + if (_gainOrOffsetEn != checked) updateGainOrOffsetEn(); + r = true; + } + } + else if (index.column() == COLUMN_OFFSET) + { + if (role == Qt::DisplayRole || role == Qt::EditRole) + { + info.offset = value.toDouble(); + r = true; + } + else if (role == Qt::CheckStateRole) + { + bool checked = value.toInt() == Qt::Checked; + info.offsetEn = checked; + if (_gainOrOffsetEn != checked) updateGainOrOffsetEn(); + r = true; } } - // invalid index/role - return false; + if (r) + { + emit dataChanged(index, index, QVector({role})); + } + + return r; } void ChannelInfoModel::setNumOfChannels(unsigned number) @@ -285,6 +396,7 @@ void ChannelInfoModel::setNumOfChannels( } _numOfChannels = number; + updateGainOrOffsetEn(); if (isInserting) { @@ -306,11 +418,13 @@ void ChannelInfoModel::resetInfos() endResetModel(); } +// TODO: fix repetitive code, ChannelInfoModel::reset* functions void ChannelInfoModel::resetNames() { beginResetModel(); for (unsigned ci = 0; (int) ci < infos.length(); ci++) { + // TODO: do not create a full object every time (applies to other reset methods as well) infos[ci].name = ChannelInfo(ci).name; } endResetModel(); @@ -336,6 +450,45 @@ void ChannelInfoModel::resetVisibility(b endResetModel(); } +void ChannelInfoModel::resetGains() +{ + beginResetModel(); + for (unsigned ci = 0; (int) ci < infos.length(); ci++) + { + infos[ci].gain = ChannelInfo(ci).gain; + infos[ci].gainEn = ChannelInfo(ci).gainEn; + } + updateGainOrOffsetEn(); + endResetModel(); +} + +void ChannelInfoModel::resetOffsets() +{ + beginResetModel(); + for (unsigned ci = 0; (int) ci < infos.length(); ci++) + { + infos[ci].offset = ChannelInfo(ci).offset; + infos[ci].offsetEn = ChannelInfo(ci).offsetEn; + } + updateGainOrOffsetEn(); + endResetModel(); +} + +bool ChannelInfoModel::gainOrOffsetEn() const +{ + return _gainOrOffsetEn; +} + +void ChannelInfoModel::updateGainOrOffsetEn() +{ + _gainOrOffsetEn = false; + for (int ci = 0; ci < _numOfChannels; ci++) + { + auto& info = infos[ci]; + _gainOrOffsetEn |= (info.gainEn || info.offsetEn); + } +} + void ChannelInfoModel::saveSettings(QSettings* settings) const { settings->beginGroup(SettingGroup_Channels); @@ -345,9 +498,14 @@ void ChannelInfoModel::saveSettings(QSet for (unsigned ci = 0; (int) ci < infos.length(); ci++) { settings->setArrayIndex(ci); - settings->setValue(SG_Channels_Name, infos[ci].name); - settings->setValue(SG_Channels_Color, infos[ci].color); - settings->setValue(SG_Channels_Visible, infos[ci].visibility); + auto& info = infos[ci]; + settings->setValue(SG_Channels_Name, info.name); + settings->setValue(SG_Channels_Color, info.color); + settings->setValue(SG_Channels_Visible, info.visibility); + settings->setValue(SG_Channels_Gain, info.gain); + settings->setValue(SG_Channels_GainEn, info.gainEn); + settings->setValue(SG_Channels_Offset, info.offset); + settings->setValue(SG_Channels_OffsetEn, info.offsetEn); } settings->endArray(); @@ -364,9 +522,13 @@ void ChannelInfoModel::loadSettings(QSet settings->setArrayIndex(ci); ChannelInfo chanInfo(ci); - chanInfo.name = settings->value(SG_Channels_Name, chanInfo.name).toString(); - chanInfo.color = settings->value(SG_Channels_Color, chanInfo.color).value(); - chanInfo.visibility = settings->value(SG_Channels_Visible, true).toBool(); + chanInfo.name = settings->value(SG_Channels_Name , chanInfo.name).toString(); + chanInfo.color = settings->value(SG_Channels_Color , chanInfo.color).value(); + chanInfo.visibility = settings->value(SG_Channels_Visible , chanInfo.visibility).toBool(); + chanInfo.gain = settings->value(SG_Channels_Gain , chanInfo.gain).toDouble(); + chanInfo.gainEn = settings->value(SG_Channels_GainEn , chanInfo.gainEn).toBool(); + chanInfo.offset = settings->value(SG_Channels_Offset , chanInfo.offset).toDouble(); + chanInfo.offsetEn = settings->value(SG_Channels_OffsetEn , chanInfo.offsetEn).toBool(); if ((int) ci < infos.size()) { @@ -385,6 +547,8 @@ void ChannelInfoModel::loadSettings(QSet } } + updateGainOrOffsetEn(); + settings->endArray(); settings->endGroup(); } diff --git a/src/channelinfomodel.h b/src/channelinfomodel.h --- a/src/channelinfomodel.h +++ b/src/channelinfomodel.h @@ -34,7 +34,9 @@ public: { COLUMN_NAME = 0, COLUMN_VISIBILITY, - COLUMN_COUNT + COLUMN_GAIN, + COLUMN_OFFSET, + COLUMN_COUNT // MUST be last }; explicit ChannelInfoModel(unsigned numberOfChannels, QObject *parent = 0); @@ -44,6 +46,12 @@ public: QString name (unsigned i) const; QColor color (unsigned i) const; bool isVisible(unsigned i) const; + bool gainEn (unsigned i) const; + double gain (unsigned i) const; + bool offsetEn (unsigned i) const; + double offset (unsigned i) const; + /// Returns true if any of the channels have gain or offset enabled + bool gainOrOffsetEn() const; /// Returns a list of channel names QStringList channelNames() const; @@ -68,6 +76,10 @@ public slots: void resetNames(); /// reset all channel colors void resetColors(); + /// reset all channel gain values and disables gains + void resetGains(); + /// reset all channel offset values and disables offsets + void resetOffsets(); /// reset visibility void resetVisibility(bool visible); @@ -79,6 +91,8 @@ private: QString name; bool visibility; QColor color; + double gain, offset; + bool gainEn, offsetEn; }; unsigned _numOfChannels; ///< @note this is not necessarily the length of `infos` @@ -88,6 +102,16 @@ private: * remember user entered info (names, colors etc.). */ QList infos; + + /** + * Cache for gain and offset enabled variables of channels. If gain and/or + * offset is not enabled for *any* of the channels this is false otherwise + * true. + */ + bool _gainOrOffsetEn; + + /// Updates `_gainOrOffsetEn` by scanning all channel infos. + void updateGainOrOffsetEn(); }; #endif // CHANNELINFOMODEL_H diff --git a/src/plotcontrolpanel.cpp b/src/plotcontrolpanel.cpp --- a/src/plotcontrolpanel.cpp +++ b/src/plotcontrolpanel.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include @@ -30,6 +31,8 @@ /// Confirm if #samples is being set to a value greater than this const int NUMSAMPLES_CONFIRM_AT = 1000000; +/// Precision used for channel info table numbers +const int DOUBLESP_PRECISION = 6; /// Used for scale range selection combobox struct Range @@ -40,6 +43,25 @@ struct Range Q_DECLARE_METATYPE(Range); +/// Used for customizing double precision in tables +class SpinBoxDelegate : public QStyledItemDelegate +{ +public: + QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const Q_DECL_OVERRIDE + { + auto w = QStyledItemDelegate::createEditor( + parent, option, index); + + auto sp = qobject_cast(w); + if (sp) + { + sp->setDecimals(DOUBLESP_PRECISION); + } + return w; + } +}; + PlotControlPanel::PlotControlPanel(QWidget *parent) : QWidget(parent), ui(new Ui::PlotControlPanel), @@ -48,10 +70,15 @@ PlotControlPanel::PlotControlPanel(QWidg resetColorsAct(tr("Reset Colors"), this), showAllAct(tr("Show All"), this), hideAllAct(tr("Hide All"), this), + resetGainsAct(tr("Reset All Gain"), this), + resetOffsetsAct(tr("Reset All Offset"), this), resetMenu(tr("Reset Menu"), this) { ui->setupUi(this); + delegate = new SpinBoxDelegate(); + ui->tvChannelInfo->setItemDelegate(delegate); + warnNumOfSamples = true; // TODO: load from settings _numOfSamples = ui->spNumOfSamples->value(); @@ -143,6 +170,8 @@ PlotControlPanel::PlotControlPanel(QWidg resetAct.setToolTip(tr("Reset channel names and colors")); resetMenu.addAction(&resetNamesAct); resetMenu.addAction(&resetColorsAct); + resetMenu.addAction(&resetGainsAct); + resetMenu.addAction(&resetOffsetsAct); resetAct.setMenu(&resetMenu); ui->tbReset->setDefaultAction(&resetAct); @@ -401,6 +430,8 @@ void PlotControlPanel::setChannelInfoMod connect(&resetAct, &QAction::triggered, model, &ChannelInfoModel::resetInfos); connect(&resetNamesAct, &QAction::triggered, model, &ChannelInfoModel::resetNames); connect(&resetColorsAct, &QAction::triggered, model, &ChannelInfoModel::resetColors); + connect(&resetGainsAct, &QAction::triggered, model, &ChannelInfoModel::resetGains); + connect(&resetOffsetsAct, &QAction::triggered, model, &ChannelInfoModel::resetOffsets); connect(&showAllAct, &QAction::triggered, [model]{model->resetVisibility(true);}); connect(&hideAllAct, &QAction::triggered, [model]{model->resetVisibility(false);}); } diff --git a/src/plotcontrolpanel.h b/src/plotcontrolpanel.h --- a/src/plotcontrolpanel.h +++ b/src/plotcontrolpanel.h @@ -24,6 +24,7 @@ #include #include #include +#include #include "channelinfomodel.h" @@ -70,8 +71,10 @@ private: /// User can disable this setting in the checkbox bool warnNumOfSamples; - QAction resetAct, resetNamesAct, resetColorsAct, showAllAct, hideAllAct; + QAction resetAct, resetNamesAct, resetColorsAct, showAllAct, + hideAllAct, resetGainsAct, resetOffsetsAct; QMenu resetMenu; + QStyledItemDelegate* delegate; /// Show a confirmation dialog before setting #samples to a big value bool askNSConfirmation(int value); diff --git a/src/plotcontrolpanel.ui b/src/plotcontrolpanel.ui --- a/src/plotcontrolpanel.ui +++ b/src/plotcontrolpanel.ui @@ -17,7 +17,7 @@ - + 0 0 @@ -41,14 +41,14 @@ - + 0 0 - 300 + 30000 170 @@ -409,22 +409,6 @@ - - - - Qt::Horizontal - - - QSizePolicy::MinimumExpanding - - - - 1 - 20 - - - - diff --git a/src/samplepack.cpp b/src/samplepack.cpp --- a/src/samplepack.cpp +++ b/src/samplepack.cpp @@ -17,6 +17,7 @@ along with serialplot. If not, see . */ +#include #include #include "samplepack.h" @@ -39,6 +40,15 @@ SamplePack::SamplePack(unsigned ns, unsi } } +SamplePack::SamplePack(const SamplePack& other) : + SamplePack(other.numSamples(), other.numChannels(), other.hasX()) +{ + size_t dataSize = sizeof(double) * numSamples(); + if (hasX()) + memcpy(xData(), other.xData(), dataSize); + memcpy(_yData, other._yData, dataSize * numChannels()); +} + SamplePack::~SamplePack() { delete[] _yData; diff --git a/src/samplepack.h b/src/samplepack.h --- a/src/samplepack.h +++ b/src/samplepack.h @@ -29,6 +29,7 @@ public: * @param x has X channel */ SamplePack(unsigned ns, unsigned nc, bool x = false); + SamplePack(const SamplePack& other); ~SamplePack(); bool hasX() const; diff --git a/src/setting_defines.h b/src/setting_defines.h --- a/src/setting_defines.h +++ b/src/setting_defines.h @@ -71,11 +71,15 @@ const char SG_CustomFrame_Endianness[] = const char SG_CustomFrame_Checksum[] = "checksum"; const char SG_CustomFrame_DebugMode[] = "debugMode"; -// channel manager keys +// channel info keys const char SG_Channels_Channel[] = "channel"; const char SG_Channels_Name[] = "name"; const char SG_Channels_Color[] = "color"; const char SG_Channels_Visible[] = "visible"; +const char SG_Channels_Gain[] = "gain"; +const char SG_Channels_GainEn[] = "gainEnabled"; +const char SG_Channels_Offset[] = "offset"; +const char SG_Channels_OffsetEn[] = "offsetEnabled"; // plot settings keys const char SG_Plot_NumOfSamples[] = "numOfSamples"; diff --git a/src/stream.cpp b/src/stream.cpp --- a/src/stream.cpp +++ b/src/stream.cpp @@ -142,25 +142,73 @@ void Stream::setNumChannels(unsigned nc, Sink::setNumChannels(nc, x); } -void Stream::feedIn(const SamplePack& data) +const SamplePack* Stream::applyGainOffset(const SamplePack& pack) const { - Q_ASSERT(data.numChannels() == numChannels() && - data.hasX() == hasX()); + Q_ASSERT(infoModel()->gainOrOffsetEn()); + + SamplePack* mPack = new SamplePack(pack); + unsigned ns = pack.numSamples(); + + for (unsigned ci = 0; ci < numChannels(); ci++) + { + // TODO: we could use some kind of map (int32, int64 would suffice) to speed things up + bool gainEn = infoModel()->gainEn(ci); + bool offsetEn = infoModel()->offsetEn(ci); + if (gainEn || offsetEn) + { + double* mdata = mPack->data(ci); + + double gain = infoModel()->gain(ci); + double offset = infoModel()->offset(ci); + + if (gainEn) + { + for (unsigned i = 0; i < ns; i++) + { + mdata[i] *= gain; + } + } + if (offsetEn) + { + for (unsigned i = 0; i < ns; i++) + { + mdata[i] += offset; + } + } + } + } + + return mPack; +} + +void Stream::feedIn(const SamplePack& pack) +{ + Q_ASSERT(pack.numChannels() == numChannels() && + pack.hasX() == hasX()); if (_paused) return; - unsigned ns = data.numSamples(); + unsigned ns = pack.numSamples(); if (_hasx) { - static_cast(xData)->addSamples(data.xData(), ns); - } - for (unsigned i = 0; i < numChannels(); i++) - { - static_cast(channels[i]->yData())->addSamples(data.data(i), ns); + static_cast(xData)->addSamples(pack.xData(), ns); } - Sink::feedIn(data); + // modified pack that gain and offset is applied to + const SamplePack* mPack = nullptr; + if (infoModel()->gainOrOffsetEn()) + mPack = applyGainOffset(pack); + for (unsigned ci = 0; ci < numChannels(); ci++) + { + auto buf = static_cast(channels[ci]->yData()); + double* data = (mPack == nullptr) ? pack.data(ci) : mPack->data(ci); + buf->addSamples(data, ns); + } + + Sink::feedIn((mPack == nullptr) ? pack : *mPack); + + if (mPack != nullptr) delete mPack; emit dataAdded(); } diff --git a/src/stream.h b/src/stream.h --- a/src/stream.h +++ b/src/stream.h @@ -69,7 +69,7 @@ public: protected: // implementations for `Sink` virtual void setNumChannels(unsigned nc, bool x); - virtual void feedIn(const SamplePack& data); + virtual void feedIn(const SamplePack& pack); signals: void numChannelsChanged(unsigned value); @@ -98,6 +98,19 @@ private: QList channels; ChannelInfoModel _infoModel; + + /** + * Applies gain and offset to given pack. + * + * Caller is responsible for deleting returned `SamplePack`. + * + * @note Should be called only when gain or offset is enabled. Guard with + * `ChannelInfoModel::gainOrOffsetEn()`. + * + * @param pack input data + * @return modified data + */ + const SamplePack* applyGainOffset(const SamplePack& pack) const; }; diff --git a/tests/test.cpp b/tests/test.cpp --- a/tests/test.cpp +++ b/tests/test.cpp @@ -55,6 +55,30 @@ TEST_CASE("samplepack with X", "[memory] REQUIRE(pack.xData() != nullptr); } +TEST_CASE("samplepack copy", "[memory]") +{ + SamplePack pack(10, 3, true); + + // fill test data + for (int i = 0; i < 10; i++) + { + pack.xData()[i] = i; + pack.data(0)[i] = i+5; + pack.data(1)[i] = i*2; + pack.data(2)[i] = i*3; + } + + SamplePack other = pack; + // compare + for (int i = 0; i < 10; i++) + { + REQUIRE(other.xData()[i] == i); + REQUIRE(other.data(0)[i] == i+5); + REQUIRE(other.data(1)[i] == i*2); + REQUIRE(other.data(2)[i] == i*3); + } +} + TEST_CASE("sink", "[memory, stream]") { TestSink sink;