diff --git a/Software/PC_Application/Traces/markerwidget.cpp b/Software/PC_Application/Traces/markerwidget.cpp index 20ac8ae..391d7b8 100644 --- a/Software/PC_Application/Traces/markerwidget.cpp +++ b/Software/PC_Application/Traces/markerwidget.cpp @@ -10,7 +10,7 @@ MarkerWidget::MarkerWidget(TraceMarkerModel &model, QWidget *parent) : ui->tableView->setModel(&model); ui->tableView->setItemDelegateForColumn(TraceMarkerModel::ColIndexTrace, new MarkerTraceDelegate); ui->tableView->setItemDelegateForColumn(TraceMarkerModel::ColIndexType, new MarkerTypeDelegate); - ui->tableView->setItemDelegateForColumn(TraceMarkerModel::ColIndexFreq, new MarkerFrequencyDelegate); + ui->tableView->setItemDelegateForColumn(TraceMarkerModel::ColIndexSettings, new MarkerSettingsDelegate); connect(&model.getModel(), &TraceModel::traceAdded, this, &MarkerWidget::updatePersistentEditors); connect(&model.getModel(), &TraceModel::traceRemoved, this, &MarkerWidget::updatePersistentEditors); diff --git a/Software/PC_Application/Traces/tracemarker.cpp b/Software/PC_Application/Traces/tracemarker.cpp index e674983..9c92f44 100644 --- a/Software/PC_Application/Traces/tracemarker.cpp +++ b/Software/PC_Application/Traces/tracemarker.cpp @@ -16,7 +16,8 @@ TraceMarker::TraceMarker(TraceMarkerModel *model, int number) number(number), data(0), type(Type::Manual), - delta(nullptr) + delta(nullptr), + cutoffAmplitude(-3.0) { } @@ -26,6 +27,7 @@ TraceMarker::~TraceMarker() if(parentTrace) { parentTrace->removeMarker(this); } + deleteHelperMarkers(); emit deleted(this); } @@ -45,6 +47,9 @@ void TraceMarker::assignTrace(Trace *t) constrainFrequency(); updateSymbol(); parentTrace->addMarker(this); + for(auto m : helperMarkers) { + m->assignTrace(t); + } update(); } @@ -74,6 +79,61 @@ QString TraceMarker::readableData() auto phase = arg(valueDiff); return Unit::ToString(freqDiff, "Hz", " kMG") + " / " + QString::number(db, 'g', 4) + "db@" + QString::number(phase*180/M_PI, 'g', 4); } + case Type::Lowpass: + case Type::Highpass: + if(parentTrace->isReflection()) { + return "Calculation not possible with reflection measurement"; + } else { + auto insertionLoss = 20*log10(abs(data)); + auto cutoff = 20*log10(abs(helperMarkers[0]->data)); + QString ret = "fc: "; + if(cutoff > insertionLoss + cutoffAmplitude) { + // the trace never dipped below the specified cutoffAmplitude, exact cutoff frequency unknown + ret += type == Type::Lowpass ? ">" : "<"; + } + ret += Unit::ToString(helperMarkers[0]->frequency, "Hz", " kMG", 4); + ret += ", Ins.Loss: >=" + QString::number(-insertionLoss, 'g', 4) + "db"; + return ret; + } + break; + case Type::Bandpass: + if(parentTrace->isReflection()) { + return "Calculation not possible with reflection measurement"; + } else { + auto insertionLoss = 20*log10(abs(data)); + auto cutoffL = 20*log10(abs(helperMarkers[0]->data)); + auto cutoffH = 20*log10(abs(helperMarkers[1]->data)); + auto bandwidth = helperMarkers[1]->frequency - helperMarkers[0]->frequency; + auto center = helperMarkers[2]->frequency; + QString ret = "fc: "; + if(cutoffL > insertionLoss + cutoffAmplitude || cutoffH > insertionLoss + cutoffAmplitude) { + // the trace never dipped below the specified cutoffAmplitude, center and exact bandwidth unknown + ret += "?, BW: >"; + } else { + ret += Unit::ToString(center, "Hz", " kMG", 5)+ ", BW: "; + } + ret += Unit::ToString(bandwidth, "Hz", " kMG", 4); + ret += ", Ins.Loss: >=" + QString::number(-insertionLoss, 'g', 4) + "db"; + return ret; + } + break; + } +} + +QString TraceMarker::readableSettings() +{ + switch(type) { + case Type::Manual: + case Type::Maximum: + case Type::Minimum: + case Type::Delta: + return Unit::ToString(frequency, "Hz", " kMG", 6); + case Type::Lowpass: + case Type::Highpass: + case Type::Bandpass: + return Unit::ToString(cutoffAmplitude, "db", " ", 3); + default: + return "Invalid"; } } @@ -115,7 +175,7 @@ void TraceMarker::updateSymbol() p.drawConvexPolygon(points, 3); auto brightness = traceColor.redF() * 0.299 + traceColor.greenF() * 0.587 + traceColor.blueF() * 0.114; p.setPen((brightness > 0.6) ? Qt::black : Qt::white); - p.drawText(QRectF(0,0,width, height*2.0/3.0), Qt::AlignCenter, QString::number(number)); + p.drawText(QRectF(0,0,width, height*2.0/3.0), Qt::AlignCenter, QString::number(number) + suffix); emit symbolChanged(this); } @@ -147,6 +207,13 @@ void TraceMarker::assignDeltaMarker(TraceMarker *m) } } +void TraceMarker::deleteHelperMarkers() +{ + for(auto m : helperMarkers) { + delete m; + } + helperMarkers.clear(); +} void TraceMarker::setNumber(int value) { @@ -222,27 +289,63 @@ void TraceMarker::updateTypeFromEditor(QWidget *w) for(auto t : getTypes()) { if(c->currentText() == typeToString(t)) { if(type != t) { + // remove any potential helper markers + deleteHelperMarkers(); type = t; - if(type == Type::Delta && !delta) { - // invalid delta marker assigned, attempt to find a matching marker - for(int pass = 0;pass < 3;pass++) { - for(auto m : model->getMarkers()) { - if(pass == 0 && m->parentTrace != parentTrace) { - // ignore markers on different traces in first pass - continue; + switch(type) { + case Type::Delta: + if(!delta) { + // invalid delta marker assigned, attempt to find a matching marker + for(int pass = 0;pass < 3;pass++) { + for(auto m : model->getMarkers()) { + if(pass == 0 && m->parentTrace != parentTrace) { + // ignore markers on different traces in first pass + continue; + } + if(pass <= 1 && m == this) { + // ignore itself on second pass + continue; + } + assignDeltaMarker(m); + break; } - if(pass <= 1 && m == this) { - // ignore itself on second pass - continue; + if(delta) { + break; } - - assignDeltaMarker(m); - break; - } - if(delta) { - break; } } + break; + case Type::Lowpass: + case Type::Highpass: { + // Create helper marker for cutoff frequency + auto cutoff = new TraceMarker(model); + cutoff->suffix = "c"; + // same trace as this one + cutoff->assignTrace(parentTrace); + helperMarkers.push_back(cutoff); + } + break; + case Type::Bandpass: { + // Create helper markers for cutoff frequency + auto lower = new TraceMarker(model); + lower->suffix = "l"; + // same trace as this one + lower->assignTrace(parentTrace); + helperMarkers.push_back(lower); + auto higher = new TraceMarker(model); + higher->suffix = "h"; + // same trace as this one + higher->assignTrace(parentTrace); + helperMarkers.push_back(higher); + auto center = new TraceMarker(model); + center->suffix = "c"; + // same trace as this one + center->assignTrace(parentTrace); + helperMarkers.push_back(center); + } + break; + default: + break; } emit typeChanged(this); update(); @@ -253,11 +356,44 @@ void TraceMarker::updateTypeFromEditor(QWidget *w) SIUnitEdit *TraceMarker::getSettingsEditor() { - return new SIUnitEdit("Hz", " kMG"); + switch(type) { + case Type::Manual: + case Type::Maximum: + case Type::Minimum: + case Type::Delta: + default: + return new SIUnitEdit("Hz", " kMG"); + case Type::Lowpass: + case Type::Highpass: + return new SIUnitEdit("db", " "); + } +} + +void TraceMarker::adjustSettings(double value) +{ + switch(type) { + case Type::Manual: + case Type::Maximum: + case Type::Minimum: + case Type::Delta: + default: + setFrequency(value); + case Type::Lowpass: + case Type::Highpass: + case Type::Bandpass: + if(value > 0.0) { + value = -value; + } + cutoffAmplitude = value; + } } void TraceMarker::update() { + if(!parentTrace->size()) { + // empty trace, nothing to do + return; + } switch(type) { case Type::Manual: // nothing to do @@ -268,6 +404,82 @@ void TraceMarker::update() case Type::Minimum: setFrequency(parentTrace->findExtremumFreq(false)); break; + case Type::Lowpass: + case Type::Highpass: + if(parentTrace->isReflection()) { + // lowpass/highpass calculation only works with transmission measurement + break; + } else { + // find the maximum + auto peakFreq = parentTrace->findExtremumFreq(true); + // this marker shows the insertion loss + setFrequency(peakFreq); + // find the cutoff frequency + auto index = parentTrace->index(peakFreq); + auto peakAmplitude = 20*log10(abs(parentTrace->sample(index).S)); + auto cutoff = peakAmplitude + cutoffAmplitude; + int inc = type == Type::Lowpass ? 1 : -1; + while(index >= 0 && index < (int) parentTrace->size()) { + auto amplitude = 20*log10(abs(parentTrace->sample(index).S)); + if(amplitude <= cutoff) { + break; + } + index += inc; + } + if(index < 0) { + index = 0; + } else if(index >= (int) parentTrace->size()) { + index = parentTrace->size() - 1; + } + // set position of cutoff marker + helperMarkers[0]->setFrequency(parentTrace->sample(index).frequency); + } + break; + case Type::Bandpass: + if(parentTrace->isReflection()) { + // lowpass/highpass calculation only works with transmission measurement + break; + } else { + // find the maximum + auto peakFreq = parentTrace->findExtremumFreq(true); + // this marker shows the insertion loss + setFrequency(peakFreq); + // find the cutoff frequencies + auto index = parentTrace->index(peakFreq); + auto peakAmplitude = 20*log10(abs(parentTrace->sample(index).S)); + auto cutoff = peakAmplitude + cutoffAmplitude; + + auto low_index = index; + while(low_index >= 0) { + auto amplitude = 20*log10(abs(parentTrace->sample(low_index).S)); + if(amplitude <= cutoff) { + break; + } + low_index--; + } + if(low_index < 0) { + low_index = 0; + } + // set position of cutoff marker + helperMarkers[0]->setFrequency(parentTrace->sample(low_index).frequency); + + auto high_index = index; + while(high_index < (int) parentTrace->size()) { + auto amplitude = 20*log10(abs(parentTrace->sample(high_index).S)); + if(amplitude <= cutoff) { + break; + } + high_index++; + } + if(high_index >= (int) parentTrace->size()) { + high_index = parentTrace->size() - 1; + } + // set position of cutoff marker + helperMarkers[1]->setFrequency(parentTrace->sample(high_index).frequency); + // set center marker inbetween cutoff markers + helperMarkers[2]->setFrequency((helperMarkers[0]->frequency + helperMarkers[1]->frequency) / 2); + } + break; } emit dataChanged(this); } diff --git a/Software/PC_Application/Traces/tracemarker.h b/Software/PC_Application/Traces/tracemarker.h index 6abc989..cc58241 100644 --- a/Software/PC_Application/Traces/tracemarker.h +++ b/Software/PC_Application/Traces/tracemarker.h @@ -18,6 +18,7 @@ public: void assignTrace(Trace *t); Trace* trace(); QString readableData(); + QString readableSettings(); double getFrequency() const; std::complex getData() const; @@ -34,6 +35,7 @@ public: void updateTypeFromEditor(QWidget *c); SIUnitEdit* getSettingsEditor(); + void adjustSettings(double value); // Updates marker position and data on automatic markers. Should be called whenever the tracedata is complete void update(); @@ -59,9 +61,12 @@ private: Maximum, Minimum, Delta, + Lowpass, + Highpass, + Bandpass, }; static std::vector getTypes() { - return {Type::Manual, Type::Maximum, Type::Minimum, Type::Delta}; + return {Type::Manual, Type::Maximum, Type::Minimum, Type::Delta, Type::Lowpass, Type::Highpass, Type::Bandpass}; } static QString typeToString(Type t) { switch(t) { @@ -69,11 +74,15 @@ private: case Type::Maximum: return "Maximum"; case Type::Minimum: return "Minimum"; case Type::Delta: return "Delta"; + case Type::Lowpass: return "Lowpass"; + case Type::Highpass: return "Highpass"; + case Type::Bandpass: return "Bandpass"; default: return QString(); } } void constrainFrequency(); void assignDeltaMarker(TraceMarker *m); + void deleteHelperMarkers(); TraceMarkerModel *model; Trace *parentTrace; @@ -82,8 +91,11 @@ private: std::complex data; QPixmap symbol; Type type; + QString suffix; TraceMarker *delta; + std::vector helperMarkers; + double cutoffAmplitude; }; #endif // TRACEMARKER_H diff --git a/Software/PC_Application/Traces/tracemarkermodel.cpp b/Software/PC_Application/Traces/tracemarkermodel.cpp index 2db85b7..c08e28e 100644 --- a/Software/PC_Application/Traces/tracemarkermodel.cpp +++ b/Software/PC_Application/Traces/tracemarkermodel.cpp @@ -72,7 +72,7 @@ void TraceMarkerModel::markerDataChanged(TraceMarker *m) // only update the other columns, do not override editor data emit dataChanged(index(row, ColIndexData), index(row, ColIndexData)); } else { - emit dataChanged(index(row, ColIndexFreq), index(row, ColIndexData)); + emit dataChanged(index(row, ColIndexSettings), index(row, ColIndexData)); } } @@ -107,9 +107,9 @@ QVariant TraceMarkerModel::data(const QModelIndex &index, int role) const } break; } - case ColIndexFreq: + case ColIndexSettings: switch(role) { - case Qt::DisplayRole: return Unit::ToString(marker->getFrequency(), "Hz", " kMG", 6); break; + case Qt::DisplayRole: return marker->readableSettings(); break; } case ColIndexData: switch(role) { @@ -127,7 +127,7 @@ QVariant TraceMarkerModel::headerData(int section, Qt::Orientation orientation, case ColIndexNumber: return "#"; break; case ColIndexTrace: return "Trace"; break; case ColIndexType: return "Type"; break; - case ColIndexFreq: return "Frequency"; break; + case ColIndexSettings: return "Settings"; break; case ColIndexData: return "Data"; break; default: return QVariant(); break; } @@ -152,11 +152,8 @@ bool TraceMarkerModel::setData(const QModelIndex &index, const QVariant &value, m->assignTrace(trace); } break; - case ColIndexFreq: { - auto newval = Unit::FromString(value.toString(), "Hz", " kMG"); - if(!qIsNaN(newval)) { - m->setFrequency(newval); - } + case ColIndexSettings: { + m->adjustSettings(value.toDouble()); } break; } @@ -171,7 +168,7 @@ Qt::ItemFlags TraceMarkerModel::flags(const QModelIndex &index) const case ColIndexNumber: flags |= Qt::ItemIsEnabled | Qt::ItemIsEditable; break; case ColIndexTrace: flags |= Qt::ItemIsEnabled | Qt::ItemIsEditable; break; case ColIndexType: flags |= Qt::ItemIsEnabled | Qt::ItemIsEditable; break; - case ColIndexFreq: flags |= Qt::ItemIsEnabled | Qt::ItemIsEditable; break; + case ColIndexSettings: flags |= Qt::ItemIsEnabled | Qt::ItemIsEditable; break; case ColIndexData: flags |= Qt::ItemIsEnabled; break; } return (Qt::ItemFlags) flags; @@ -239,18 +236,18 @@ void MarkerTraceDelegate::setModelData(QWidget *editor, QAbstractItemModel *mode markerModel->setData(index, c->itemData(c->currentIndex())); } -QWidget *MarkerFrequencyDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const +QWidget *MarkerSettingsDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { auto model = (TraceMarkerModel*) index.model(); auto marker = model->getMarkers()[index.row()]; marker->editingFrequeny = true; auto e = marker->getSettingsEditor(); e->setParent(parent); - connect(e, &SIUnitEdit::valueUpdated, this, &MarkerFrequencyDelegate::commitData); + connect(e, &SIUnitEdit::valueUpdated, this, &MarkerSettingsDelegate::commitData); return e; } -void MarkerFrequencyDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const +void MarkerSettingsDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { auto markerModel = (TraceMarkerModel*) model; auto marker = markerModel->getMarkers()[index.row()]; diff --git a/Software/PC_Application/Traces/tracemarkermodel.h b/Software/PC_Application/Traces/tracemarkermodel.h index 98c49aa..7f91d2d 100644 --- a/Software/PC_Application/Traces/tracemarkermodel.h +++ b/Software/PC_Application/Traces/tracemarkermodel.h @@ -22,7 +22,7 @@ class MarkerTypeDelegate : public QStyledItemDelegate void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override; }; -class MarkerFrequencyDelegate : public QStyledItemDelegate +class MarkerSettingsDelegate : public QStyledItemDelegate { Q_OBJECT; QWidget *createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const override; @@ -39,7 +39,7 @@ public: ColIndexNumber = 0, ColIndexTrace = 1, ColIndexType = 2, - ColIndexFreq = 3, + ColIndexSettings = 3, ColIndexData = 4, ColIndexLast, };