File indexing completed on 2025-01-26 03:34:21
0001 /* 0002 File : XYHilbertTransformCurve.cpp 0003 Project : LabPlot 0004 Description : A xy-curve defined by a Hilbert transform 0005 -------------------------------------------------------------------- 0006 SPDX-FileCopyrightText: 2021 Stefan Gerlach <stefan.gerlach@uni.kn> 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 0010 /*! 0011 \class XYHilbertTransformCurve 0012 \brief A xy-curve defined by a Hilbert transform 0013 0014 \ingroup worksheet 0015 */ 0016 0017 #include "XYHilbertTransformCurve.h" 0018 #include "XYHilbertTransformCurvePrivate.h" 0019 #include "backend/core/AbstractColumn.h" 0020 #include "backend/core/column/Column.h" 0021 #include "backend/gsl/errors.h" 0022 #include "backend/lib/XmlStreamReader.h" 0023 #include "backend/lib/commandtemplates.h" 0024 #include "backend/lib/macros.h" 0025 0026 #include <KLocalizedString> 0027 #include <QDebug> // qWarning() 0028 #include <QElapsedTimer> 0029 #include <QIcon> 0030 #include <QThreadPool> 0031 0032 XYHilbertTransformCurve::XYHilbertTransformCurve(const QString& name) 0033 : XYAnalysisCurve(name, new XYHilbertTransformCurvePrivate(this), AspectType::XYHilbertTransformCurve) { 0034 } 0035 0036 XYHilbertTransformCurve::XYHilbertTransformCurve(const QString& name, XYHilbertTransformCurvePrivate* dd) 0037 : XYAnalysisCurve(name, dd, AspectType::XYHilbertTransformCurve) { 0038 } 0039 0040 // no need to delete the d-pointer here - it inherits from QGraphicsItem 0041 // and is deleted during the cleanup in QGraphicsScene 0042 XYHilbertTransformCurve::~XYHilbertTransformCurve() = default; 0043 0044 void XYHilbertTransformCurve::recalculate() { 0045 Q_D(XYHilbertTransformCurve); 0046 d->recalculate(); 0047 } 0048 0049 const XYAnalysisCurve::Result& XYHilbertTransformCurve::result() const { 0050 Q_D(const XYHilbertTransformCurve); 0051 return d->transformResult; 0052 } 0053 0054 /*! 0055 Returns an icon to be used in the project explorer. 0056 */ 0057 QIcon XYHilbertTransformCurve::icon() const { 0058 return QIcon::fromTheme(QStringLiteral("labplot-xy-fourier-transform-curve")); 0059 } 0060 0061 // ############################################################################## 0062 // ########################## getter methods ################################## 0063 // ############################################################################## 0064 BASIC_SHARED_D_READER_IMPL(XYHilbertTransformCurve, XYHilbertTransformCurve::TransformData, transformData, transformData) 0065 0066 // ############################################################################## 0067 // ################# setter methods and undo commands ########################## 0068 // ############################################################################## 0069 STD_SETTER_CMD_IMPL_F_S(XYHilbertTransformCurve, SetTransformData, XYHilbertTransformCurve::TransformData, transformData, recalculate) 0070 void XYHilbertTransformCurve::setTransformData(const XYHilbertTransformCurve::TransformData& transformData) { 0071 Q_D(XYHilbertTransformCurve); 0072 exec(new XYHilbertTransformCurveSetTransformDataCmd(d, transformData, ki18n("%1: set transform options and perform the Hilbert transform"))); 0073 } 0074 0075 // ############################################################################## 0076 // ######################### Private implementation ############################# 0077 // ############################################################################## 0078 XYHilbertTransformCurvePrivate::XYHilbertTransformCurvePrivate(XYHilbertTransformCurve* owner) 0079 : XYAnalysisCurvePrivate(owner) 0080 , q(owner) { 0081 } 0082 0083 // no need to delete xColumn and yColumn, they are deleted 0084 // when the parent aspect is removed 0085 XYHilbertTransformCurvePrivate::~XYHilbertTransformCurvePrivate() = default; 0086 0087 void XYHilbertTransformCurvePrivate::resetResults() { 0088 transformResult = XYHilbertTransformCurve::TransformResult(); 0089 } 0090 0091 bool XYHilbertTransformCurvePrivate::recalculateSpecific(const AbstractColumn* tmpXDataColumn, const AbstractColumn* tmpYDataColumn) { 0092 DEBUG(Q_FUNC_INFO) 0093 if (!tmpXDataColumn || !tmpYDataColumn) 0094 return false; 0095 0096 QElapsedTimer timer; 0097 timer.start(); 0098 0099 // copy all valid data point for the transform to temporary vectors 0100 QVector<double> xdataVector; 0101 QVector<double> ydataVector; 0102 double xmin, xmax; 0103 if (transformData.autoRange) { 0104 xmin = tmpXDataColumn->minimum(); 0105 xmax = tmpXDataColumn->maximum(); 0106 } else { 0107 xmin = transformData.xRange.first(); 0108 xmax = transformData.xRange.last(); 0109 } 0110 0111 int rowCount = std::min(tmpXDataColumn->rowCount(), tmpYDataColumn->rowCount()); 0112 DEBUG(Q_FUNC_INFO << ", row count = " << rowCount) 0113 DEBUG(Q_FUNC_INFO << ", xmin/xmax = " << xmin << '/' << xmax) 0114 for (int row = 0; row < rowCount; ++row) { 0115 // only copy those data where _all_ values (for x and y, if given) are valid 0116 if (std::isnan(tmpXDataColumn->valueAt(row)) || std::isnan(tmpYDataColumn->valueAt(row)) || tmpXDataColumn->isMasked(row) 0117 || tmpYDataColumn->isMasked(row)) 0118 continue; 0119 0120 // only when inside given range 0121 if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { 0122 xdataVector.append(tmpXDataColumn->valueAt(row)); 0123 ydataVector.append(tmpYDataColumn->valueAt(row)); 0124 } 0125 } 0126 0127 // number of data points to transform 0128 unsigned int n = (unsigned int)ydataVector.size(); 0129 if (n == 0) { 0130 transformResult.available = true; 0131 transformResult.valid = false; 0132 transformResult.status = i18n("No data points available."); 0133 DEBUG(Q_FUNC_INFO << "no data (n = 0)!") 0134 return true; 0135 } 0136 0137 double* xdata = xdataVector.data(); 0138 double* ydata = ydataVector.data(); 0139 0140 // transform settings 0141 const nsl_hilbert_result_type type = transformData.type; 0142 0143 DEBUG("n = " << n); 0144 DEBUG("type:" << nsl_hilbert_result_type_name[type]); 0145 #ifndef NDEBUG 0146 // QDebug out = qDebug(); 0147 // for (unsigned int i = 0; i < n; i++) 0148 // out<<ydata[i]; 0149 #endif 0150 0151 /////////////////////////////////////////////////////////// 0152 // transform with window 0153 // TODO: type 0154 gsl_set_error_handler_off(); 0155 int status = nsl_hilbert_transform(ydata, 1, n, type); 0156 0157 unsigned int N = n; 0158 #ifndef NDEBUG 0159 // out = qDebug(); 0160 // for (unsigned int i = 0; i < N; i++) 0161 // out << ydata[i] << '(' << xdata[i] << ')'; 0162 #endif 0163 0164 xVector->resize((int)N); 0165 yVector->resize((int)N); 0166 memcpy(xVector->data(), xdata, N * sizeof(double)); 0167 memcpy(yVector->data(), ydata, N * sizeof(double)); 0168 /////////////////////////////////////////////////////////// 0169 0170 // write the result 0171 transformResult.available = true; 0172 transformResult.valid = (status == GSL_SUCCESS); 0173 transformResult.status = gslErrorToString(status); 0174 transformResult.elapsedTime = timer.elapsed(); 0175 return true; 0176 } 0177 0178 // ############################################################################## 0179 // ################## Serialization/Deserialization ########################### 0180 // ############################################################################## 0181 //! Save as XML 0182 void XYHilbertTransformCurve::save(QXmlStreamWriter* writer) const { 0183 Q_D(const XYHilbertTransformCurve); 0184 0185 writer->writeStartElement(QStringLiteral("xyHilbertTransformCurve")); 0186 0187 // write the base class 0188 XYAnalysisCurve::save(writer); 0189 0190 // write xy-fourier_transform-curve specific information 0191 // transform data 0192 writer->writeStartElement(QStringLiteral("transformData")); 0193 writer->writeAttribute(QStringLiteral("autoRange"), QString::number(d->transformData.autoRange)); 0194 writer->writeAttribute(QStringLiteral("xRangeMin"), QString::number(d->transformData.xRange.first())); 0195 writer->writeAttribute(QStringLiteral("xRangeMax"), QString::number(d->transformData.xRange.last())); 0196 writer->writeAttribute(QStringLiteral("type"), QString::number(d->transformData.type)); 0197 writer->writeEndElement(); // transformData 0198 0199 // transform results (generated columns) 0200 writer->writeStartElement(QStringLiteral("transformResult")); 0201 writer->writeAttribute(QStringLiteral("available"), QString::number(d->transformResult.available)); 0202 writer->writeAttribute(QStringLiteral("valid"), QString::number(d->transformResult.valid)); 0203 writer->writeAttribute(QStringLiteral("status"), d->transformResult.status); 0204 writer->writeAttribute(QStringLiteral("time"), QString::number(d->transformResult.elapsedTime)); 0205 0206 // save calculated columns if available 0207 if (saveCalculations() && d->xColumn && d->yColumn) { 0208 d->xColumn->save(writer); 0209 d->yColumn->save(writer); 0210 } 0211 writer->writeEndElement(); //"transformResult" 0212 writer->writeEndElement(); //"xyHilbertTransformCurve" 0213 } 0214 0215 //! Load from XML 0216 bool XYHilbertTransformCurve::load(XmlStreamReader* reader, bool preview) { 0217 Q_D(XYHilbertTransformCurve); 0218 0219 QXmlStreamAttributes attribs; 0220 QString str; 0221 0222 while (!reader->atEnd()) { 0223 reader->readNext(); 0224 if (reader->isEndElement() && reader->name() == QLatin1String("xyHilbertTransformCurve")) 0225 break; 0226 0227 if (!reader->isStartElement()) 0228 continue; 0229 0230 if (reader->name() == QLatin1String("xyAnalysisCurve")) { 0231 if (!XYAnalysisCurve::load(reader, preview)) 0232 return false; 0233 } else if (!preview && reader->name() == QLatin1String("transformData")) { 0234 attribs = reader->attributes(); 0235 READ_INT_VALUE("autoRange", transformData.autoRange, bool); 0236 READ_DOUBLE_VALUE("xRangeMin", transformData.xRange.first()); 0237 READ_DOUBLE_VALUE("xRangeMax", transformData.xRange.last()); 0238 READ_INT_VALUE("type", transformData.type, nsl_hilbert_result_type); 0239 } else if (!preview && reader->name() == QLatin1String("transformResult")) { 0240 attribs = reader->attributes(); 0241 READ_INT_VALUE("available", transformResult.available, int); 0242 READ_INT_VALUE("valid", transformResult.valid, int); 0243 READ_STRING_VALUE("status", transformResult.status); 0244 READ_INT_VALUE("time", transformResult.elapsedTime, int); 0245 } else if (reader->name() == QLatin1String("column")) { 0246 Column* column = new Column(QString(), AbstractColumn::ColumnMode::Double); 0247 if (!column->load(reader, preview)) { 0248 delete column; 0249 return false; 0250 } 0251 0252 if (column->name() == QLatin1String("x")) 0253 d->xColumn = column; 0254 else if (column->name() == QLatin1String("y")) 0255 d->yColumn = column; 0256 } else { // unknown element 0257 reader->raiseUnknownElementWarning(); 0258 if (!reader->skipToEndElement()) 0259 return false; 0260 } 0261 } 0262 0263 if (preview) 0264 return true; 0265 0266 // wait for data to be read before using the pointers 0267 QThreadPool::globalInstance()->waitForDone(); 0268 0269 if (d->xColumn && d->yColumn) { 0270 d->xColumn->setHidden(true); 0271 addChild(d->xColumn); 0272 0273 d->yColumn->setHidden(true); 0274 addChild(d->yColumn); 0275 0276 d->xVector = static_cast<QVector<double>*>(d->xColumn->data()); 0277 d->yVector = static_cast<QVector<double>*>(d->yColumn->data()); 0278 0279 static_cast<XYCurvePrivate*>(d_ptr)->xColumn = d->xColumn; 0280 static_cast<XYCurvePrivate*>(d_ptr)->yColumn = d->yColumn; 0281 0282 recalcLogicalPoints(); 0283 } 0284 0285 return true; 0286 }