File indexing completed on 2025-01-26 03:34:20

0001 /*
0002     File                 : XYFourierTransformCurve.cpp
0003     Project              : LabPlot
0004     Description          : A xy-curve defined by a Fourier transform
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2016 Stefan Gerlach <stefan.gerlach@uni.kn>
0007     SPDX-FileCopyrightText: 2017 Alexander Semke <alexander.semke@web.de>
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 /*!
0012   \class XYFourierTransformCurve
0013   \brief A xy-curve defined by a Fourier transform
0014 
0015   \ingroup worksheet
0016 */
0017 
0018 #include "XYFourierTransformCurve.h"
0019 #include "XYFourierTransformCurvePrivate.h"
0020 #include "backend/core/AbstractColumn.h"
0021 #include "backend/core/column/Column.h"
0022 #include "backend/gsl/errors.h"
0023 #include "backend/lib/XmlStreamReader.h"
0024 #include "backend/lib/commandtemplates.h"
0025 #include "backend/lib/macros.h"
0026 
0027 extern "C" {
0028 #include "backend/nsl/nsl_sf_poly.h"
0029 }
0030 
0031 #include <KLocalizedString>
0032 #include <QDebug> // qWarning()
0033 #include <QElapsedTimer>
0034 #include <QIcon>
0035 #include <QThreadPool>
0036 
0037 XYFourierTransformCurve::XYFourierTransformCurve(const QString& name)
0038     : XYAnalysisCurve(name, new XYFourierTransformCurvePrivate(this), AspectType::XYFourierTransformCurve) {
0039 }
0040 
0041 XYFourierTransformCurve::XYFourierTransformCurve(const QString& name, XYFourierTransformCurvePrivate* dd)
0042     : XYAnalysisCurve(name, dd, AspectType::XYFourierTransformCurve) {
0043 }
0044 
0045 // no need to delete the d-pointer here - it inherits from QGraphicsItem
0046 // and is deleted during the cleanup in QGraphicsScene
0047 XYFourierTransformCurve::~XYFourierTransformCurve() = default;
0048 
0049 void XYFourierTransformCurve::recalculate() {
0050     Q_D(XYFourierTransformCurve);
0051     d->recalculate();
0052 }
0053 
0054 /*!
0055     Returns an icon to be used in the project explorer.
0056 */
0057 QIcon XYFourierTransformCurve::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(XYFourierTransformCurve, XYFourierTransformCurve::TransformData, transformData, transformData)
0065 
0066 const XYAnalysisCurve::Result& XYFourierTransformCurve::result() const {
0067     Q_D(const XYFourierTransformCurve);
0068     return d->transformResult;
0069 }
0070 
0071 // ##############################################################################
0072 // #################  setter methods and undo commands ##########################
0073 // ##############################################################################
0074 STD_SETTER_CMD_IMPL_F_S(XYFourierTransformCurve, SetTransformData, XYFourierTransformCurve::TransformData, transformData, recalculate)
0075 void XYFourierTransformCurve::setTransformData(const XYFourierTransformCurve::TransformData& transformData) {
0076     Q_D(XYFourierTransformCurve);
0077     exec(new XYFourierTransformCurveSetTransformDataCmd(d, transformData, ki18n("%1: set transform options and perform the Fourier transform")));
0078 }
0079 
0080 // ##############################################################################
0081 // ######################### Private implementation #############################
0082 // ##############################################################################
0083 XYFourierTransformCurvePrivate::XYFourierTransformCurvePrivate(XYFourierTransformCurve* 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 XYFourierTransformCurvePrivate::~XYFourierTransformCurvePrivate() = default;
0091 
0092 void XYFourierTransformCurvePrivate::resetResults() {
0093     transformResult = XYFourierTransformCurve::TransformResult();
0094 }
0095 
0096 bool XYFourierTransformCurvePrivate::recalculateSpecific(const AbstractColumn* tmpXDataColumn, const AbstractColumn* tmpYDataColumn) {
0097     QElapsedTimer timer;
0098     timer.start();
0099 
0100     // copy all valid data point for the transform to temporary vectors
0101     QVector<double> xdataVector;
0102     QVector<double> ydataVector;
0103     const double xmin = transformData.xRange.first();
0104     const double xmax = transformData.xRange.last();
0105 
0106     int rowCount = std::min(tmpXDataColumn->rowCount(), tmpYDataColumn->rowCount());
0107     for (int row = 0; row < rowCount; ++row) {
0108         // only copy those data where _all_ values (for x and y, if given) are valid
0109         if (std::isnan(tmpXDataColumn->valueAt(row)) || std::isnan(tmpYDataColumn->valueAt(row)) || tmpXDataColumn->isMasked(row)
0110             || tmpYDataColumn->isMasked(row))
0111             continue;
0112 
0113         // only when inside given range
0114         if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) {
0115             xdataVector.append(tmpXDataColumn->valueAt(row));
0116             ydataVector.append(tmpYDataColumn->valueAt(row));
0117         }
0118     }
0119 
0120     // number of data points to transform
0121     auto n = (unsigned int)ydataVector.size();
0122     if (n == 0) {
0123         transformResult.available = true;
0124         transformResult.valid = false;
0125         transformResult.status = i18n("No data points available.");
0126         return true;
0127     }
0128 
0129     double* xdata = xdataVector.data();
0130     double* ydata = ydataVector.data();
0131 
0132     // transform settings
0133     const nsl_sf_window_type windowType = transformData.windowType;
0134     const nsl_dft_result_type type = transformData.type;
0135     const bool twoSided = transformData.twoSided;
0136     const bool shifted = transformData.shifted;
0137     const nsl_dft_xscale xScale = transformData.xScale;
0138 
0139     DEBUG("n =" << n);
0140     DEBUG("window type:" << nsl_sf_window_type_name[windowType]);
0141     DEBUG("type:" << nsl_dft_result_type_name[type]);
0142     DEBUG("scale:" << nsl_dft_xscale_name[xScale]);
0143     DEBUG("two sided:" << twoSided);
0144     DEBUG("shifted:" << shifted);
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     gsl_set_error_handler_off();
0154     int status = nsl_dft_transform_window(ydata, 1, n, twoSided, type, windowType);
0155 
0156     unsigned int N = n;
0157     if (twoSided == false)
0158         N = n / 2;
0159 
0160     switch (xScale) {
0161     case nsl_dft_xscale_frequency:
0162         for (unsigned int i = 0; i < N; i++) {
0163             if (i >= n / 2 && shifted)
0164                 xdata[i] = (n - 1) / (xmax - xmin) * (i / (double)n - 1.);
0165             else
0166                 xdata[i] = (n - 1) * i / (xmax - xmin) / n;
0167         }
0168         break;
0169     case nsl_dft_xscale_index:
0170         for (unsigned int i = 0; i < N; i++) {
0171             if (i >= n / 2 && shifted)
0172                 xdata[i] = (int)i - (int)N;
0173             else
0174                 xdata[i] = i;
0175         }
0176         break;
0177     case nsl_dft_xscale_period: {
0178         double f0 = (n - 1) / (xmax - xmin) / n;
0179         for (unsigned int i = 0; i < N; i++) {
0180             double f = (n - 1) * i / (xmax - xmin) / n;
0181             xdata[i] = 1 / (f + f0);
0182         }
0183         break;
0184     }
0185     }
0186 #ifndef NDEBUG
0187     out = qDebug();
0188     for (unsigned int i = 0; i < N; i++)
0189         out << ydata[i] << '(' << xdata[i] << ')';
0190 #endif
0191 
0192     xVector->resize((int)N);
0193     yVector->resize((int)N);
0194     if (shifted) {
0195         memcpy(xVector->data(), &xdata[n / 2], n / 2 * sizeof(double));
0196         memcpy(&xVector->data()[n / 2], xdata, n / 2 * sizeof(double));
0197         memcpy(yVector->data(), &ydata[n / 2], n / 2 * sizeof(double));
0198         memcpy(&yVector->data()[n / 2], ydata, n / 2 * sizeof(double));
0199     } else {
0200         memcpy(xVector->data(), xdata, N * sizeof(double));
0201         memcpy(yVector->data(), ydata, N * sizeof(double));
0202     }
0203     ///////////////////////////////////////////////////////////
0204 
0205     // write the result
0206     transformResult.available = true;
0207     transformResult.valid = (status == GSL_SUCCESS);
0208     transformResult.status = gslErrorToString(status);
0209     transformResult.elapsedTime = timer.elapsed();
0210 
0211     return true;
0212 }
0213 
0214 // ##############################################################################
0215 // ##################  Serialization/Deserialization  ###########################
0216 // ##############################################################################
0217 //! Save as XML
0218 void XYFourierTransformCurve::save(QXmlStreamWriter* writer) const {
0219     Q_D(const XYFourierTransformCurve);
0220 
0221     writer->writeStartElement(QStringLiteral("xyFourierTransformCurve"));
0222 
0223     // write the base class
0224     XYAnalysisCurve::save(writer);
0225 
0226     // write xy-fourier_transform-curve specific information
0227     // transform data
0228     writer->writeStartElement(QStringLiteral("transformData"));
0229     writer->writeAttribute(QStringLiteral("autoRange"), QString::number(d->transformData.autoRange));
0230     writer->writeAttribute(QStringLiteral("xRangeMin"), QString::number(d->transformData.xRange.first()));
0231     writer->writeAttribute(QStringLiteral("xRangeMax"), QString::number(d->transformData.xRange.last()));
0232     writer->writeAttribute(QStringLiteral("type"), QString::number(d->transformData.type));
0233     writer->writeAttribute(QStringLiteral("twoSided"), QString::number(d->transformData.twoSided));
0234     writer->writeAttribute(QStringLiteral("shifted"), QString::number(d->transformData.shifted));
0235     writer->writeAttribute(QStringLiteral("xScale"), QString::number(d->transformData.xScale));
0236     writer->writeAttribute(QStringLiteral("windowType"), QString::number(d->transformData.windowType));
0237     writer->writeEndElement(); // transformData
0238 
0239     // transform results (generated columns)
0240     writer->writeStartElement(QStringLiteral("transformResult"));
0241     writer->writeAttribute(QStringLiteral("available"), QString::number(d->transformResult.available));
0242     writer->writeAttribute(QStringLiteral("valid"), QString::number(d->transformResult.valid));
0243     writer->writeAttribute(QStringLiteral("status"), d->transformResult.status);
0244     writer->writeAttribute(QStringLiteral("time"), QString::number(d->transformResult.elapsedTime));
0245 
0246     // save calculated columns if available
0247     if (saveCalculations() && d->xColumn && d->yColumn) {
0248         d->xColumn->save(writer);
0249         d->yColumn->save(writer);
0250     }
0251     writer->writeEndElement(); //"transformResult"
0252     writer->writeEndElement(); //"xyFourierTransformCurve"
0253 }
0254 
0255 //! Load from XML
0256 bool XYFourierTransformCurve::load(XmlStreamReader* reader, bool preview) {
0257     Q_D(XYFourierTransformCurve);
0258 
0259     QXmlStreamAttributes attribs;
0260     QString str;
0261 
0262     while (!reader->atEnd()) {
0263         reader->readNext();
0264         if (reader->isEndElement() && reader->name() == QLatin1String("xyFourierTransformCurve"))
0265             break;
0266 
0267         if (!reader->isStartElement())
0268             continue;
0269 
0270         if (reader->name() == QLatin1String("xyAnalysisCurve")) {
0271             if (!XYAnalysisCurve::load(reader, preview))
0272                 return false;
0273         } else if (!preview && reader->name() == QLatin1String("transformData")) {
0274             attribs = reader->attributes();
0275             READ_INT_VALUE("autoRange", transformData.autoRange, bool);
0276             READ_DOUBLE_VALUE("xRangeMin", transformData.xRange.first());
0277             READ_DOUBLE_VALUE("xRangeMax", transformData.xRange.last());
0278             READ_INT_VALUE("type", transformData.type, nsl_dft_result_type);
0279             READ_INT_VALUE("twoSided", transformData.twoSided, bool);
0280             READ_INT_VALUE("shifted", transformData.shifted, bool);
0281             READ_INT_VALUE("xScale", transformData.xScale, nsl_dft_xscale);
0282             READ_INT_VALUE("windowType", transformData.windowType, nsl_sf_window_type);
0283         } else if (!preview && reader->name() == QLatin1String("transformResult")) {
0284             attribs = reader->attributes();
0285             READ_INT_VALUE("available", transformResult.available, int);
0286             READ_INT_VALUE("valid", transformResult.valid, int);
0287             READ_STRING_VALUE("status", transformResult.status);
0288             READ_INT_VALUE("time", transformResult.elapsedTime, int);
0289         } else if (reader->name() == QLatin1String("column")) {
0290             Column* column = new Column(QString(), AbstractColumn::ColumnMode::Double);
0291             if (!column->load(reader, preview)) {
0292                 delete column;
0293                 return false;
0294             }
0295 
0296             if (column->name() == QLatin1String("x"))
0297                 d->xColumn = column;
0298             else if (column->name() == QLatin1String("y"))
0299                 d->yColumn = column;
0300         } else { // unknown element
0301             reader->raiseUnknownElementWarning();
0302             if (!reader->skipToEndElement())
0303                 return false;
0304         }
0305     }
0306 
0307     if (preview)
0308         return true;
0309 
0310     // wait for data to be read before using the pointers
0311     QThreadPool::globalInstance()->waitForDone();
0312 
0313     if (d->xColumn && d->yColumn) {
0314         d->xColumn->setHidden(true);
0315         addChild(d->xColumn);
0316 
0317         d->yColumn->setHidden(true);
0318         addChild(d->yColumn);
0319 
0320         d->xVector = static_cast<QVector<double>*>(d->xColumn->data());
0321         d->yVector = static_cast<QVector<double>*>(d->yColumn->data());
0322 
0323         static_cast<XYCurvePrivate*>(d_ptr)->xColumn = d->xColumn;
0324         static_cast<XYCurvePrivate*>(d_ptr)->yColumn = d->yColumn;
0325 
0326         recalcLogicalPoints();
0327     }
0328 
0329     return true;
0330 }