keep original trace data when de-embedding, toggle de-embedding view for individual traces

This commit is contained in:
Jan Käberich 2022-12-11 20:37:29 +01:00
parent b67275831b
commit 89c9f20d16
12 changed files with 453 additions and 18 deletions

View File

@ -91,11 +91,11 @@ public:
};
static TypeInfo getInfo(Type type);
Data getSample(unsigned int index);
Data getInterpolatedSample(double x);
virtual Data getSample(unsigned int index);
virtual Data getInterpolatedSample(double x);
double getStepResponse(unsigned int index);
double getInterpolatedStepResponse(double x);
unsigned int numSamples();
virtual unsigned int numSamples();
static QString dataTypeToString(DataType type);
static DataType dataTypeFromString(QString s);

View File

@ -74,6 +74,7 @@ void Trace::clear(bool force) {
return;
}
data.clear();
clearDeembedding();
settings.valid = false;
warning("No data");
emit cleared(this);
@ -149,6 +150,39 @@ void Trace::addData(const Trace::Data &d, const VirtualDevice::SASettings &s, in
addData(d, domain, 50.0, index);
}
void Trace::addDeembeddingData(const Trace::Data &d, int index)
{
if(index >= 0) {
// index position specified
if(deembeddingData.size() <= (unsigned int) index) {
deembeddingData.resize(index + 1);
}
deembeddingData[index] = d;
} else {
// no index given, determine position by X-coordinate
// add or replace data in vector while keeping it sorted with increasing frequency
auto lower = lower_bound(deembeddingData.begin(), deembeddingData.end(), d, [](const Data &lhs, const Data &rhs) -> bool {
return lhs.x < rhs.x;
});
// calculate index now because inserting a sample into data might lead to reallocation -> arithmetic on lower not valid anymore
index = lower - deembeddingData.begin();
if(lower == deembeddingData.end()) {
// highest frequency yet, add to vector
deembeddingData.push_back(d);
} else if(lower->x == d.x) {
*lower = d;
} else {
// insert at this position
deembeddingData.insert(lower, d);
}
}
if(deembeddingActive) {
emit outputSamplesChanged(index, index + 1);
}
emit deembeddingChanged();
}
void Trace::setName(QString name) {
_name = name;
emit nameChanged();
@ -265,11 +299,15 @@ QString Trace::fillFromCSV(CSV &csv, unsigned int parameter)
return lastTraceName;
}
void Trace::fillFromDatapoints(std::map<QString, Trace *> traceSet, const std::vector<VirtualDevice::VNAMeasurement> &data)
void Trace::fillFromDatapoints(std::map<QString, Trace *> traceSet, const std::vector<VirtualDevice::VNAMeasurement> &data, bool deembedded)
{
// remove all previous points
for(auto m : traceSet) {
m.second->clear();
if(!deembedded) {
m.second->clear();
} else {
m.second->clearDeembedding();
}
}
// add new points to traces
for(auto d : data) {
@ -279,7 +317,11 @@ void Trace::fillFromDatapoints(std::map<QString, Trace *> traceSet, const std::v
td.y = m.second;
QString measurement = m.first;
if(traceSet.count(measurement)) {
traceSet[measurement]->addData(td, DataType::Frequency);
if(!deembedded) {
traceSet[measurement]->addData(td, DataType::Frequency);
} else {
traceSet[measurement]->addDeembeddingData(td);
}
}
}
}
@ -743,6 +785,17 @@ nlohmann::json Trace::toJSON()
j["data"] = jdata;
}
j["deembeddingActive"] = deembeddingActive;
nlohmann::json jdedata;
for(const auto &d : deembeddingData) {
nlohmann::json jpoint;
jpoint["x"] = d.x;
jpoint["real"] = d.y.real();
jpoint["imag"] = d.y.imag();
jdedata.push_back(jpoint);
}
j["deembeddingData"] = jdedata;
nlohmann::json mathList;
for(auto m : mathOps) {
if(m.math->getType() == Type::Last) {
@ -759,6 +812,7 @@ nlohmann::json Trace::toJSON()
j["math"] = mathList;
j["math_enabled"] = mathEnabled();
return j;
}
@ -785,6 +839,18 @@ void Trace::fromJSON(nlohmann::json j)
data.push_back(d);
}
}
if(j.contains("deembeddingData")) {
// Deembedded data is contained in the json, load now
clearDeembedding();
for(auto jpoint : j["deembeddingData"]) {
Data d;
d.x = jpoint.value("x", 0.0);
d.y = complex<double>(jpoint.value("real", 0.0), jpoint.value("imag", 0.0));
deembeddingData.push_back(d);
}
}
deembeddingActive = j.value("deembeddingActive", false);
if(type == "Live") {
if(j.contains("parameter")) {
if(j["parameter"].type() == nlohmann::json::value_t::string) {
@ -1226,6 +1292,36 @@ unsigned int Trace::size() const
return lastMath->numSamples();
}
bool Trace::isDeembeddingActive()
{
return deembeddingActive;
}
bool Trace::deembeddingAvailable()
{
return deembeddingData.size() > 0;
}
void Trace::setDeembeddingActive(bool active)
{
deembeddingActive = active;
if(deembeddingAvailable()) {
if(active) {
emit outputSamplesChanged(0, deembeddingData.size());
} else {
emit outputSamplesChanged(0, data.size());
}
}
emit deembeddingChanged();
}
void Trace::clearDeembedding()
{
setDeembeddingActive(false);
deembeddingData.clear();
deembeddingChanged();
}
double Trace::minX()
{
if(lastMath->numSamples() > 0) {
@ -1331,6 +1427,60 @@ Trace::Data Trace::sample(unsigned int index, bool getStepResponse) const
return data;
}
Trace::Data Trace::getSample(unsigned int index)
{
if(deembeddingActive && deembeddingAvailable()) {
if(index < deembeddingData.size()) {
return deembeddingData[index];
} else {
TraceMath::Data d;
d.x = 0;
d.y = 0;
return d;
}
} else {
return TraceMath::getSample(index);
}
}
Trace::Data Trace::getInterpolatedSample(double x)
{
if(deembeddingActive && deembeddingAvailable()) {
Data ret;
if(deembeddingData.size() == 0 || x < deembeddingData.front().x || x > deembeddingData.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(deembeddingData.begin(), deembeddingData.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;
} else {
return TraceMath::getInterpolatedSample(x);
}
}
unsigned int Trace::numSamples()
{
if(deembeddingActive && deembeddingAvailable()) {
return deembeddingData.size();
} else {
return TraceMath::numSamples();
}
}
double Trace::getUnwrappedPhase(unsigned int index)
{
if(index >= size()) {

View File

@ -45,11 +45,12 @@ public:
void clear(bool force = false);
void addData(const Data& d, DataType domain, double reference_impedance = 50.0, int index = -1);
void addData(const Data& d, const VirtualDevice::SASettings &s, int index = -1);
void addDeembeddingData(const Data& d, int index = -1);
void setName(QString name);
void setVelocityFactor(double v);
void fillFromTouchstone(Touchstone &t, unsigned int parameter);
QString fillFromCSV(CSV &csv, unsigned int parameter); // returns the suggested trace name (not yet set in member data)
static void fillFromDatapoints(std::map<QString, Trace*> traceSet, const std::vector<VirtualDevice::VNAMeasurement> &data);
static void fillFromDatapoints(std::map<QString, Trace*> traceSet, const std::vector<VirtualDevice::VNAMeasurement> &data, bool deembedded = false);
void fromLivedata(LivedataType type, QString param);
void fromMath();
QString name() { return _name; }
@ -65,6 +66,12 @@ public:
LivedataType liveType() { return _liveType; }
TraceMath::DataType outputType() const { return lastMath->getDataType(); }
unsigned int size() const;
bool isDeembeddingActive();
bool deembeddingAvailable();
void setDeembeddingActive(bool active);
void clearDeembedding();
double minX();
double maxX();
double findExtremum(bool max, double xmin = std::numeric_limits<double>::lowest(), double xmax = std::numeric_limits<double>::max());
@ -82,6 +89,11 @@ public:
};
Data sample(unsigned int index, bool getStepResponse = false) const;
virtual Data getSample(unsigned int index) override;
virtual Data getInterpolatedSample(double x) override;
virtual unsigned int numSamples() override;
double getUnwrappedPhase(unsigned int index);
// returns a (possibly interpolated sample) at a specified frequency/time/power
Data interpolatedSample(double x);
@ -190,6 +202,7 @@ signals:
void dataChanged(unsigned int begin, unsigned int end);
void nameChanged();
void pauseChanged();
void deembeddingChanged();
void colorChanged(Trace *t);
void markerAdded(Marker *m);
void markerRemoved(Marker *m);
@ -266,6 +279,10 @@ private:
bool valid;
} settings;
// de-embedding variables
std::vector<Data> deembeddingData;
bool deembeddingActive;
std::vector<MathInfo> mathOps;
TraceMath *lastMath;
std::vector<double> unwrappedPhase;

View File

@ -32,6 +32,9 @@ void TraceModel::addTrace(Trace *t)
connect(t, &Trace::pauseChanged, [=](){
emit dataChanged(createIndex(0, 0), createIndex(traces.size() - 1, ColIndexLast - 1));
});
connect(t, &Trace::deembeddingChanged, [=](){
emit dataChanged(createIndex(0, 0), createIndex(traces.size() - 1, ColIndexLast - 1));
});
traces.push_back(t);
endInsertRows();
t->setModel(this);
@ -106,6 +109,18 @@ void TraceModel::toggleMath(unsigned int index)
}
}
void TraceModel::toggleDeembedding(unsigned int index)
{
if (index >= traces.size()) {
return;
}
auto trace = traces[index];
if(trace->deembeddingAvailable()) {
trace->setDeembeddingActive(!trace->isDeembeddingActive());
emit dataChanged(createIndex(index, ColIndexDeembedding), createIndex(index, ColIndexDeembedding));
}
}
int TraceModel::rowCount(const QModelIndex &) const
{
return traces.size();
@ -133,6 +148,8 @@ QVariant TraceModel::data(const QModelIndex &index, int role) const
} else {
return QIcon(":/icons/invisible.svg");
}
} else if(role == Qt::ToolTipRole){
return "Toggle Visibility";
} else {
return QVariant();
}
@ -144,6 +161,21 @@ QVariant TraceModel::data(const QModelIndex &index, int role) const
} else {
return QIcon(":/icons/play.svg");
}
} else if(role == Qt::ToolTipRole && trace->canBePaused()){
return "Toggle Play/Pause";
} else {
return QVariant();
}
break;
case ColIndexDeembedding:
if (role == Qt::DecorationRole && trace->deembeddingAvailable()) {
if(trace->isDeembeddingActive()) {
return QIcon(":icons/deembedding_enabled.svg");
} else {
return QIcon(":icons/deembedding_disabled.svg");
}
} else if(role == Qt::ToolTipRole && trace->deembeddingAvailable()){
return "Toggle De-embedding";
} else {
return QVariant();
}
@ -155,6 +187,10 @@ QVariant TraceModel::data(const QModelIndex &index, int role) const
} else {
return QIcon(":icons/math_disabled");
}
} else if(role == Qt::ToolTipRole && trace->hasMathOperations()){
return "Toggle Math Operations";
} else {
return QVariant();
}
break;
case ColIndexName:
@ -175,6 +211,17 @@ std::vector<Trace *> TraceModel::getTraces() const
return traces;
}
std::vector<Trace *> TraceModel::getLiveTraces() const
{
std::vector<Trace*> ret;
for(auto t : traces) {
if(t->getSource() == Trace::Source::Live) {
ret.push_back(t);
}
}
return ret;
}
bool TraceModel::PortExcitationRequired(int port)
{
port++;
@ -233,7 +280,7 @@ void TraceModel::clearLiveData()
}
}
void TraceModel::addVNAData(const VirtualDevice::VNAMeasurement& d, TraceMath::DataType datatype)
void TraceModel::addVNAData(const VirtualDevice::VNAMeasurement& d, TraceMath::DataType datatype, bool deembedded)
{
source = DataSource::VNA;
lastReceivedData = QDateTime::currentDateTimeUtc();
@ -263,7 +310,11 @@ void TraceModel::addVNAData(const VirtualDevice::VNAMeasurement& d, TraceMath::D
// parameter not included in data, skip
continue;
}
t->addData(td, datatype, d.Z0, index);
if(!deembedded) {
t->addData(td, datatype, d.Z0, index);
} else {
t->addDeembeddingData(td, index);
}
}
}
}

