some experiments with TDR math operations

This commit is contained in:
Jan Käberich 2020-11-28 19:32:18 +01:00
parent 8e661aecd6
commit a7ff3d60fb
19 changed files with 589 additions and 77 deletions

View File

@ -26,6 +26,8 @@ HEADERS += \
Tools/eseries.h \
Tools/impedancematchdialog.h \
Traces/Math/medianfilter.h \
Traces/Math/tdrbandpass.h \
Traces/Math/tdrlowpass.h \
Traces/Math/tracemath.h \
Traces/Math/tracematheditdialog.h \
Traces/fftcomplex.h \
@ -81,6 +83,8 @@ SOURCES += \
Tools/eseries.cpp \
Tools/impedancematchdialog.cpp \
Traces/Math/medianfilter.cpp \
Traces/Math/tdrbandpass.cpp \
Traces/Math/tdrlowpass.cpp \
Traces/Math/tracemath.cpp \
Traces/Math/tracematheditdialog.cpp \
Traces/fftcomplex.cpp \
@ -127,7 +131,9 @@ FORMS += \
Device/manualcontroldialog.ui \
Generator/signalgenwidget.ui \
Tools/impedancematchdialog.ui \
Traces/Math/medianexplanationwidget.ui \
Traces/Math/medianfilterdialog.ui \
Traces/Math/newtracemathdialog.ui \
Traces/Math/tracematheditdialog.ui \
Traces/markerwidget.ui \
Traces/smithchartdialog.ui \

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MedianFilterExplanationWidget</class>
<widget class="QWidget" name="MedianFilterExplanationWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>364</width>
<height>412</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="widget">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Median Filter&lt;/span&gt;&lt;/p&gt;&lt;p&gt;The median filter looks at a number of input samples, sorts them and uses the one in the middle as the output sample. This filters the data and especially eliminates outliers.&lt;/p&gt;&lt;p&gt;Parameters: &lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Kernel Size: the amount of input samples used for each output sample. Higher values result in more aggressive filtering&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Sorting method: Since the input data is complex, there are several options for sorting:&lt;/li&gt;&lt;/ul&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 2;&quot;&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Absolute value&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Phase&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Real&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Imaginary&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -1,15 +1,12 @@
#include "medianfilter.h"
#include "ui_medianfilterdialog.h"
#include "ui_medianexplanationwidget.h"
#include <QMessageBox>
using namespace Math;
using namespace std;
namespace Ui {
class MedianFilterDialog;
}
MedianFilter::MedianFilter()
{
kernelSize = 3;
@ -50,18 +47,26 @@ void MedianFilter::edit()
d->show();
}
QWidget *MedianFilter::createExplanationWidget()
{
auto w = new QWidget();
auto ui = new Ui::MedianFilterExplanationWidget;
ui->setupUi(w);
return w;
}
void MedianFilter::inputSamplesChanged(unsigned int begin, unsigned int end) {
if(data.size() != input->rData().size()) {
data.resize(input->rData().size());
}
if(data.size() > 0) {
auto kernelOffset = (kernelSize-1)/2;
int start = (int) begin - kernelOffset;
unsigned int stop = (int) end + kernelOffset;
int start = (int) begin - (int) kernelOffset;
unsigned int stop = end + kernelOffset;
if(start < 0) {
start = 0;
}
if(stop >= input->rData().size()) {
if(stop > input->rData().size()) {
stop = input->rData().size();
}
@ -71,6 +76,7 @@ void MedianFilter::inputSamplesChanged(unsigned int begin, unsigned int end) {
case Order::Phase: return arg(a) < arg(b);
case Order::Real: return real(a) < real(b);
case Order::Imag: return imag(a) < imag(b);
default: return false;
}
};
@ -82,16 +88,13 @@ void MedianFilter::inputSamplesChanged(unsigned int begin, unsigned int end) {
unsigned int inputSample;
if(kernelOffset > in + out) {
inputSample = 0;
} else if(in + kernelOffset + out >= input->rData().size()) {
} else if(in + out >= input->rData().size() + kernelOffset) {
inputSample = input->rData().size() - 1;
} else {
inputSample = in + out - kernelOffset;
}
auto sample = input->rData().at(inputSample).y;
if(out == (unsigned int) start) {
// this is the first sample to update, fill initial kernel
kernel[in] = sample;
}
kernel[in] = sample;
}
// sort initial kernel
sort(kernel.begin(), kernel.end(), comp);
@ -131,5 +134,6 @@ QString MedianFilter::orderToString(MedianFilter::Order o)
case Order::Phase: return "Phase";
case Order::Real: return "Real";
case Order::Imag: return "Imag";
default: return QString();
}
}

