File indexing completed on 2025-01-26 03:34:16
0001 /* 0002 File : XYConvolutionCurve.cpp 0003 Project : LabPlot 0004 Description : A xy-curve defined by a convolution 0005 -------------------------------------------------------------------- 0006 SPDX-FileCopyrightText: 2018 Stefan Gerlach <stefan.gerlach@uni.kn> 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 0010 /*! 0011 \class XYConvolutionCurve 0012 \brief A xy-curve defined by a convolution 0013 0014 \ingroup worksheet 0015 */ 0016 0017 #include "XYConvolutionCurve.h" 0018 #include "XYConvolutionCurvePrivate.h" 0019 #include "backend/core/column/Column.h" 0020 #include "backend/lib/XmlStreamReader.h" 0021 #include "backend/lib/commandtemplates.h" 0022 #include "backend/lib/macros.h" 0023 0024 #include <KLocalizedString> 0025 #include <QElapsedTimer> 0026 #include <QIcon> 0027 #include <QThreadPool> 0028 0029 #include <gsl/gsl_math.h> 0030 0031 XYConvolutionCurve::XYConvolutionCurve(const QString& name) 0032 : XYAnalysisCurve(name, new XYConvolutionCurvePrivate(this), AspectType::XYConvolutionCurve) { 0033 } 0034 0035 XYConvolutionCurve::XYConvolutionCurve(const QString& name, XYConvolutionCurvePrivate* dd) 0036 : XYAnalysisCurve(name, dd, AspectType::XYConvolutionCurve) { 0037 } 0038 0039 // no need to delete the d-pointer here - it inherits from QGraphicsItem 0040 // and is deleted during the cleanup in QGraphicsScene 0041 XYConvolutionCurve::~XYConvolutionCurve() = default; 0042 0043 void XYConvolutionCurve::recalculate() { 0044 Q_D(XYConvolutionCurve); 0045 d->recalculate(); 0046 } 0047 0048 const XYAnalysisCurve::Result& XYConvolutionCurve::result() const { 0049 Q_D(const XYConvolutionCurve); 0050 return d->convolutionResult; 0051 } 0052 0053 /*! 0054 Returns an icon to be used in the project explorer. 0055 */ 0056 QIcon XYConvolutionCurve::icon() const { 0057 // return QIcon::fromTheme("labplot-xy-convolution-curve");//not available yet 0058 return QIcon::fromTheme(QStringLiteral("labplot-xy-curve")); 0059 } 0060 0061 // ############################################################################## 0062 // ########################## getter methods ################################## 0063 // ############################################################################## 0064 BASIC_SHARED_D_READER_IMPL(XYConvolutionCurve, XYConvolutionCurve::ConvolutionData, convolutionData, convolutionData) 0065 0066 const XYConvolutionCurve::ConvolutionResult& XYConvolutionCurve::convolutionResult() const { 0067 Q_D(const XYConvolutionCurve); 0068 return d->convolutionResult; 0069 } 0070 0071 // ############################################################################## 0072 // ################# setter methods and undo commands ########################## 0073 // ############################################################################## 0074 STD_SETTER_CMD_IMPL_F_S(XYConvolutionCurve, SetConvolutionData, XYConvolutionCurve::ConvolutionData, convolutionData, recalculate) 0075 void XYConvolutionCurve::setConvolutionData(const XYConvolutionCurve::ConvolutionData& convolutionData) { 0076 Q_D(XYConvolutionCurve); 0077 exec(new XYConvolutionCurveSetConvolutionDataCmd(d, convolutionData, ki18n("%1: set options and perform the convolution"))); 0078 } 0079 0080 // ############################################################################## 0081 // ######################### Private implementation ############################# 0082 // ############################################################################## 0083 XYConvolutionCurvePrivate::XYConvolutionCurvePrivate(XYConvolutionCurve* owner) 0084 : XYAnalysisCurvePrivate(owner) 0085 , q(owner) { 0086 } 0087 0088 // no need to delete xColumn and yColumn, they are deleted 0089 // when the parent aspect is removed 0090 XYConvolutionCurvePrivate::~XYConvolutionCurvePrivate() = default; 0091 0092 void XYConvolutionCurvePrivate::resetResults() { 0093 convolutionResult = XYConvolutionCurve::ConvolutionResult(); 0094 } 0095 0096 bool XYConvolutionCurvePrivate::preparationValid(const AbstractColumn* tmpXDataColumn, const AbstractColumn* tmpYDataColumn) { 0097 Q_UNUSED(tmpXDataColumn); 0098 return tmpYDataColumn != nullptr; 0099 } 0100 0101 bool XYConvolutionCurvePrivate::recalculateSpecific(const AbstractColumn* tmpXDataColumn, const AbstractColumn* tmpYDataColumn) { 0102 QElapsedTimer timer; 0103 timer.start(); 0104 0105 // determine the data source columns 0106 const AbstractColumn* tmpY2DataColumn = nullptr; 0107 if (dataSourceType == XYAnalysisCurve::DataSourceType::Spreadsheet) { 0108 // spreadsheet columns as data source 0109 tmpY2DataColumn = y2DataColumn; 0110 } else { 0111 // curve columns as data source 0112 // no y2 column: use standard kernel 0113 } 0114 0115 // copy all valid data point for the convolution to temporary vectors 0116 QVector<double> xdataVector; 0117 QVector<double> ydataVector; 0118 QVector<double> y2dataVector; 0119 0120 double xmin, xmax; 0121 if (tmpXDataColumn && convolutionData.autoRange) { 0122 xmin = tmpXDataColumn->minimum(); 0123 xmax = tmpXDataColumn->maximum(); 0124 } else { 0125 xmin = convolutionData.xRange.first(); 0126 xmax = convolutionData.xRange.last(); 0127 } 0128 0129 // only copy those data where values are valid and in range 0130 if (tmpXDataColumn) { // x-axis present (with possible range) 0131 for (int row = 0; row < tmpXDataColumn->rowCount(); ++row) { 0132 if (tmpXDataColumn->isValid(row) && !tmpXDataColumn->isMasked(row) && tmpYDataColumn->isValid(row) && !tmpYDataColumn->isMasked(row)) { 0133 if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { 0134 xdataVector.append(tmpXDataColumn->valueAt(row)); 0135 ydataVector.append(tmpYDataColumn->valueAt(row)); 0136 } 0137 } 0138 } 0139 } else { // no x-axis: take all valid values 0140 for (int row = 0; row < tmpYDataColumn->rowCount(); ++row) 0141 if (tmpYDataColumn->isValid(row) && !tmpYDataColumn->isMasked(row)) 0142 ydataVector.append(tmpYDataColumn->valueAt(row)); 0143 } 0144 0145 const nsl_conv_kernel_type kernel = convolutionData.kernel; 0146 const size_t kernelSize = convolutionData.kernelSize; 0147 if (tmpY2DataColumn != nullptr) { 0148 for (int row = 0; row < tmpY2DataColumn->rowCount(); ++row) 0149 if (tmpY2DataColumn->isValid(row) && !tmpY2DataColumn->isMasked(row)) 0150 y2dataVector.append(tmpY2DataColumn->valueAt(row)); 0151 DEBUG("kernel = given response"); 0152 } else { 0153 DEBUG("kernel = " << nsl_conv_kernel_name[kernel] << ", size = " << kernelSize); 0154 double* k = new double[kernelSize]; 0155 nsl_conv_standard_kernel(k, kernelSize, kernel); 0156 for (size_t i = 0; i < kernelSize; i++) 0157 y2dataVector.append(k[i]); 0158 delete[] k; 0159 } 0160 0161 const size_t n = (size_t)ydataVector.size(); // number of points for signal 0162 const size_t m = (size_t)y2dataVector.size(); // number of points for response 0163 if (n < 1 || m < 1) { 0164 convolutionResult.available = true; 0165 convolutionResult.valid = false; 0166 convolutionResult.status = i18n("Not enough data points available."); 0167 return true; 0168 } 0169 0170 double* xdata = xdataVector.data(); 0171 double* ydata = ydataVector.data(); 0172 double* y2data = y2dataVector.data(); 0173 0174 // convolution settings 0175 const double samplingInterval = convolutionData.samplingInterval; 0176 const nsl_conv_direction_type direction = convolutionData.direction; 0177 const nsl_conv_type_type type = convolutionData.type; 0178 const nsl_conv_method_type method = convolutionData.method; 0179 const nsl_conv_norm_type norm = convolutionData.normalize; 0180 const nsl_conv_wrap_type wrap = convolutionData.wrap; 0181 0182 DEBUG("signal n = " << n << ", response m = " << m); 0183 DEBUG("sampling interval = " << samplingInterval); 0184 DEBUG("direction = " << nsl_conv_direction_name[direction]); 0185 DEBUG("type = " << nsl_conv_type_name[type]); 0186 DEBUG("method = " << nsl_conv_method_name[method]); 0187 DEBUG("norm = " << nsl_conv_norm_name[norm]); 0188 DEBUG("wrap = " << nsl_conv_wrap_name[wrap]); 0189 0190 /////////////////////////////////////////////////////////// 0191 size_t np; 0192 if (type == nsl_conv_type_linear) 0193 np = n + m - 1; 0194 else 0195 np = GSL_MAX(n, m); 0196 0197 double* out = (double*)malloc(np * sizeof(double)); 0198 int status = nsl_conv_convolution_direction(ydata, n, y2data, m, direction, type, method, norm, wrap, out); 0199 0200 if (direction == nsl_conv_direction_backward) 0201 if (type == nsl_conv_type_linear) 0202 np = abs((int)(n - m)) + 1; 0203 0204 xVector->resize((int)np); 0205 yVector->resize((int)np); 0206 // take given x-axis values or use index 0207 if (tmpXDataColumn != nullptr) { 0208 int size = GSL_MIN(xdataVector.size(), (int)np); 0209 memcpy(xVector->data(), xdata, size * sizeof(double)); 0210 double sampleInterval = (xVector->data()[size - 1] - xVector->data()[0]) / (xdataVector.size() - 1); 0211 DEBUG("xdata size = " << xdataVector.size() << ", np = " << np << ", sample interval = " << sampleInterval); 0212 for (int i = size; i < (int)np; i++) // fill missing values 0213 xVector->data()[i] = xVector->data()[size - 1] + (i - size + 1) * sampleInterval; 0214 } else { // fill with index (starting with 0) 0215 for (size_t i = 0; i < np; i++) 0216 xVector->data()[i] = i * samplingInterval; 0217 } 0218 0219 memcpy(yVector->data(), out, np * sizeof(double)); 0220 free(out); 0221 /////////////////////////////////////////////////////////// 0222 0223 // write the result 0224 convolutionResult.available = true; 0225 convolutionResult.valid = (status == 0); 0226 convolutionResult.status = QString::number(status); 0227 convolutionResult.elapsedTime = timer.elapsed(); 0228 0229 return true; 0230 } 0231 0232 // ############################################################################## 0233 // ################## Serialization/Deserialization ########################### 0234 // ############################################################################## 0235 //! Save as XML 0236 void XYConvolutionCurve::save(QXmlStreamWriter* writer) const { 0237 Q_D(const XYConvolutionCurve); 0238 0239 writer->writeStartElement(QStringLiteral("xyConvolutionCurve")); 0240 0241 // write the base class 0242 XYAnalysisCurve::save(writer); 0243 0244 // write xy-convolution-curve specific information 0245 // convolution data 0246 writer->writeStartElement(QStringLiteral("convolutionData")); 0247 writer->writeAttribute(QStringLiteral("samplingInterval"), QString::number(d->convolutionData.samplingInterval)); 0248 writer->writeAttribute(QStringLiteral("kernel"), QString::number(d->convolutionData.kernel)); 0249 writer->writeAttribute(QStringLiteral("kernelSize"), QString::number(d->convolutionData.kernelSize)); 0250 writer->writeAttribute(QStringLiteral("autoRange"), QString::number(d->convolutionData.autoRange)); 0251 writer->writeAttribute(QStringLiteral("xRangeMin"), QString::number(d->convolutionData.xRange.first())); 0252 writer->writeAttribute(QStringLiteral("xRangeMax"), QString::number(d->convolutionData.xRange.last())); 0253 writer->writeAttribute(QStringLiteral("direction"), QString::number(d->convolutionData.direction)); 0254 writer->writeAttribute(QStringLiteral("type"), QString::number(d->convolutionData.type)); 0255 writer->writeAttribute(QStringLiteral("method"), QString::number(d->convolutionData.method)); 0256 writer->writeAttribute(QStringLiteral("normalize"), QString::number(d->convolutionData.normalize)); 0257 writer->writeAttribute(QStringLiteral("wrap"), QString::number(d->convolutionData.wrap)); 0258 writer->writeEndElement(); // convolutionData 0259 0260 // convolution results (generated columns) 0261 writer->writeStartElement(QStringLiteral("convolutionResult")); 0262 writer->writeAttribute(QStringLiteral("available"), QString::number(d->convolutionResult.available)); 0263 writer->writeAttribute(QStringLiteral("valid"), QString::number(d->convolutionResult.valid)); 0264 writer->writeAttribute(QStringLiteral("status"), d->convolutionResult.status); 0265 writer->writeAttribute(QStringLiteral("time"), QString::number(d->convolutionResult.elapsedTime)); 0266 0267 // save calculated columns if available 0268 if (saveCalculations() && d->xColumn) { 0269 d->xColumn->save(writer); 0270 d->yColumn->save(writer); 0271 } 0272 writer->writeEndElement(); //"convolutionResult" 0273 0274 writer->writeEndElement(); //"xyConvolutionCurve" 0275 } 0276 0277 //! Load from XML 0278 bool XYConvolutionCurve::load(XmlStreamReader* reader, bool preview) { 0279 Q_D(XYConvolutionCurve); 0280 0281 QXmlStreamAttributes attribs; 0282 QString str; 0283 0284 while (!reader->atEnd()) { 0285 reader->readNext(); 0286 if (reader->isEndElement() && reader->name() == QLatin1String("xyConvolutionCurve")) 0287 break; 0288 0289 if (!reader->isStartElement()) 0290 continue; 0291 0292 if (reader->name() == QLatin1String("xyAnalysisCurve")) { 0293 if (!XYAnalysisCurve::load(reader, preview)) 0294 return false; 0295 } else if (!preview && reader->name() == QLatin1String("convolutionData")) { 0296 attribs = reader->attributes(); 0297 READ_DOUBLE_VALUE("samplingInterval", convolutionData.samplingInterval); 0298 READ_INT_VALUE("kernel", convolutionData.kernel, nsl_conv_kernel_type); 0299 READ_INT_VALUE("kernelSize", convolutionData.kernelSize, size_t); 0300 READ_INT_VALUE("autoRange", convolutionData.autoRange, bool); 0301 READ_DOUBLE_VALUE("xRangeMin", convolutionData.xRange.first()); 0302 READ_DOUBLE_VALUE("xRangeMax", convolutionData.xRange.last()); 0303 READ_INT_VALUE("direction", convolutionData.direction, nsl_conv_direction_type); 0304 READ_INT_VALUE("type", convolutionData.type, nsl_conv_type_type); 0305 READ_INT_VALUE("method", convolutionData.method, nsl_conv_method_type); 0306 READ_INT_VALUE("normalize", convolutionData.normalize, nsl_conv_norm_type); 0307 READ_INT_VALUE("wrap", convolutionData.wrap, nsl_conv_wrap_type); 0308 } else if (!preview && reader->name() == QLatin1String("convolutionResult")) { 0309 attribs = reader->attributes(); 0310 READ_INT_VALUE("available", convolutionResult.available, int); 0311 READ_INT_VALUE("valid", convolutionResult.valid, int); 0312 READ_STRING_VALUE("status", convolutionResult.status); 0313 READ_INT_VALUE("time", convolutionResult.elapsedTime, int); 0314 } else if (!preview && reader->name() == QLatin1String("column")) { 0315 Column* column = new Column(QString(), AbstractColumn::ColumnMode::Double); 0316 if (!column->load(reader, preview)) { 0317 delete column; 0318 return false; 0319 } 0320 if (column->name() == QLatin1String("x")) 0321 d->xColumn = column; 0322 else if (column->name() == QLatin1String("y")) 0323 d->yColumn = column; 0324 } else { // unknown element 0325 reader->raiseUnknownElementWarning(); 0326 if (!reader->skipToEndElement()) 0327 return false; 0328 } 0329 } 0330 0331 if (preview) 0332 return true; 0333 0334 // wait for data to be read before using the pointers 0335 QThreadPool::globalInstance()->waitForDone(); 0336 0337 if (d->xColumn && d->yColumn) { 0338 d->xColumn->setHidden(true); 0339 addChild(d->xColumn); 0340 0341 d->yColumn->setHidden(true); 0342 addChild(d->yColumn); 0343 0344 d->xVector = static_cast<QVector<double>*>(d->xColumn->data()); 0345 d->yVector = static_cast<QVector<double>*>(d->yColumn->data()); 0346 0347 static_cast<XYCurvePrivate*>(d_ptr)->xColumn = d->xColumn; 0348 static_cast<XYCurvePrivate*>(d_ptr)->yColumn = d->yColumn; 0349 0350 recalcLogicalPoints(); 0351 } 0352 0353 return true; 0354 }