View File

@ -20,8 +20,9 @@ public:
enum {
ColIndexVisible = 0,
ColIndexPlayPause = 1,
ColIndexMath = 2,
ColIndexName = 3,
ColIndexDeembedding = 2,
ColIndexMath = 3,
ColIndexName = 4,
ColIndexLast,
};
@ -39,12 +40,14 @@ public:
void toggleVisibility(unsigned int index);
void togglePause(unsigned int index);
void toggleMath(unsigned int index);
void toggleDeembedding(unsigned int index);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
std::vector<Trace*> getTraces() const;
std::vector<Trace*> getLiveTraces() const;
bool PortExcitationRequired(int port);
@ -68,7 +71,7 @@ signals:
public slots:
void clearLiveData();
void addVNAData(const VirtualDevice::VNAMeasurement& d, TraceMath::DataType datatype);
void addVNAData(const VirtualDevice::VNAMeasurement& d, TraceMath::DataType datatype, bool deembedded);
void addSAData(const VirtualDevice::SAMeasurement &d, const VirtualDevice::SASettings &settings);
private:

View File

@ -140,6 +140,8 @@ void TraceWidget::on_view_clicked(const QModelIndex &index)
case TraceModel::ColIndexPlayPause:
model.togglePause(index.row());
break;
case TraceModel::ColIndexDeembedding:
model.toggleDeembedding(index.row());
case TraceModel::ColIndexMath:
model.toggleMath(index.row());
break;

