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