View File

@ -15,6 +15,8 @@ public:
virtual void edit() override;
static QWidget *createExplanationWidget();
public slots:
// a single value of the input data has changed, index determines which sample has changed
virtual void inputSamplesChanged(unsigned int begin, unsigned int end) override;

View File

@ -17,7 +17,7 @@
<string>Median Filter</string>
</property>
<property name="modal">
<bool>false</bool>
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NewTraceMathDialog</class>
<widget class="QDialog" name="NewTraceMathDialog">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>840</width>
<height>596</height>
</rect>
</property>
<property name="windowTitle">
<string>Available math operations</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1">
<item>
<widget class="QListWidget" name="list"/>
</item>
<item>
<widget class="QStackedWidget" name="stack">
<property name="currentIndex">
<number>-1</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>NewTraceMathDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>NewTraceMathDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,85 @@
#include "tdrbandpass.h"
#include "Traces/fftcomplex.h"
#include <QLabel>
using namespace Math;
using namespace std;
TDRBandpass::TDRBandpass()
{
}
TraceMath::DataType TDRBandpass::outputType(TraceMath::DataType inputType)
{
if(inputType == DataType::Frequency) {
return DataType::Time;
} else {
return DataType::Invalid;
}
}
QString TDRBandpass::description()
{
return "TDR (bandpass mode)";
}
void TDRBandpass::edit()
{
// nothing to do for now
}
QWidget *TDRBandpass::createExplanationWidget()
{
return new QLabel("Test");
}
void TDRBandpass::inputSamplesChanged(unsigned int begin, unsigned int end)
{
Q_UNUSED(begin);
const double PI = 3.141592653589793238463;
if(input->rData().size() >= 2) {
// TDR is computationally expensive, only update at the end of sweep
if(end != input->rData().size()) {
// not the end, do nothing
return;
}
// create vector for frequency data
vector<complex<double>> dft(end);
// window data and perform FFTshift ("zero"-bin expected at index 0 but is in the middle of input data)
for(unsigned int i=0;i<end;i++) {
// TODO implement other windows as well
constexpr double alpha0 = 0.54;
auto hamming = alpha0 - (1.0 - alpha0) * cos(2 * PI * i / end);
auto windowed = input->rData()[i].y * hamming;
// swap upper and lower half
if(i < end/2) {
dft[i + (end+1)/2] = windowed;
} else {
dft[i - end/2] = windowed;
}
}
// perform IFT operation on data
Fft::transform(dft, true);
// calculate sample distance
auto freqStep = input->rData()[1].x - input->rData()[0].x;
auto timeStep = 1.0 / (freqStep * end);
// copy DFT data to output data
data.resize(dft.size());
for(unsigned int i=0;i<dft.size();i++) {
data[i].y = dft[i];
data[i].x = i * timeStep;
}
emit outputSamplesChanged(0, data.size());
success();
} else {
// not enough input data
data.clear();
emit outputSamplesChanged(0, 0);
warning("Not enough input samples");
}
}

View File

@ -0,0 +1,25 @@
#ifndef TDRBANDPASS_H
#define TDRBANDPASS_H
#include "tracemath.h"
namespace Math {
class TDRBandpass : public TraceMath
{
public:
TDRBandpass();
DataType outputType(DataType inputType) override;
QString description() override;
void edit() override;
static QWidget* createExplanationWidget();
public slots:
void inputSamplesChanged(unsigned int begin, unsigned int end) override;
};
}
#endif // TDRBANDPASS_H

View File