View File

@ -124,7 +124,10 @@ void Deembedding::Deembed(std::map<QString, Trace *> traceSet)
for(auto &p : points) {
Deembed(p);
}
Trace::fillFromDatapoints(traceSet, points);
Trace::fillFromDatapoints(traceSet, points, true);
for(auto t : traceSet) {
t.second->setDeembeddingActive(true);
}
}
}

View File

@ -2,6 +2,7 @@
#include "ui_manualdeembeddingdialog.h"
#include "Traces/sparamtraceselector.h"
#include "CustomWidgets/informationbox.h"
ManualDeembeddingDialog::ManualDeembeddingDialog(const TraceModel &model, Deembedding *deemb) :
ui(new Ui::ManualDeembeddingDialog)
@ -12,6 +13,22 @@ ManualDeembeddingDialog::ManualDeembeddingDialog(const TraceModel &model, Deembe
ui->buttonBox->setEnabled(false);
connect(traceSelector, &SparamTraceSelector::selectionValid, ui->buttonBox, &QDialogButtonBox::setEnabled);
connect(ui->buttonBox, &QDialogButtonBox::accepted, [=]() {
auto traces = traceSelector->getTraces();
bool clearDeembedding = false;
for(auto t : traces) {
if(t.second->deembeddingAvailable()) {
clearDeembedding = InformationBox::AskQuestion("Clear previous de-embedding data?", "At least one of the selected traces "
"has already been de-embedded. Do you want to clear the old de-embedding data before applying the new de-embedding?", true);
break;
}
}
if(clearDeembedding) {
for(auto t : traces) {
if(t.second->deembeddingAvailable()) {
t.second->clearDeembedding();
}
}
}
deemb->Deembed(traceSelector->getTraces());
accept();
});

View File

@ -850,10 +850,6 @@ void VNA::NewDatapoint(VirtualDevice::VNAMeasurement m)
cal.correctMeasurement(m_avg);
if(deembedding_active) {
deembedding.Deembed(m_avg);
}
TraceMath::DataType type;
if(settings.zerospan) {
type = TraceMath::DataType::TimeZeroSpan;
@ -877,7 +873,11 @@ void VNA::NewDatapoint(VirtualDevice::VNAMeasurement m)
}
}
traceModel.addVNAData(m_avg, type);
traceModel.addVNAData(m_avg, type, false);
if(deembedding_active) {
deembedding.Deembed(m_avg);
traceModel.addVNAData(m_avg, type, true);
}
emit dataChanged();
if(m_avg.pointNum == settings.npoints - 1) {
UpdateAverageCount();
@ -1579,6 +1579,14 @@ void VNA::EnableDeembedding(bool enable)
enableDeembeddingAction->blockSignals(true);
enableDeembeddingAction->setChecked(enable);
enableDeembeddingAction->blockSignals(false);
for(auto t : traceModel.getLiveTraces()) {
if(enable) {
t->setDeembeddingActive(true);
} else {
t->clearDeembedding();
}
}
}
void VNA::setAveragingMode(Averaging::Mode mode)

