File indexing completed on 2025-01-05 04:12:54

0001 /***************************************************************************
0002  *                                                                         *
0003  *   copyright : (C) 2010 The University of Toronto                        *
0004  *                   sbenton@phsyics.utoronto.ca                           *
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 "lockin.h"
0015 #include "objectstore.h"
0016 #include "ui_lockinconfig.h"
0017 #include "iirfilter.h"
0018 
0019 #include <vector>
0020 #include <complex>
0021 
0022 using std::vector;
0023 typedef std::complex<double> complex;
0024 
0025 //TODO maybe use Qt vector types, and kst filters
0026 //TODO allow reference waveforms other than square wave
0027 //TODO these parameters, and filter cutoffs, and the guess period should be made commandable
0028 const double f_samp = 4.0E6/384.0/104.0;    //sample frequency
0029 
0030 
0031 
0032 static const QString& PLUGIN_NAME = "Lock-In DataObject Plugin";
0033 static const QString& IN_INPUT_VECTOR = "Input Vector";
0034 static const QString& IN_REF_VECTOR = "Reference Vector";
0035 static const QString& OUT_REF_VECTOR_NORM = "Normalized Reference Vector";
0036 static const QString& OUT_LOCKIN_RESULT = "Lock-In Result";
0037 
0038 class ConfigLockInPlugin : public Kst::DataObjectConfigWidget, public Ui_LockInConfig {
0039   public:
0040     ConfigLockInPlugin(QSettings* cfg) : DataObjectConfigWidget(cfg), Ui_LockInConfig() {
0041       _store = 0;
0042       setupUi(this);
0043     }
0044 
0045     ~ConfigLockInPlugin() {}
0046 
0047     void setObjectStore(Kst::ObjectStore* store) { 
0048       _store = store; 
0049       _inputVector->setObjectStore(store);
0050       _refVector->setObjectStore(store);
0051     }
0052 
0053     void setupSlots(QWidget* dialog) {
0054       if (dialog) {
0055         connect(_inputVector, SIGNAL(selectionChanged(QString)), dialog, SIGNAL(modified()));
0056         connect(_refVector, SIGNAL(selectionChanged(QString)), dialog, SIGNAL(modified()));
0057       }
0058     }
0059 
0060     Kst::VectorPtr selectedInputVector() { return _inputVector->selectedVector(); };
0061     void setSelectedInputVector(Kst::VectorPtr vector) { return _inputVector->setSelectedVector(vector); };
0062 
0063     Kst::VectorPtr selectedRefVector() { return _refVector->selectedVector(); };
0064     void setSelectedRefVector(Kst::VectorPtr vector) { return _refVector->setSelectedVector(vector); };
0065 
0066     virtual void setupFromObject(Kst::Object* dataObject) {
0067       if (LockInSource* source = static_cast<LockInSource*>(dataObject)) {
0068         setSelectedInputVector(source->inputVector());
0069         setSelectedRefVector(source->refVector());
0070       }
0071     }
0072 
0073     virtual bool configurePropertiesFromXml(Kst::ObjectStore *store, QXmlStreamAttributes& attrs) {
0074       Q_UNUSED(store);
0075       Q_UNUSED(attrs);
0076 
0077       bool validTag = true;
0078 
0079 //       QStringRef av;
0080 //       av = attrs.value("value");
0081 //       if (!av.isNull()) {
0082 //         _configValue = QVariant(av.toString()).toBool();
0083 //       }
0084 
0085       return validTag;
0086     }
0087 
0088   public slots:
0089     virtual void save() {
0090       if (_cfg) {
0091         _cfg->beginGroup(PLUGIN_NAME);
0092         _cfg->setValue(IN_INPUT_VECTOR, _inputVector->selectedVector()->Name());
0093         _cfg->setValue(IN_REF_VECTOR, _refVector->selectedVector()->Name());
0094         _cfg->endGroup();
0095       }
0096     }
0097 
0098     virtual void load() {
0099       if (_cfg && _store) {
0100         _cfg->beginGroup(PLUGIN_NAME);
0101         QString vectorName = _cfg->value(IN_INPUT_VECTOR).toString();
0102         Kst::Object* object = _store->retrieveObject(vectorName);
0103         Kst::Vector* vector = static_cast<Kst::Vector*>(object);
0104         if (vector) {
0105           setSelectedInputVector(vector);
0106         }
0107         vectorName = _cfg->value(IN_REF_VECTOR).toString();
0108         Kst::Object* object2 = _store->retrieveObject(vectorName);
0109         Kst::Vector* vector2 = static_cast<Kst::Vector*>(object2);
0110         if (vector2) {
0111           setSelectedRefVector(vector2);
0112         }
0113         _cfg->endGroup();
0114       }
0115     }
0116 
0117   private:
0118     Kst::ObjectStore *_store;
0119 
0120 };
0121 
0122 
0123 LockInSource::LockInSource(Kst::ObjectStore *store)
0124 : Kst::BasicPlugin(store) {
0125 }
0126 
0127 
0128 LockInSource::~LockInSource() {
0129 }
0130 
0131 
0132 QString LockInSource::_automaticDescriptiveName() const {
0133   return tr("Lock-In DataObject Plugin");
0134 }
0135 
0136 
0137 void LockInSource::change(Kst::DataObjectConfigWidget *configWidget) {
0138   if (ConfigLockInPlugin* config = static_cast<ConfigLockInPlugin*>(configWidget)) {
0139     setInputVector(IN_INPUT_VECTOR, config->selectedInputVector());
0140     setInputVector(IN_REF_VECTOR, config->selectedRefVector());
0141   }
0142 }
0143 
0144 
0145 void LockInSource::setupOutputs() {
0146   setOutputVector(OUT_REF_VECTOR_NORM, "");
0147   setOutputVector(OUT_LOCKIN_RESULT, "");
0148 }
0149 
0150 /* REALLY BAD CODE
0151  * use two copies of this function, to have independent static variables
0152  */
0153 static complex f_pll(double ref)
0154 {
0155   static double period = 20.0;
0156   static double phase = 0.0;
0157 
0158   static double T = 0.0;
0159 
0160   static int ref_edge = 0;
0161   static int pll_edge = 0;
0162 
0163   static double ph = 0.0;
0164   static double dph = 0.0;
0165 
0166   static BesselHP1<double> fref(0.0125/f_samp);
0167   static BesselLP1<double> fperiod(0.15/f_samp);
0168   static BesselLP1<double> fphase(0.15/f_samp);
0169 
0170   double ref_ = fref(ref);
0171   ph += dph;
0172   T  += 1.0;
0173   double pll_ref = cos(ph);
0174 
0175   if(ref_edge == 0  && ref_ >= 0.0) {
0176     period = fperiod(T);
0177     ref_edge = 1;
0178     dph = (2.0*M_PI)/period;
0179     T =  0.0;
0180   } else if(ref_edge == 1 && ref_ < 0.0 && T > 1.0) {
0181     ref_edge = 0;
0182   }
0183 
0184 
0185   if(pll_edge == 0 && (pll_ref >= 0.0)) {
0186     phase = fphase(T*dph);
0187     pll_edge = 1;
0188   } else if(pll_edge == 1 && (pll_ref < 0.0)) {
0189     pll_edge = 0;
0190   }
0191 
0192   return complex( ((cos(ph+phase) > 0.0) ? 1 : -1),
0193          ((sin(ph+phase) > 0.0) ? 1 : -1) );
0194 }
0195 
0196 static complex b_pll(double ref)
0197 {
0198   static double period = 20.0;
0199   static double phase = 0.0;
0200 
0201   static double T = 0.0;
0202 
0203   static int ref_edge = 0;
0204   static int pll_edge = 0;
0205 
0206   static double ph = 0.0;
0207   static double dph = 0.0;
0208 
0209   static BesselHP1<double> fref(0.0125/f_samp);
0210   static BesselLP1<double> fperiod(0.15/f_samp);
0211   static BesselLP1<double> fphase(0.15/f_samp);
0212 
0213   double ref_ = fref(ref);
0214   ph += dph;
0215   T  += 1.0;
0216   double pll_ref = cos(ph);
0217 
0218   if(ref_edge == 0  && ref_ >= 0.0) {
0219     period = fperiod(T);
0220     ref_edge = 1;
0221     dph = (2.0*M_PI)/period;
0222     T =  0.0;
0223   } else if(ref_edge == 1 && ref_ < 0.0 && T > 1.0) {
0224     ref_edge = 0;
0225   }
0226 
0227 
0228   if(pll_edge == 0 && (pll_ref >= 0.0)) {
0229     phase = fphase(T*dph);
0230     pll_edge = 1;
0231   } else if(pll_edge == 1 && (pll_ref < 0.0)) {
0232     pll_edge = 0;
0233   }
0234 
0235   return complex( ((cos(ph+phase) > 0.0) ? 1 : -1),
0236          ((sin(ph+phase) > 0.0) ? 1 : -1) );
0237 }
0238 
0239 bool LockInSource::algorithm() {
0240   Kst::VectorPtr inputVector = _inputVectors[IN_INPUT_VECTOR];
0241   Kst::VectorPtr refVector = _inputVectors[IN_REF_VECTOR];
0242   Kst::VectorPtr normRefVector = _outputVectors[OUT_REF_VECTOR_NORM];
0243   Kst::VectorPtr lockinResult = _outputVectors[OUT_LOCKIN_RESULT];
0244 
0245   if (inputVector->length() <= 0 || refVector->length() <= 0 || inputVector->length() != refVector->length()) {
0246     _errorString = tr("Error:  Input Vectors - invalid size");
0247     return false;
0248   }
0249 
0250   int iLength = inputVector->length();
0251 
0252   normRefVector->resize(iLength, false);
0253   lockinResult->resize(iLength, false);
0254 
0255   double const * pdInput = inputVector->value();
0256   double const * pdRef = refVector->value();
0257   double * pdNormRef = normRefVector->raw_V_ptr();
0258   double * pdResult = lockinResult->raw_V_ptr();
0259 
0260   /* to remove initial settling, evaluate the result separately in forward and backward direction */
0261   BesselHP1<double> f_filt_d(0.15/f_samp);
0262   BesselLP4<complex> f_filt_li(0.39/f_samp);
0263   BesselHP1<double> b_filt_d(0.15/f_samp);
0264   BesselLP4<complex> b_filt_li(0.39/f_samp);
0265 
0266   for (int i=0; i<iLength; i++) {
0267     complex f_pll_ref = f_pll(pdRef[i]);
0268     double f_result = abs(f_filt_li(f_filt_d(pdInput[i])*f_pll_ref));
0269     complex b_pll_ref = b_pll(pdRef[iLength-i-1]);
0270     double b_result = abs(b_filt_li(b_filt_d(pdInput[iLength-i-1])*b_pll_ref));
0271     //combine the forwards and backwards data into result
0272     if (i >= iLength/2) {
0273       if (i >= (iLength*3)/4) {
0274         //for first and last quarter of the domain, use backward and forward result
0275         pdResult[i] = f_result;
0276         pdResult[iLength-i-1] = b_result;
0277       } else {
0278         //for middle half, use weight that changes linearly between the extremes
0279         double weight = 2.0*double(i)/double(iLength) - 0.5;
0280         pdResult[i] = weight*f_result + (1.0-weight)*b_result;
0281         pdResult[iLength-i-1] = weight*b_result + (1.0-weight)*f_result;
0282       }
0283       //for the reference, just split the result at the middle
0284       pdNormRef[i] = f_pll_ref.real();
0285       pdNormRef[iLength-i-1] = b_pll_ref.real();
0286     }
0287   }
0288 
0289   return true;
0290 }
0291 
0292 
0293 Kst::VectorPtr LockInSource::inputVector() const {
0294   return _inputVectors[IN_INPUT_VECTOR];
0295 }
0296 
0297 
0298 Kst::VectorPtr LockInSource::refVector() const {
0299   return _inputVectors[IN_REF_VECTOR];
0300 }
0301 
0302 
0303 QStringList LockInSource::inputVectorList() const {
0304   QStringList vectors(IN_INPUT_VECTOR);
0305   vectors += IN_REF_VECTOR;
0306   return vectors;
0307 }
0308 
0309 
0310 QStringList LockInSource::inputScalarList() const {
0311   return QStringList( /*SCALAR_IN*/ );
0312 }
0313 
0314 
0315 QStringList LockInSource::inputStringList() const {
0316   return QStringList( /*STRING_IN*/ );
0317 }
0318 
0319 
0320 QStringList LockInSource::outputVectorList() const {
0321   QStringList vectors(OUT_LOCKIN_RESULT);
0322   vectors += OUT_REF_VECTOR_NORM;
0323   return vectors;
0324 }
0325 
0326 
0327 QStringList LockInSource::outputScalarList() const {
0328   return QStringList( /*SCALAR_OUT*/ );
0329 }
0330 
0331 
0332 QStringList LockInSource::outputStringList() const {
0333   return QStringList( /*STRING_OUT*/ );
0334 }
0335 
0336 
0337 void LockInSource::saveProperties(QXmlStreamWriter &s) {
0338   Q_UNUSED(s);
0339 //   s.writeAttribute("value", _configValue);
0340 }
0341 
0342 
0343 QString LockInPlugin::pluginName() const { return tr("Lock-In"); }
0344 QString LockInPlugin::pluginDescription() const { return tr("Lock-In amplifies Input using a Reference carrier wave"); }
0345 
0346 
0347 Kst::DataObject *LockInPlugin::create(Kst::ObjectStore *store, Kst::DataObjectConfigWidget *configWidget, bool setupInputsOutputs) const {
0348 
0349   if (ConfigLockInPlugin* config = static_cast<ConfigLockInPlugin*>(configWidget)) {
0350 
0351     LockInSource* object = store->createObject<LockInSource>();
0352 
0353     if (setupInputsOutputs) {
0354       object->setupOutputs();
0355       object->setInputVector(IN_INPUT_VECTOR, config->selectedInputVector());
0356       object->setInputVector(IN_REF_VECTOR, config->selectedRefVector());
0357     }
0358 
0359     object->setPluginName(pluginName());
0360 
0361     object->writeLock();
0362     object->registerChange();
0363     object->unlock();
0364 
0365     return object;
0366   }
0367   return 0;
0368 }
0369 
0370 
0371 Kst::DataObjectConfigWidget *LockInPlugin::configWidget(QSettings *settingsObject) const {
0372   ConfigLockInPlugin *widget = new ConfigLockInPlugin(settingsObject);
0373   return widget;
0374 }
0375 
0376 #ifndef QT5
0377 Q_EXPORT_PLUGIN2(kstplugin_LockInPlugin, LockInPlugin)
0378 #endif
0379 
0380 // vim: ts=2 sw=2 et