@ -0,0 +1,96 @@
#include "tdrlowpass.h"
#include "Traces/fftcomplex.h"
#include <QLabel>
using namespace Math;
using namespace std;
TDRLowpass::TDRLowpass()
{
}
TraceMath::DataType TDRLowpass::outputType(TraceMath::DataType inputType)
{
if(inputType == DataType::Frequency) {
return DataType::Time;
} else {
return DataType::Invalid;
}
}
QString TDRLowpass::description()
{
return "TDR (bandpass mode)";
}
void TDRLowpass::edit()
{
// nothing to do for now
}
QWidget *TDRLowpass::createExplanationWidget()
{
return new QLabel("Test");
}
void TDRLowpass::inputSamplesChanged(unsigned int begin, unsigned int end)
{
Q_UNUSED(begin);
if(input->rData().size() >= 2) {
// TDR is computationally expensive, only update at the end of sweep
if(end != input->rData().size()) {
// not the end, do nothing
return;
}
auto steps = input->rData().size();
auto firstStep = input->rData().front().x;
if(firstStep == 0) {
// zero as first step would result in infinite number of points, skip and start with second
firstStep = input->rData()[1].x;
steps--;
}
if(firstStep * steps != input->rData().back().x) {
// data is not available with correct frequency spacing, calculate required steps
steps = input->rData().back().x / firstStep;
}
const double PI = 3.141592653589793238463;
// reserve vector for negative frequenies and DC as well
vector<complex<double>> frequencyDomain(2*steps + 1);
// copy frequencies, use the flipped conjugate for negative part
for(unsigned int i = 1;i<=steps;i++) {
auto S = input->getInterpolatedSample(firstStep * i).y;
constexpr double alpha0 = 0.54;
auto hamming = alpha0 - (1.0 - alpha0) * -cos(PI * i / steps);
S *= hamming;
frequencyDomain[2 * steps - i + 1] = conj(S);
frequencyDomain[i] = S;
}
// use simple extrapolation from lowest two points to extract DC value
auto abs_DC = 2.0 * abs(frequencyDomain[1]) - abs(frequencyDomain[2]);
auto phase_DC = 2.0 * arg(frequencyDomain[1]) - arg(frequencyDomain[2]);
frequencyDomain[0] = polar(abs_DC, phase_DC);
auto fft_bins = frequencyDomain.size();
const double fs = 1.0 / (firstStep * fft_bins);
Fft::transform(frequencyDomain, true);
data.clear();
data.resize(fft_bins);
for(unsigned int i = 0;i<fft_bins;i++) {
data[i].x = fs * i;
data[i].y = frequencyDomain[i] / (double) fft_bins;
}
emit outputSamplesChanged(0, data.size());
success();
} else {
// not enough input data
data.clear();
emit outputSamplesChanged(0, 0);
warning("Not enough input samples");
}
}

View File

@ -0,0 +1,25 @@
#ifndef TDRLOWPASS_H
#define TDRLOWPASS_H
#include "tracemath.h"
namespace Math {
class TDRLowpass : public TraceMath
{
public:
TDRLowpass();
DataType outputType(DataType inputType) override;
QString description() override;
void edit() override;
static QWidget* createExplanationWidget();
public slots:
void inputSamplesChanged(unsigned int begin, unsigned int end) override;
};
}
#endif // TDRBANDPASS_H

View File