View File

@ -71,5 +71,7 @@
<file>icons/definedShunt.svg</file>
<file>icons/definedThrough.png</file>
<file>icons/definedThrough.svg</file>
<file>icons/deembedding_disabled.svg</file>
<file>icons/deembedding_enabled.svg</file>
</qresource>
</RCC>

View File

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="20mm"
height="20mm"
viewBox="0 0 20 20"
version="1.1"
id="svg5"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01, custom)"
sodipodi:docname="deembedding_disabled.svg"
inkscape:export-filename="deembedding_disabled.svg"
inkscape:export-xdpi="17.1"
inkscape:export-ydpi="17.1"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="8"
inkscape:cx="32.625"
inkscape:cy="38.0625"
inkscape:window-width="1848"
inkscape:window-height="1016"
inkscape:window-x="1992"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2">
<marker
style="overflow:visible"
id="TriangleStart"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="TriangleStart"
markerWidth="3"
markerHeight="5.99999999"
viewBox="0 0 5.3244081 6.15538509"
inkscape:isstock="true"
inkscape:collect="always"
preserveAspectRatio="none">
<path
style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:0.5pt"
d="M 2.885,0 -1.44,2.5 v -5 z"
id="path135" />
</marker>
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#000000;stroke-width:0.396875"
id="rect234"
width="15"
height="1"
x="2.5"
y="16.5" />
<path
style="fill:#000000;stroke-width:0.396875"
d="M 9.7834433,13.650246 V 3.2217677"
id="path453" />
<path
style="display:none;fill:none;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#TriangleStart)"
d="M 10.015208,14.285628 10,6"
id="path1395"
sodipodi:nodetypes="cc" />
<path
style="display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.8;stroke-dasharray:none;stroke-opacity:1"
d="M 11.015525,14.28403 V 7.997396 h 4.994451 L 9.99278,1.9801998 3.9615043,8.0114755 H 8.995516 v 6.2752715 z"
id="path13540" />
<path
style="display:inline;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="M 1.8217445,18.24796 18.5743,1.8171144"
id="path819-2"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="display:inline;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="M 1.8322416,18.193877 18.562413,1.8336946"
id="path819-2-6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="20mm"
height="20mm"
viewBox="0 0 20 20"
version="1.1"
id="svg5"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01, custom)"
sodipodi:docname="deembedding_enabled.svg"
inkscape:export-filename="deembedding_disabled.svg"
inkscape:export-xdpi="17.1"
inkscape:export-ydpi="17.1"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="8"
inkscape:cx="32.625"
inkscape:cy="38.0625"
inkscape:window-width="1848"
inkscape:window-height="1016"
inkscape:window-x="1992"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2">
<marker
style="overflow:visible"
id="TriangleStart"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="TriangleStart"
markerWidth="3"
markerHeight="5.99999999"
viewBox="0 0 5.3244081 6.15538509"
inkscape:isstock="true"
inkscape:collect="always"
preserveAspectRatio="none">
<path
style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:0.5pt"
d="M 2.885,0 -1.44,2.5 v -5 z"
id="path135" />
</marker>
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#000000;stroke-width:0.396875"
id="rect234"
width="15"
height="1"
x="2.5"
y="16.5" />
<path
style="fill:#000000;stroke-width:0.396875"
d="M 9.7834433,13.650246 V 3.2217677"
id="path453" />
<path
style="display:none;fill:none;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#TriangleStart)"
d="M 10.015208,14.285628 10,6"
id="path1395"
sodipodi:nodetypes="cc" />
<path
style="display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.8;stroke-dasharray:none;stroke-opacity:1"
d="M 11.015525,14.28403 V 7.997396 h 4.994451 L 9.99278,1.9801998 3.9615043,8.0114755 H 8.995516 v 6.2752715 z"
id="path13540" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB