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