@ -1,5 +1,9 @@
#include "tracemath.h"
#include "medianfilter.h"
#include "tdrbandpass.h"
#include "tdrlowpass.h"
TraceMath::TraceMath()
{
input = nullptr;
@ -7,11 +11,73 @@ TraceMath::TraceMath()
error("Invalid input");
}
TraceMath *TraceMath::createMath(TraceMath::Type type)
{
switch(type) {
case Type::MedianFilter:
return new Math::MedianFilter();
case Type::TDRlowpass:
return new Math::TDRLowpass();
case Type::TDRbandpass:
return new Math::TDRBandpass();
default:
return nullptr;
}
}
TraceMath::TypeInfo TraceMath::getInfo(TraceMath::Type type)
{
TypeInfo ret = {};
switch(type) {
case Type::MedianFilter:
ret.name = "Median filter";
ret.explanationWidget = Math::MedianFilter::createExplanationWidget();
break;
case Type::TDRlowpass:
ret.name = "TDR (lowpass)";
ret.explanationWidget = Math::TDRLowpass::createExplanationWidget();
break;
case Type::TDRbandpass:
ret.name = "TDR (bandpass)";
ret.explanationWidget = Math::TDRBandpass::createExplanationWidget();
break;
default:
break;
}
return ret;
}
TraceMath::Data TraceMath::getSample(unsigned int index)
{
return data.at(index);
}
TraceMath::Data TraceMath::getInterpolatedSample(double x)
{
Data ret;
if(data.size() == 0 || x < data.front().x || x > data.back().x) {
ret.y = std::numeric_limits<std::complex<double>>::quiet_NaN();
ret.x = std::numeric_limits<double>::quiet_NaN();
} else {
auto it = lower_bound(data.begin(), data.end(), x, [](const Data &lhs, const double x) -> bool {
return lhs.x < x;
});
if(it->x == x) {
ret = *it;
} else {
// no exact match, needs to interpolate
auto high = *it;
it--;
auto low = *it;
double alpha = (x - low.x) / (high.x - low.x);
ret.y = low.y * (1 - alpha) + high.y * alpha;
ret.x = x;
}
}
return ret;
}
unsigned int TraceMath::numSamples()
{
return data.size();

View File

@ -5,6 +5,42 @@
#include <vector>
#include <complex>
/*
* How to implement a new type of math operation:
* 1. Create your new math operation class by deriving from this class. Put the new class in the namespace
* "Math" to avoid name collisions.
* 2. Implement required virtual functions:
* a. outputType(DataType inputType):
* Indicates what kind of result your math operation creates, depending on the type of its input.
* If the input type is not applicable for your operation (e.g. frequency domain data as input for
* a (forward) DFT, you can return DataType::Invalid
* b. description():
* Return a short, user-readable description how the operation is set up. This will be displayed
* in the math edit dialog.
* c. edit():
* Optional. If your operation has customizable parameters, calling this function should start a
* dialog that allows the user to change these parameters.
* d. inputSamplesChanged(unsigned int begin, unsigned int end)
* This slot gets called whenever the input data has changed. Override it and implement your math
* operation in it. Parameters begin and end indicate which input samples have changed: If, for
* example, only the 2nd and third input values have changed, they are set like this: begin=1 end=3
* CAUTION: the size of the input vector may have changed, check before accessing it.
*
* Emit the signal outputSamplesChanged(unsigned int begin, unsigned int end) after your operation is
* finished. Also call either success(), warning() or error() at the end of this slot, depending on
* whether the operation succeeded:
* success(): everything went well, output data contains valid values
* warning(): something might be wrong (e.g. not enough input samples to create meaningful data, ...).
* Provide a hint by passing a short description string
* error(): something went wrong (called with wrong type of data, mathematical error, ...).
* Provide a hint by passing a short description string
* 3. Add a new type to the Type enum for your operation
* 4. Extend the createMath(Type type) factory function to create an instance of your operation
* 5. Add a static function "createExplanationWidget" which returns a QWidget explaining what your operation does.
* This will be displayed when the user chooses to add a new math operation.
* 6. Extend the function getInfo(Type type) to set a name and create the explanation widget for your operation
*/
class TraceMath : public QObject {
Q_OBJECT
public:
@ -28,7 +64,24 @@ public:
Error,
};
enum class Type {
MedianFilter,
TDRlowpass,
TDRbandpass,
// Add new math operations here, do not explicitly assign values and keep the Last entry at the last position
Last,
};
static TraceMath *createMath(Type type);
class TypeInfo {
public:
QString name;
QWidget *explanationWidget;
};
static TypeInfo getInfo(Type type);
Data getSample(unsigned int index);
Data getInterpolatedSample(double x);
unsigned int numSamples();
// indicate whether this function produces time or frequency domain data

View File

@ -2,6 +2,11 @@
#include "ui_tracematheditdialog.h"
#include <QHeaderView>
#include "ui_newtracemathdialog.h"
namespace Ui {
class NewTraceMathDialog;
}
#include "medianfilter.h"
TraceMathEditDialog::TraceMathEditDialog(Trace &t, QWidget *parent) :
@ -18,6 +23,7 @@ TraceMathEditDialog::TraceMathEditDialog(Trace &t, QWidget *parent) :
headerView->setSectionResizeMode(MathModel::ColIndexDomain, QHeaderView::ResizeToContents);
connect(ui->view->selectionModel(), &QItemSelectionModel::currentRowChanged, [=](const QModelIndex &current, const QModelIndex &previous){
Q_UNUSED(previous)
if(!current.isValid()) {
ui->bDelete->setEnabled(false);
ui->bMoveUp->setEnabled(false);
@ -37,7 +43,30 @@ TraceMathEditDialog::TraceMathEditDialog(Trace &t, QWidget *parent) :
});
connect(ui->bAdd, &QPushButton::clicked, [=](){
model->addOperation(new Math::MedianFilter);
auto d = new QDialog();
auto ui = new Ui::NewTraceMathDialog();
ui->setupUi(d);
for(int i = 0; i < (int) TraceMath::Type::Last;i++) {
auto info = TraceMath::getInfo(static_cast<TraceMath::Type>(i));
ui->list->addItem(info.name);
if(!info.explanationWidget) {
info.explanationWidget = new QWidget();
}
ui->stack->addWidget(info.explanationWidget);
}
// always show the widget for the selected function
connect(ui->list, &QListWidget::currentRowChanged, ui->stack, &QStackedWidget::setCurrentIndex);
connect(ui->buttonBox, &QDialogButtonBox::accepted, [=](){
auto newMath = TraceMath::createMath(static_cast<TraceMath::Type>(ui->list->currentRow()));
if(newMath) {
model->addOperation(newMath);
}
});
ui->list->setCurrentRow(0);
ui->stack->setCurrentIndex(0);
d->show();
});
connect(ui->bDelete, &QPushButton::clicked, [=](){
model->deleteRow(ui->view->currentIndex().row());
@ -45,13 +74,11 @@ TraceMathEditDialog::TraceMathEditDialog(Trace &t, QWidget *parent) :
connect(ui->bMoveUp, &QPushButton::clicked, [&](){
auto index = ui->view->currentIndex();
t.swapMathOrder(index.row() - 1);
model->rowsSwapped(index.row() - 1);
ui->view->setCurrentIndex(index.sibling(index.row() - 1, 0));
});
connect(ui->bMoveDown, &QPushButton::clicked, [&](){
auto index = ui->view->currentIndex();
t.swapMathOrder(index.row());
model->rowsSwapped(index.row());
ui->view->setCurrentIndex(index.sibling(index.row() + 1, 0));
});
}
@ -70,11 +97,13 @@ MathModel::MathModel(Trace &t, QObject *parent)
int MathModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return t.getMathOperations().size();
}
int MathModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return ColIndexLast;
}
@ -168,7 +197,3 @@ void MathModel::deleteRow(unsigned int row)
endRemoveRows();
}
void MathModel::rowsSwapped(unsigned int top)
{
// emit dataChanged(createIndex(top, 0), createIndex(top+1, ColIndexLast - 1));
}

