Created
August 3, 2017 20:50
-
-
Save suryadutta/1834b128cd46c2b392c39144a335b8da to your computer and use it in GitHub Desktop.
Implementation of Optimum Filter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "QOptimumFilter.hh" | |
#include "QError.hh" | |
#include "QMatrix.hh" | |
#include "QRealComplexFFT.hh" | |
#include "QFir.hh" | |
#include <sstream> | |
// EXTRA_JITTERS: | |
// 1: minimum number needed for interpolation | |
// 2: more point to see if we have a minimum at the boundary (enable nice parabola) | |
using std::vector; | |
using namespace Cuore; | |
QOptimumFilter::QOptimumFilter(const QVector& ap1, const QVector& an, int maxJitter, bool useDiff, bool debugOn) : | |
fMaxJitter(maxJitter), | |
fDebugOn(debugOn), | |
fUseDiff(useDiff), | |
fTransformer(0), | |
fSize(0), | |
fCutOffFrequency(Q_DOUBLE_DEFAULT) | |
{ | |
fCheckForFilteredSamples = QERR_UNKNOWN_ERR; | |
fCheckForFilteredSamples.SetDescription(__FILE__,__LINE__,"Filtered samples not available"); | |
fSize = ap1.Size(); | |
QError err; | |
if(an.Size() != fSize) { | |
err = QERR_SIZE_NOT_MATCH; | |
err.SetDescription(__FILE__,__LINE__,"Size of input QVectors mismatches"); | |
CuoreThrow(err); | |
} | |
fAveragePulse1 = NormalizeVector(ap1); | |
fMaxPos = fAveragePulse1.GetMaxIndex(); | |
fAvgPulseIntegral1= fAveragePulse1.Sum(fAveragePulse1.Size(),0); | |
// compute the time span from half-rise to maximum | |
fAvgPulseHalfTimeToMax = -1; | |
double A = fAveragePulse1.GetMax()-fAveragePulse1[0]; | |
for(int i = fMaxPos; i > 0; i--) { | |
if( (fAveragePulse1[i]-fAveragePulse1[0])<0.5*A ) { | |
fAvgPulseHalfTimeToMax = (fMaxPos-i); | |
break; | |
} | |
} | |
if(fAvgPulseHalfTimeToMax < 1 || fAvgPulseHalfTimeToMax >= fMaxPos) { | |
err = QERR_OUT_OF_RANGE; | |
err.SetDescription(__FILE__,__LINE__,"failed to autoset the maximum jitter"); | |
CuoreThrow(err); | |
} | |
// autoset maximum jitter | |
if(fMaxJitter < 0) fMaxJitter = fAvgPulseHalfTimeToMax*2; | |
fTransformer = new QRealComplexFFT(fSize); | |
QVectorC apf; fTransformer->TransformToFreq(fAveragePulse1,apf); | |
fAveragePulseEnergySpectrum = apf.GetMagnitudesSquare(); | |
fAverageNoise = an; | |
fAverageNoise[0] = 0; | |
err = BuildFilter(); | |
if(err != QERR_SUCCESS) CuoreThrow(err); | |
} | |
QOptimumFilter::~QOptimumFilter() | |
{ | |
if(fTransformer) { | |
delete fTransformer; | |
fTransformer = 0; | |
} | |
} | |
QError QOptimumFilter::Filter(const Cuore::QVector& p) | |
{ | |
fCheckForFilteredSamples = QERR_SUCCESS; | |
fFiltered1.Clear(); | |
if(p.Size() != fSize) { | |
QError err(QERR_SIZE_NOT_MATCH,__FILE__,__LINE__,"Size of input QVector is not correct"); | |
fCheckForFilteredSamples = err; | |
return fCheckForFilteredSamples; | |
} | |
if(fUseDiff) { | |
Cuore::QVector diff = p; | |
diff.Differentiate(); | |
fTransformer->TransformToFreq(diff,fTransformedWaveform); | |
} else { | |
fTransformer->TransformToFreq(p,fTransformedWaveform); | |
} | |
fTransformedWaveform[0].Set(0,0); | |
fTransformer->TransformFromFreq(fFilter1.Mult(fTransformedWaveform),fFiltered1); | |
return fCheckForFilteredSamples; | |
} | |
const Cuore::QVector& QOptimumFilter::GetFiltered() const | |
{ | |
if(fCheckForFilteredSamples != QERR_SUCCESS) CuoreThrow(fCheckForFilteredSamples); | |
return fFiltered1; | |
} | |
Cuore::QVector QOptimumFilter::GetFilteredShifted() const | |
{ | |
QVector filtShift = GetFiltered(); | |
filtShift.Shift(fMaxPos); | |
return filtShift; | |
} | |
QError QOptimumFilter::GetInterpolated(double& intJitter, double& chi2,double& amplitude, double& integral) const | |
{ | |
double dummy, yb,chib; | |
QError err = Get(dummy, chib, yb, dummy); | |
if(err != QERR_SUCCESS) return err; | |
double xa = fOptimumJitter-1; | |
double xb = fOptimumJitter; | |
double xc = fOptimumJitter+1; | |
int ia = fOptimumJitter-1; if(ia < 0) ia+=fSize; | |
double ya = fFiltered1[ia]; | |
int ic = fOptimumJitter+1; if(ic >= (int)fSize) ic-=fSize; | |
double yc = fFiltered1[ic]; | |
if(yb<ya || yb<yc) { | |
QError err = QERR_OUT_OF_RANGE; | |
std::stringstream msg; | |
msg<<"Amplitude "<<yb<<" not at maximum. Boundaries: ["<<ya<<" "<<yc<<"]"; | |
err.SetDescription(__FILE__,__LINE__,msg.str()); | |
return err; | |
} | |
double detA = xa*xa*(xb-xc)-xb*xb*(xa-xc)+xc*xc*(xa-xb); | |
double detA1 = ya*(xb-xc)-yb*(xa-xc)+yc*(xa-xb); | |
double detA2 = xa*xa*(yb-yc)-xb*xb*(ya-yc)+xc*xc*(ya-yb); | |
double detA3 = xa*xa*(xb*yc-yb*xc)-xb*xb*(xa*yc-ya*xc)+xc*xc*(xa*yb-ya*xb); | |
double p0 = detA1/detA; | |
double p1 = detA2/detA; | |
double p2 = detA3/detA; | |
amplitude = -p1*p1/(4.*p0)+p2; intJitter = -p1/2./p0; | |
integral = fAvgPulseIntegral1*amplitude; | |
intJitter = -p1/2./p0; | |
if(intJitter <xa || intJitter >xc) { | |
QError err = QERR_OUT_OF_RANGE; | |
std::stringstream msg; | |
msg<<"Interpolated jitter "<<intJitter<<" out of boundaries ["<<xa<<" "<<xc<<"]"; | |
err.SetDescription(__FILE__,__LINE__,msg.str()); | |
return err; | |
} | |
// interpolate chi2 | |
int low = floor(intJitter); | |
double chi1Low, chi1High; | |
if(low == xa) { | |
const Cuore::QVectorC& ap1fa = fAveragePulse1Shifted[ia]; | |
double chia = (fTransformedWaveform-ya*ap1fa).GetModulusSquare().Div(fAverageNoiseForChi2).Sum(fSize-1,1); | |
chia /= (fSize-2); | |
chi1Low = chia; | |
chi1High = chib; | |
} else { | |
const Cuore::QVectorC& ap1fc = fAveragePulse1Shifted[ic]; | |
double chic = (fTransformedWaveform-yc*ap1fc).GetModulusSquare().Div(fAverageNoiseForChi2).Sum(fSize-1,1); | |
chic /= (fSize-2); | |
chi1Low = chib; | |
chi1High = chic; | |
} | |
double x = intJitter-low; | |
chi2 = (chi1High-chi1Low)*x+chi1Low; | |
if(intJitter > (int)fSize/2) intJitter-=fSize; | |
return QERR_SUCCESS; | |
} | |
QVector QOptimumFilter::NormalizeVector(const QVector& vec) const | |
{ | |
QVector ret = vec; | |
QVector baseline(ret.Size()); | |
baseline.Initialize(ret[0]); | |
ret -= baseline; | |
ret /= ret.GetMax(); | |
return ret; | |
} | |
QError QOptimumFilter::BuildFilter() | |
{ | |
QError err = QERR_SUCCESS; | |
fDebugVectors.clear(); | |
fDebugSpectra.clear(); | |
QVector averageNoise = fAverageNoise; | |
QVector averagePulse = fAveragePulse1; | |
if(fUseDiff) { | |
// differentiate noise and average pulse | |
double pi = M_PI; | |
for(size_t i = 0; i < fSize; i++){ | |
if(i <= averageNoise.Size()/2) averageNoise[i] *= 2.*(1-cos(2.*pi/(averageNoise.Size())*i)); | |
else averageNoise[i] *= 2.*(1-cos(2.*pi/(averageNoise.Size())*(averageNoise.Size()-i))); | |
} | |
averagePulse.Differentiate(); | |
} | |
// force to zero the average pulse at the boundaries | |
fEffectiveFilterLength = fSize-fMaxJitter*2; | |
QVector window = QFFT::GetWindow(QFFT::WT_Cosinus,fEffectiveFilterLength,0,false,fMaxJitter); | |
window = QFFT::ZeroPad(window,fSize-fEffectiveFilterLength, QFFT::kSymmetric,0); | |
QVector ap = averagePulse.Mult(window); | |
QVectorC ap1f; | |
if(fDebugOn) { | |
fTransformer->TransformToFreq(averagePulse,ap1f); | |
fDebugSpectra.push_back(averageNoise); // 0 input average noise | |
fDebugSpectra.push_back(ap1f.GetMagnitudesSquare()); // 1 average pulse | |
fDebugVectors.push_back(ap); // 0 windowed average pulse | |
} | |
// create filter in FD | |
QVectorC anc(fSize); | |
anc.Initialize(0,0); | |
anc.SetRe(averageNoise); | |
fTransformer->TransformToFreq(ap,ap1f); | |
QVector SNR = ap1f.GetModulusSquare().Div(averageNoise); | |
double M = 1./SNR.Sum(fSize-1,1); | |
fFilter1 = M*ap1f.Conj().Div(anc); | |
fFilter1 *= fSize; | |
fFilter1[0].Set(0,0); | |
// get kernel in TD | |
QVector kernel; | |
fTransformer->TransformFromFreq(fFilter1,kernel); | |
if(fDebugOn) { | |
fDebugSpectra.push_back(ap1f.GetMagnitudesSquare()); // 2 windowed average pulse | |
fDebugSpectra.push_back(fFilter1.GetMagnitudesSquare()); // 3 filter in FD with windowed average pulse | |
fDebugVectors.push_back(kernel); // 1 filter in TD with windowed average pulse | |
} | |
// determine SNR as a function of frequency | |
fSNRFreq.Resize(fSize/2+1); fSNRFreq[0] = 0; | |
for(size_t i = 1; i < fSize/2; i++) { | |
fSNRFreq[i] = fSNRFreq[i-1]+2*SNR[i]; | |
} | |
fSNRFreq[fSize/2] = fSNRFreq[fSize/2-1]+SNR[fSize/2]; | |
if(fDebugOn) { | |
QVector filteredAverageNoise = fFilter1.GetModulusSquare().Mult(averageNoise); | |
fDebugSpectra.push_back(filteredAverageNoise); // 4 filtered average noise with windowed average pulse | |
} | |
// filter out frequencies where SNR improvement is irrelevant | |
size_t cutOffFrequency = fSize/2; | |
for( ; cutOffFrequency > 1; cutOffFrequency--) { | |
if(fSNRFreq[cutOffFrequency] < 0.990*fSNRFreq[fSize/2]) break; | |
} | |
fCutOffFrequency = double(cutOffFrequency)/fSize; // normalize to Nyquist | |
QCFirData firData; | |
firData.fMethod = QCFirData::KayserBessel; | |
firData.fCutoff= fCutOffFrequency; | |
firData.fM= fMaxJitter*2; | |
if(firData.fM%2 == 0) firData.fM--; | |
firData.fAttDB= 60; | |
QFir fir(firData); | |
int reduction = fir.GetFilterReduction(); | |
QVector kernelout; | |
fir.Filter(kernel,kernelout); | |
for(size_t i = reduction/2; i < fSize-reduction/2; i++) kernel[i]=kernelout[i-reduction/2]; | |
if(fDebugOn) { | |
fDebugVectors.push_back(kernel); // 2 filter in TD low-pass filtered | |
} | |
// force to zero the kernel at the boundaries | |
QVector window2 = QFFT::GetWindow(QFFT::WT_Cosinus,fEffectiveFilterLength,0,false, fMaxJitter); | |
window2 = QFFT::ZeroPad(window2,fSize-fEffectiveFilterLength, QFFT::kSymmetric,0); | |
fValidSample.resize(fSize); | |
for(size_t i = 0; i < fSize; i++) { | |
kernel[i] *= window2[i]; | |
// store the information on the samples that are correctly filtered | |
fValidSample[i] = (window2[i] == 0); | |
} | |
// force to zero the integral of the survived kernel | |
double integral = kernel.Sum(fSize,0); | |
// alternate method | |
/* | |
double add = integral/fEffectiveFilterLength; | |
add *= fEffectiveFilterLength/window2.Sum(fSize,0); | |
for(size_t i = 0; i < fSize; i++) { | |
kernel[i] -= add*window2[i]; | |
} | |
*/ | |
double add = 0; | |
for(size_t i = 0; i < fSize; i++) { | |
if(kernel[i] < 0) add += kernel[i]; | |
} | |
for(size_t i = 0; i < fSize; i++) { | |
if(!fValidSample[i]) { | |
if(integral > 0 && kernel[i] < 0) kernel[i] *= (add-integral)/add; | |
else if(integral < 0 && kernel[i] > 0) kernel[i] *= add/(add-integral); | |
} | |
} | |
fTransformer->TransformToFreq(kernel,fFilter1); | |
// force to zero the filter integral (even if it should be aready zero) | |
// fFilter1[0].Set(0.,0.); | |
fFilter1[fSize/2].SetIm(0.); | |
// Adjust gain since we manipulated the kernel | |
err = Filter(fAveragePulse1); | |
if(err != QERR_SUCCESS) return err; | |
fCheckForFilteredSamples = QERR_UNKNOWN_ERR; | |
fCheckForFilteredSamples.SetDescription(__FILE__,__LINE__,"Filtered samples not available"); | |
fFilter1 /= fFiltered1.GetMax(); | |
// Store filter in TD | |
fTransformer->TransformFromFreq(fFilter1,fFilterTD); | |
// estimate filtered average noise and resolution | |
fFilteredAverageNoise = fFilter1.GetModulusSquare().Mult(averageNoise); | |
fFilteredRMS1 = sqrt(fFilteredAverageNoise.Sum(fSize-1,1))/fSize; | |
// estimate shifted ap spectra for chi2 evaluation | |
// | |
fAverageNoiseForChi2=averageNoise; | |
fAveragePulse1Shifted.resize(fSize); | |
for(size_t j = 0; j < fSize; j++) { | |
if(fValidSample[j]) { | |
QVector ap1Shifted; | |
if(fAveragePulse1Shifted[j].Size() == 0) { | |
ap1Shifted = averagePulse; | |
ap1Shifted.Shift(j); | |
fTransformer->TransformToFreq(ap1Shifted,fAveragePulse1Shifted[j]); | |
} | |
if(j+1 < fSize && fAveragePulse1Shifted[j+1].Size() == 0) { | |
ap1Shifted = averagePulse; | |
ap1Shifted.Shift(j+1); | |
fTransformer->TransformToFreq(ap1Shifted,fAveragePulse1Shifted[j+1]); | |
} | |
if(j > 0 && fAveragePulse1Shifted[j-1].Size() == 0) { | |
ap1Shifted = averagePulse; | |
ap1Shifted.Shift(j-1); | |
fTransformer->TransformToFreq(ap1Shifted,fAveragePulse1Shifted[j-1]); | |
} | |
} | |
} | |
return err; | |
} | |
QError QOptimumFilter::SetJitter(JitterMode mode, int triggerpos) | |
{ | |
QError err = QERR_SUCCESS; | |
switch(mode) { | |
case J_ABSMAX: | |
fOptimumJitter = fFiltered1.GetMaxIndex(); | |
break; | |
case J_LOCMAX: | |
{ | |
// find first local maximum after the trigger. Copied from OptimumFilter.cc | |
const int first_start = (triggerpos-fMaxPos+fSize)%fSize; | |
int start = first_start; | |
bool check = false; | |
do { | |
if(fFiltered1[start%fSize]> fFiltered1[(start-1+fSize)%fSize] | |
&& fFiltered1[start%fSize]> fFiltered1[(start+1)%fSize]) { | |
fOptimumJitter = start%fSize; | |
check=true; | |
} | |
start++; | |
}while(!check && (start%(int)fSize) != first_start); | |
if(!check) { | |
fOptimumJitter = 0; | |
err= QERR_OUT_OF_RANGE; | |
err.SetDescription(__FILE__,__LINE__,"Failed to find a local maximum, this is wired."); | |
} | |
} | |
break; | |
case J_FIXED: | |
/* copied from OptimumFilter.cc, to be checked | |
{ | |
const int first_start = (triggerpos-fMaxPos+fSize)%fSize; | |
const int fixed_guess = (first_start + trigger_shift_ + fSize)%fSize; | |
int ii = fixed_guess, maxpos = -1; | |
bool check = false; | |
// go fixed distance, then move to maximum according to slope | |
while(!check && ii!=first_start) | |
{ | |
const int slope = | |
int(pSamples[ii]>pSamples[(ii-1+fSize)%fSize]) - | |
int(pSamples[ii]>pSamples[(ii+1)%fSize]); | |
if(slope==0 && pSamples[ii]<=pSamples[(ii-1+fSize)%fSize]) | |
{ | |
// Local min; either have local max on left, or wave is | |
// decreasing from trigger to ii; either way, move left | |
ii = ((ii - 1 + fSize)%fSize); | |
continue; | |
} | |
if(slope==0) | |
{ | |
check = true; | |
maxpos = ii; | |
} | |
ii = ((ii + slope + fSize)%fSize); | |
} | |
// If we went back to the trigger, look for the first maximum | |
if(!check) | |
{ | |
ii=first_start; | |
do | |
{ | |
if(pSamples[ii]>pSamples[(ii+1)%fSize] && | |
pSamples[ii]>pSamples[(ii-1+fSize)%fSize]) | |
{ | |
check = true; | |
maxpos = ii; | |
} | |
ii = ((ii + 1)%fSize); | |
} while(!check && ii!=first_start); | |
} | |
// If we got to the end of window, set to fixed_guess | |
if(!check) | |
{ | |
if(ii==first_start) | |
{ | |
//QMessageHandler::Debug("OptimumFilter","COF End slope"); | |
maxpos = avg_nmax; | |
} | |
else | |
QMessageHandler::Panic("OptimumFilter","COF slope something else happened"); | |
} | |
data.max_index = maxpos; | |
} | |
*/ | |
// MV FIXME: to be implemented | |
err= QERR_NOT_IMPLEMENTED; | |
err.SetDescription(__FILE__,__LINE__,"Jitter mode to compute amplitude at fixed distance not implmented"); | |
break; | |
case J_NOJITTER: | |
fOptimumJitter = 0; | |
break; | |
default: | |
err = QERR_OUT_OF_RANGE; | |
err.SetDescription(__FILE__,__LINE__,"Jitter mode not implemented"); | |
} | |
if(err != QERR_SUCCESS) return err; | |
if(!fValidSample[fOptimumJitter]) { | |
err = QERR_OUT_OF_RANGE; | |
std::stringstream msg; | |
msg<<"Maximum position "<<fOptimumJitter<<" lies outside the filter effective length"; | |
err.SetDescription(__FILE__,__LINE__,msg.str()); | |
//std::cout<<"Maximum was: "<<fFiltered1.GetMaxIndex()<<" TriggerPos: "<<triggerpos-fMaxPos<<std::endl; | |
} | |
return err; | |
} | |
QError QOptimumFilter::Get(double& jitter, double& chi2, double& amplitude, double& integral) const | |
{ | |
if(fCheckForFilteredSamples != QERR_SUCCESS) return fCheckForFilteredSamples; | |
QError err = QERR_SUCCESS; | |
jitter = fOptimumJitter; | |
if(fOptimumJitter > (int)fSize/2) jitter-=fSize; | |
amplitude = fFiltered1[fOptimumJitter]; | |
// std::cout<<fOptimumJitter<<" "<<fAmplitude1<<std::endl; | |
const Cuore::QVectorC& ap1f = fAveragePulse1Shifted[fOptimumJitter]; | |
chi2 = (fTransformedWaveform-amplitude*ap1f).GetModulusSquare().Div(fAverageNoiseForChi2).Sum(fSize-1,1); | |
chi2 /= (fSize-2); | |
integral = fAvgPulseIntegral1*amplitude; | |
return err; | |
} | |
QVector QOptimumFilter::GetFitFunction(const double jitter, const double a1) const | |
{ | |
QVector output = fAveragePulse1*a1; | |
output.ShiftReal(jitter); | |
return output; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment