File indexing completed on 2024-12-22 04:18:00

0001 /***************************************************************************
0002  *                                                                         *
0003  *   copyright : (C) 2013 The Kst Team                                     *
0004  *                   kst@kde.org                                           *
0005  *                                                                         *
0006  *   This program is free software; you can redistribute it and/or modify  *
0007  *   it under the terms of the GNU General Public License as published by  *
0008  *   the Free Software Foundation; either version 2 of the License, or     *
0009  *   (at your option) any later version.                                   *
0010  *                                                                         *
0011  ***************************************************************************/
0012 
0013 
0014 #include "activitylevel.h"
0015 #include "objectstore.h"
0016 #include "ui_activitylevel.h"
0017 
0018 static const QString& VECTOR_IN = "Vector In";
0019 static const QString& SCALAR_IN_SAMPLING = "Sampling";
0020 static const QString& SCALAR_IN_WINDOWWIDTH = "Window Width";
0021 static const QString& SCALAR_IN_THRESHOLD = "Threshold";
0022 static const QString& VECTOR_OUT_ACTIVITY = "Activity";
0023 static const QString& VECTOR_OUT_REVERSALS = "Nb Reversals";
0024 static const QString& VECTOR_OUT_STDDEV = "Sliding Standard Deviation";
0025 static const QString& VECTOR_OUT_DENOISED = "Denoised Input";
0026 
0027 class ConfigWidgetActivityLevelPlugin : public Kst::DataObjectConfigWidget, public Ui_ActivityLevelConfig {
0028   public:
0029     ConfigWidgetActivityLevelPlugin(QSettings* cfg) : DataObjectConfigWidget(cfg), Ui_ActivityLevelConfig() {
0030       setupUi(this);
0031     }
0032 
0033     ~ConfigWidgetActivityLevelPlugin() {}
0034 
0035     void setObjectStore(Kst::ObjectStore* store) { 
0036       _store = store; 
0037       _vector->setObjectStore(store);
0038       _windowWidth->setObjectStore(store);
0039       _windowWidth->setDefaultValue(3);
0040       _samplingTime->setObjectStore(store);
0041       _samplingTime->setDefaultValue(0.025);
0042       _noiseThreshold->setObjectStore(store);
0043       _noiseThreshold->setDefaultValue(0.2);
0044     }
0045 
0046     void setupSlots(QWidget* dialog) {
0047       if (dialog) {
0048         connect(_vector, SIGNAL(selectionChanged(QString)), dialog, SIGNAL(modified()));
0049         connect(_samplingTime, SIGNAL(selectionChanged(QString)), dialog, SIGNAL(modified()));
0050         connect(_windowWidth, SIGNAL(selectionChanged(QString)), dialog, SIGNAL(modified()));
0051         connect(_noiseThreshold, SIGNAL(selectionChanged(QString)), dialog, SIGNAL(modified()));
0052       }
0053     }
0054 
0055     Kst::VectorPtr selectedVector() { return _vector->selectedVector(); };
0056     void setSelectedVector(Kst::VectorPtr vector) { return _vector->setSelectedVector(vector); };
0057 
0058     Kst::ScalarPtr selectedSamplingTime() { return _samplingTime->selectedScalar(); };
0059     void setSelectedSamplingTime(Kst::ScalarPtr scalar) { return _samplingTime->setSelectedScalar(scalar); };
0060 
0061     Kst::ScalarPtr selectedWindowWidth() { return _windowWidth->selectedScalar(); };
0062     void setSelectedWindowWidth(Kst::ScalarPtr scalar) { return _windowWidth->setSelectedScalar(scalar); };
0063 
0064     Kst::ScalarPtr selectedNoiseThreshold() { return _noiseThreshold->selectedScalar(); };
0065     void setSelectedNoiseThreshold(Kst::ScalarPtr scalar) { return _noiseThreshold->setSelectedScalar(scalar); };
0066 
0067     virtual void setupFromObject(Kst::Object* dataObject) {
0068       if (ActivityLevelSource* source = static_cast<ActivityLevelSource*>(dataObject)) {
0069         setSelectedVector(source->vector());
0070         setSelectedSamplingTime(source->samplingTime());
0071         setSelectedWindowWidth(source->windowWidth());
0072         setSelectedNoiseThreshold(source->noiseThreshold());
0073       }
0074     }
0075 
0076     virtual bool configurePropertiesFromXml(Kst::ObjectStore *store, QXmlStreamAttributes& attrs) {
0077       Q_UNUSED(store);
0078       Q_UNUSED(attrs);
0079 
0080       bool validTag = true;
0081 
0082 //       QStringRef av;
0083 //       av = attrs.value("value");
0084 //       if (!av.isNull()) {
0085 //         _configValue = QVariant(av.toString()).toBool();
0086 //       }
0087 
0088       return validTag;
0089     }
0090 
0091   public slots:
0092     virtual void save() {
0093       if (_cfg) {
0094         _cfg->beginGroup("Activity Level DataObject Plugin");
0095         _cfg->setValue("Input Vector", _vector->selectedVector()->Name());
0096         _cfg->setValue("Input Scalar Sampling Time", _samplingTime->selectedScalar()->Name());
0097         _cfg->setValue("Input Scalar Window Width", _windowWidth->selectedScalar()->Name());
0098         _cfg->setValue("Input Scalar Noise Threshold", _noiseThreshold->selectedScalar()->Name());
0099         _cfg->endGroup();
0100       }
0101     }
0102 
0103     virtual void load() {
0104       if (_cfg && _store) {
0105         _cfg->beginGroup("Activity Level DataObject Plugin");
0106         QString vectorName = _cfg->value("Input Vector").toString();
0107         Kst::Object* object = _store->retrieveObject(vectorName);
0108         Kst::Vector* vector = static_cast<Kst::Vector*>(object);
0109         if (vector) {
0110           setSelectedVector(vector);
0111         }
0112         // Sampling Time
0113         QString scalarName = _cfg->value("Input Scalar Sampling Time").toString();
0114         object = _store->retrieveObject(scalarName);
0115         Kst::Scalar* scalar = static_cast<Kst::Scalar*>(object);
0116         if (scalar) {
0117           setSelectedSamplingTime(scalar);
0118         }
0119         // Window Width
0120         scalarName = _cfg->value("Input Scalar Window Width").toString();
0121         object = _store->retrieveObject(scalarName);
0122         scalar = static_cast<Kst::Scalar*>(object);
0123         if (scalar) {
0124           setSelectedWindowWidth(scalar);
0125         }
0126         // Noise Threshold
0127         scalarName = _cfg->value("Input Scalar Noise Threshold").toString();
0128         object = _store->retrieveObject(scalarName);
0129         scalar = static_cast<Kst::Scalar*>(object);
0130         if (scalar) {
0131           setSelectedNoiseThreshold(scalar);
0132         }
0133         _cfg->endGroup();
0134       }
0135     }
0136 
0137   private:
0138     Kst::ObjectStore *_store;
0139 
0140 };
0141 
0142 
0143 ActivityLevelSource::ActivityLevelSource(Kst::ObjectStore *store)
0144 : Kst::BasicPlugin(store) {
0145 }
0146 
0147 
0148 ActivityLevelSource::~ActivityLevelSource() {
0149 }
0150 
0151 
0152 QString ActivityLevelSource::_automaticDescriptiveName() const {
0153   if (vector()) {
0154     return tr("%1 Activity Level").arg(vector()->descriptiveName());
0155   } else {
0156     return tr("Activity Level");
0157   }
0158 }
0159 
0160 
0161 QString ActivityLevelSource::descriptionTip() const {
0162   QString tip;
0163   tip = tr("Activity Level: %1\n  Sampling Time: %2 (s)\n  Window width: %3 (s)\n  Noise Threshold: %4 \n").
0164                               arg(Name()).arg(samplingTime()->value()).arg(windowWidth()->value()).arg(noiseThreshold()->value());
0165   tip += tr("\nInput: %1").arg(vector()->descriptionTip());
0166   return tip;
0167 }
0168 
0169 
0170 void ActivityLevelSource::change(Kst::DataObjectConfigWidget *configWidget) {
0171   if (ConfigWidgetActivityLevelPlugin* config = static_cast<ConfigWidgetActivityLevelPlugin*>(configWidget)) {
0172     setInputVector(VECTOR_IN, config->selectedVector());
0173     setInputScalar(SCALAR_IN_SAMPLING, config->selectedSamplingTime());
0174     setInputScalar(SCALAR_IN_WINDOWWIDTH, config->selectedWindowWidth());
0175     setInputScalar(SCALAR_IN_THRESHOLD, config->selectedNoiseThreshold());
0176   }
0177 }
0178 
0179 
0180 void ActivityLevelSource::setupOutputs() {
0181   setOutputVector(VECTOR_OUT_ACTIVITY, "");
0182   setOutputVector(VECTOR_OUT_REVERSALS, "");
0183   setOutputVector(VECTOR_OUT_STDDEV, "");
0184   setOutputVector(VECTOR_OUT_DENOISED, "");
0185 }
0186 
0187 
0188 bool ActivityLevelSource::algorithm() {
0189   Kst::VectorPtr inputVector = _inputVectors[VECTOR_IN];
0190   Kst::ScalarPtr samplingTime = _inputScalars[SCALAR_IN_SAMPLING];
0191   Kst::ScalarPtr windowWidth = _inputScalars[SCALAR_IN_WINDOWWIDTH];
0192   Kst::ScalarPtr noiseThreshold = _inputScalars[SCALAR_IN_THRESHOLD];
0193   Kst::VectorPtr outputVectorActivity = _outputVectors[VECTOR_OUT_ACTIVITY];
0194   Kst::VectorPtr outputVectorReversals = _outputVectors[VECTOR_OUT_REVERSALS];
0195   Kst::VectorPtr outputVectorStdDeviation = _outputVectors[VECTOR_OUT_STDDEV];
0196   Kst::VectorPtr outputVectorDenoised = _outputVectors[VECTOR_OUT_DENOISED];
0197 
0198   int i, length;
0199   // Check for consistent values
0200   if (windowWidth->value() < samplingTime->value() || samplingTime->value() == 0.0) {
0201     return false;
0202   }
0203 
0204   int iSamplesForWindow = (int) (windowWidth->value() / samplingTime->value());
0205   double dStandardDeviation = 0.0, dTotal = 0.0, dVariance = 0.0, dSquaredTotal = 0.0;
0206   int iTrendPrevious = 0, iTrend = 0;
0207   double dNbReversals = 0.0; // Compute as a double since we output a vector of doubles anyway
0208   length = inputVector->length();
0209   /* The metric is computed over a couple of seconds, let us compute the corresponding number of samples */
0210   if (iSamplesForWindow > length) {
0211     _errorString = tr("Error: Input vector too short.");
0212     return false;
0213   }
0214   if (iSamplesForWindow < 2) {
0215     _errorString = tr("Error: the window must be broader.");
0216     return false;
0217   }
0218 
0219   /* Array allocations */
0220   outputVectorActivity->resize(length, true);
0221   outputVectorReversals->resize(length, true);
0222   outputVectorStdDeviation->resize(length, true);
0223   outputVectorDenoised->resize(length, true);
0224 
0225 //  /* Requantize to avoid noise creating many unwanted sign changes */
0226 //  if (noiseThreshold->value() > 0.0) {
0227 //    for (i = 0; i < length; ++i) {
0228 //      outputVectorDenoised->raw_V_ptr()[i] = (double) rint( inputVector->value()[i] / noiseThreshold->value() ) * noiseThreshold->value();
0229 //    }
0230 //  }
0231   /* Recompute input data, taking direction changes only when they exceed a given threshold */
0232   if (noiseThreshold->value() > 0.0) {
0233     iTrendPrevious = (inputVector->value()[1]-inputVector->value()[0] > 0) ? 1 : -1;
0234     outputVectorDenoised->raw_V_ptr()[0] = inputVector->value()[0];
0235     bool bFreeze = false;
0236     for (i = 1; i < length; ++i) {
0237       // Update current trend
0238       if (inputVector->value()[i] == inputVector->value()[i-1]) {
0239         iTrend = 0;
0240       } else {
0241         iTrend = ( (inputVector->value()[i]-inputVector->value()[i-1]) > 0) ? 1 : -1;
0242       }
0243       // Check what to do with the value
0244       if ( iTrendPrevious * iTrend >= 0 && !bFreeze) {
0245         outputVectorDenoised->raw_V_ptr()[i] = inputVector->value()[i];
0246         iTrendPrevious = iTrend;
0247       } else { // Change of direction: check whether the delta is significant, otherwise freeze the value
0248         if ( qAbs(inputVector->value()[i] - outputVectorDenoised->raw_V_ptr()[i-1]) >= noiseThreshold->value() ) { // Delta is significant: keep value
0249           outputVectorDenoised->raw_V_ptr()[i] = inputVector->value()[i];
0250           bFreeze = false;
0251           iTrendPrevious = iTrend;
0252         } else {
0253           outputVectorDenoised->raw_V_ptr()[i] = outputVectorDenoised->raw_V_ptr()[i-1];
0254           bFreeze = true;
0255         }
0256       }
0257     }
0258   }
0259 
0260   /* Compute initial values for first windowWidth seconds */
0261   dTotal = outputVectorDenoised->raw_V_ptr()[0] + outputVectorDenoised->raw_V_ptr()[1];
0262   dSquaredTotal += outputVectorDenoised->raw_V_ptr()[0] * outputVectorDenoised->raw_V_ptr()[0] + outputVectorDenoised->raw_V_ptr()[1] * outputVectorDenoised->raw_V_ptr()[1];
0263   outputVectorReversals->raw_V_ptr()[1] = outputVectorReversals->raw_V_ptr()[0] = 0.0;
0264   outputVectorStdDeviation->raw_V_ptr()[1] = outputVectorStdDeviation->raw_V_ptr()[0] = 0.0;
0265   outputVectorActivity->raw_V_ptr()[1] = outputVectorActivity->raw_V_ptr()[0] = 0.0;
0266   for (i = 2; i < iSamplesForWindow; ++i) {
0267     /* Update previous sign if needed */
0268     if (outputVectorDenoised->raw_V_ptr()[i-1] != outputVectorDenoised->raw_V_ptr()[i-2]) {
0269       iTrendPrevious = ( (outputVectorDenoised->raw_V_ptr()[i-1] - outputVectorDenoised->raw_V_ptr()[i-2]) > 0 ) ? 1 : -1;
0270     }
0271     /* Compute current sign */
0272     if (outputVectorDenoised->raw_V_ptr()[i] != outputVectorDenoised->raw_V_ptr()[i-1]) {
0273       iTrend = ( (outputVectorDenoised->raw_V_ptr()[i] - outputVectorDenoised->raw_V_ptr()[i-1]) > 0 ) ? 1 : -1;
0274     } else {
0275       iTrend = 0;
0276     }
0277     /* Check for reversal */
0278     if ( iTrend * iTrendPrevious < 0 ) {
0279       dNbReversals += 1.0;
0280     }
0281     dTotal += outputVectorDenoised->raw_V_ptr()[i];
0282     dSquaredTotal += outputVectorDenoised->raw_V_ptr()[i] * outputVectorDenoised->raw_V_ptr()[i];
0283     /* Store zeros as long as we do not have enough values */
0284     outputVectorReversals->raw_V_ptr()[i] = 0.0;
0285     outputVectorStdDeviation->raw_V_ptr()[i] = 0.0;
0286     outputVectorActivity->raw_V_ptr()[i] = 0.0;
0287   }
0288   dVariance  = 1.0 / ( (double)iSamplesForWindow - 1.0 );
0289   dVariance *= dSquaredTotal - ( dTotal * dTotal / (double)iSamplesForWindow );
0290   if( dVariance > 0.0 ) { // The computation method can have numerical artefacts leading to negative values here!
0291     dStandardDeviation = sqrt( dVariance );
0292   } else {
0293     dVariance = 0.0;
0294     dStandardDeviation = 0.0;
0295   }
0296   /* Now, we can actually store the first useful value (exactly the right number of samples processed) */
0297   outputVectorReversals->raw_V_ptr()[i] = dNbReversals;
0298   outputVectorStdDeviation->raw_V_ptr()[i] = dStandardDeviation;
0299   outputVectorActivity->raw_V_ptr()[i] = dNbReversals * dStandardDeviation;
0300 
0301   /* Finally, update continuously for each new value for the rest of values */
0302   double outgoingValue, outgoingValuePrev, outgoingValueNext, incomingValue, incomingValuePrev, incomingValueNext;
0303   for (i = iSamplesForWindow; i < length; ++i) {
0304     dTotal += outputVectorDenoised->raw_V_ptr()[i] - outputVectorDenoised->raw_V_ptr()[i-iSamplesForWindow];
0305     dSquaredTotal += outputVectorDenoised->raw_V_ptr()[i] * outputVectorDenoised->raw_V_ptr()[i] - outputVectorDenoised->raw_V_ptr()[i-iSamplesForWindow] * outputVectorDenoised->raw_V_ptr()[i-iSamplesForWindow];
0306     dVariance  = 1.0 / ( (double)iSamplesForWindow - 1.0 );
0307     dVariance *= dSquaredTotal - ( dTotal * dTotal / (double)iSamplesForWindow );
0308     if( dVariance > 0.0 ) {
0309       dStandardDeviation = sqrt( dVariance );
0310     } else {
0311       dVariance = 0.0;
0312       dStandardDeviation = 0.0;
0313     }
0314     /* Update the number of reversals, by removing 1 if the outgoing data point was a reversal and adding 1 if the incoming point is one */
0315     outgoingValue = outputVectorDenoised->raw_V_ptr()[i-iSamplesForWindow];
0316     outgoingValuePrev = outputVectorDenoised->raw_V_ptr()[i-iSamplesForWindow-1];
0317     outgoingValueNext = outputVectorDenoised->raw_V_ptr()[i-iSamplesForWindow+1];
0318     incomingValue = outputVectorDenoised->raw_V_ptr()[i];
0319     incomingValuePrev = outputVectorDenoised->raw_V_ptr()[i-1];
0320     if (i == length-1) { // Protect against accessing past the boundary of the vector
0321       incomingValueNext = outputVectorDenoised->raw_V_ptr()[i];
0322     } else {
0323       incomingValueNext = outputVectorDenoised->raw_V_ptr()[i+1];
0324     }
0325     if ( (outgoingValue-outgoingValuePrev)*(outgoingValueNext-outgoingValue) < 0) {
0326       dNbReversals = qMax(dNbReversals - 1.0, double(0.0)); // Avoid getting negative values, which can happen
0327     }
0328     if ( (incomingValue-incomingValuePrev)*(incomingValueNext-incomingValue) < 0) {
0329       dNbReversals += 1.0;
0330     }
0331 
0332     /* Store values */
0333     outputVectorReversals->raw_V_ptr()[i] = dNbReversals;
0334     outputVectorStdDeviation->raw_V_ptr()[i] = dStandardDeviation;
0335     outputVectorActivity->raw_V_ptr()[i] = dNbReversals * dStandardDeviation;
0336   }
0337   return true;
0338 }
0339 
0340 
0341 Kst::VectorPtr ActivityLevelSource::vector() const {
0342   return _inputVectors[VECTOR_IN];
0343 }
0344 
0345 
0346 Kst::ScalarPtr ActivityLevelSource::samplingTime() const {
0347   return _inputScalars[SCALAR_IN_SAMPLING];
0348 }
0349 
0350 
0351 Kst::ScalarPtr ActivityLevelSource::windowWidth() const {
0352   return _inputScalars[SCALAR_IN_WINDOWWIDTH];
0353 }
0354 
0355 
0356 Kst::ScalarPtr ActivityLevelSource::noiseThreshold() const {
0357   return _inputScalars[SCALAR_IN_THRESHOLD];
0358 }
0359 
0360 
0361 QStringList ActivityLevelSource::inputVectorList() const {
0362   return QStringList( VECTOR_IN );
0363 }
0364 
0365 
0366 QStringList ActivityLevelSource::inputScalarList() const {
0367   QStringList scalars( SCALAR_IN_SAMPLING );
0368   scalars += SCALAR_IN_WINDOWWIDTH;
0369   scalars += SCALAR_IN_THRESHOLD;
0370   return scalars;
0371 }
0372 
0373 
0374 QStringList ActivityLevelSource::inputStringList() const {
0375   return QStringList( /*STRING_IN*/ );
0376 }
0377 
0378 
0379 QStringList ActivityLevelSource::outputVectorList() const {
0380   QStringList vectors( VECTOR_OUT_ACTIVITY );
0381   vectors += VECTOR_OUT_REVERSALS;
0382   vectors += VECTOR_OUT_STDDEV;
0383   vectors += VECTOR_OUT_DENOISED;
0384   return vectors;
0385 }
0386 
0387 
0388 QStringList ActivityLevelSource::outputScalarList() const {
0389   return QStringList( /*SCALAR_OUT*/ );
0390 }
0391 
0392 
0393 QStringList ActivityLevelSource::outputStringList() const {
0394   return QStringList( /*STRING_OUT*/ );
0395 }
0396 
0397 
0398 void ActivityLevelSource::saveProperties(QXmlStreamWriter &s) {
0399   Q_UNUSED(s);
0400 //   s.writeAttribute("value", _configValue);
0401 }
0402 
0403 
0404 QString ActivityLevelPlugin::pluginName() const { return tr("Activity Level"); }
0405 QString ActivityLevelPlugin::pluginDescription() const { return tr("Computes the activity level of a signal as the product of standard deviation and number of reversals over a sliding window."); }
0406 
0407 
0408 Kst::DataObject *ActivityLevelPlugin::create(Kst::ObjectStore *store, Kst::DataObjectConfigWidget *configWidget, bool setupInputsOutputs) const {
0409 
0410   if (ConfigWidgetActivityLevelPlugin* config = static_cast<ConfigWidgetActivityLevelPlugin*>(configWidget)) {
0411 
0412     ActivityLevelSource* object = store->createObject<ActivityLevelSource>();
0413 
0414     if (setupInputsOutputs) {
0415       object->setInputScalar(SCALAR_IN_SAMPLING, config->selectedSamplingTime());
0416       object->setInputScalar(SCALAR_IN_WINDOWWIDTH, config->selectedWindowWidth());
0417       object->setInputScalar(SCALAR_IN_THRESHOLD, config->selectedNoiseThreshold());
0418       object->setupOutputs();
0419       object->setInputVector(VECTOR_IN, config->selectedVector());
0420     }
0421 
0422     object->setPluginName(pluginName());
0423 
0424     object->writeLock();
0425     object->registerChange();
0426     object->unlock();
0427 
0428     return object;
0429   }
0430   return 0;
0431 }
0432 
0433 
0434 Kst::DataObjectConfigWidget *ActivityLevelPlugin::configWidget(QSettings *settingsObject) const {
0435   ConfigWidgetActivityLevelPlugin *widget = new ConfigWidgetActivityLevelPlugin(settingsObject);
0436   return widget;
0437 }
0438 
0439 #ifndef QT5
0440 Q_EXPORT_PLUGIN2(kstplugin_ActivityLevelPlugin, ActivityLevelPlugin)
0441 #endif
0442 
0443 // vim: ts=2 sw=2 et