View File

@ -30,7 +30,6 @@ public:
void addOperation(TraceMath *math);
void deleteRow(unsigned int row);
void rowsSwapped(unsigned int top);
private:
Trace &t;

View File

@ -17,7 +17,7 @@
<string>Math functions</string>
</property>
<property name="modal">
<bool>false</bool>
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>

View File

@ -263,6 +263,7 @@ void Trace::updateLastMath(vector<MathInfo>::reverse_iterator start)
lastMath = newLast;
// relay signals of end of math chain
connect(lastMath, &TraceMath::outputSamplesChanged, this, &Trace::dataChanged);
emit outputSamplesChanged(0, data.size());
}
}

View File

@ -17,7 +17,7 @@
<string>Edit Trace</string>
</property>
<property name="modal">
<bool>false</bool>
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>

View File

@ -9,13 +9,13 @@
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="17.410528mm"
height="9.36625mm"
viewBox="0 0 17.410529 9.3662499"
width="9.5429888mm"
height="8.6545076mm"
viewBox="0 0 9.5429888 8.6545077"
version="1.1"
id="svg8"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="math_disabled.svg">
sodipodi:docname="math_disabled.svg"
inkscape:version="0.92.3 (2405546, 2018-03-11)">
<defs
id="defs2" />
<sodipodi:namedview
@ -25,9 +25,9 @@
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6"
inkscape:cx="43.034455"
inkscape:cy="-5.145459"
inkscape:zoom="7.9195959"
inkscape:cx="23.599766"
inkscape:cy="6.7003366"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
@ -52,33 +52,29 @@
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-77.99312,-144.11468)">
transform="translate(-84.809019,-129.3552)">
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:125%;font-family:'Latin Modern Math';-inkscape-font-specification:'Latin Modern Math';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="74.839287"
y="158.66071"
x="85.422623"
y="136.7381"
id="text817"><tspan
sodipodi:role="line"
id="tspan815"
x="74.839287"
y="168.51379"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Latin Modern Math';-inkscape-font-specification:'Latin Modern Math';stroke-width:0.26458332px" /></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:125%;font-family:'Latin Modern Math';-inkscape-font-specification:'Latin Modern Math';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="78.808037"
y="151.29018"
id="text825"><tspan
sodipodi:role="line"
id="tspan823"
x="78.808037"
y="151.29018"
style="font-style:italic;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:FreeSerif;-inkscape-font-specification:'FreeSerif Italic';fill:#969696;fill-opacity:1;stroke-width:0.26458332px">f(x)</tspan></text>
x="85.422623"
y="136.7381"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Padauk Book';-inkscape-font-specification:'Padauk Book, ';stroke-width:0.26458332px">M</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#969696;stroke-width:0.271;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 78.040368,148.85072 h 17.36328"
id="path867"
inkscape:connector-curvature="0" />
style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#fcffff;stroke-width:1.70000005;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 85.314335,137.50494 6.93903,-6.21735 0.812743,-0.72822 0.780583,-0.6994"
id="path819"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 85.314335,137.50494 8.532356,-7.64497"
id="path819-2"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -9,13 +9,13 @@
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16.50322mm"
height="9.36625mm"
viewBox="0 0 16.50322 9.36625"
width="9.5429888mm"
height="8.6545076mm"
viewBox="0 0 9.5429888 8.6545077"
version="1.1"
id="svg8"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="math_enabled.svg">
sodipodi:docname="math_enabled.svg"
inkscape:version="0.92.3 (2405546, 2018-03-11)">
<defs
id="defs2" />
<sodipodi:namedview
@ -25,9 +25,9 @@
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="11.2"
inkscape:cx="43.1742"
inkscape:cy="5.0657458"
inkscape:zoom="7.9195959"
inkscape:cx="23.599766"
inkscape:cy="-28.655003"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
@ -52,28 +52,29 @@
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-77.99312,-144.11468)">
transform="translate(-84.809019,-129.3552)">
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:125%;font-family:'Latin Modern Math';-inkscape-font-specification:'Latin Modern Math';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="74.839287"
y="158.66071"
x="85.422623"
y="136.7381"
id="text817"><tspan
sodipodi:role="line"
id="tspan815"
x="74.839287"
y="168.51379"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Latin Modern Math';-inkscape-font-specification:'Latin Modern Math';stroke-width:0.26458332px" /></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:125%;font-family:'Latin Modern Math';-inkscape-font-specification:'Latin Modern Math';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="78.808037"
y="151.29018"
id="text825"><tspan
sodipodi:role="line"
id="tspan823"
x="78.808037"
y="151.29018"
style="font-style:italic;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:FreeSerif;-inkscape-font-specification:'FreeSerif Italic';stroke-width:0.26458332px">f(x)</tspan></text>
x="85.422623"
y="136.7381"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Padauk Book';-inkscape-font-specification:'Padauk Book, ';stroke-width:0.26458332px">M</tspan></text>
<path
style="opacity:0;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#fcffff;stroke-width:1.70000005;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 85.314335,137.50494 6.93903,-6.21735 0.812743,-0.72822 0.780583,-0.6994"
id="path819"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="opacity:0;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 85.314335,137.50494 8.532356,-7.64497"
id="path819-2"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB