def CalculateSpectrumBlock(self, region): '''Return the power spectrum of a region based on a Welch-Bartlett method. The block used in each FFT is half the length of the total window. The step size is half the size of the FFT window. Average over A-lines. This function assumes the size of the region is divisible by 4. It uses a zoomed in FFT to compute the power spectrum. The zoomed in FFT is given by the chirpz transform. ''' from scipy.signal import hann,convolve import numpy from chirpz import chirpz points = region.shape[0] points -= points%4 points /= 2 #######SAMPLE REGION############# maxDataWindow = region[0:2*points, :] #compute 3 fourier transforms and average them #Cutting off the zero-value end points of the hann window #so it matches Matlab's definition of the function windowFunc = hann(points+2)[1:-1].reshape(points,1) fftSample = numpy.zeros(points) for f in range(3): dataWindow = maxDataWindow[(points/2)*f:(points/2)*f + points, :]*windowFunc for l in range(dataWindow.shape[1]): fftSample += abs(chirpz(dataWindow[:,l], self.cztA, self.cztW, points))**2 return fftSample
print('actual frequency content', freqs) m = len(x)//2 Fs = 48e3 F1 = 900 F2 = 3000 # input, start, step, length # def chirpz(x, A, W, M): A = np.e**(1j*2*np.pi*F1/Fs) W = np.e**(-1j*2.0*np.pi*(F2-F1)/(Fs*m)) cztAmps = np.abs( chirpz(x, A, W, m) ) cztFreqs = np.linspace(F1, F2, m) cztPeaks = [cztFreqs[i-1] for i in range(m) if cztAmps[i-2] < cztAmps[i-1] > cztAmps[i] if cztAmps[i] > max(cztAmps)*0.5] print('freq peaks from czt / zoom FFT', cztPeaks) plt.vlines(cztPeaks, 0, max(cztAmps), 'r', label='peaks from czt') plt.plot(cztFreqs, cztAmps, 'or', label='czt') fftFreqs = np.linspace(0, Fs/2, len(x)*2) fftAmps = np.abs(np.fft.rfft(x, len(x)*4))[1::]
tmpRf = rfClass(TISSUE_A_DIR + '/' + fName, 'rfd') tmpRf.SetRoiFixedSize(WINDOWX, WINDOWY) tmpRf.ReadFrame() #PSD fit windowFuncPsd = hann(psdPoints+2)[1:-1].reshape(psdPoints,1) powerSpectrum = np.zeros( psdPoints ) region = tmpRf.data[tmpRf.roiY[0]:tmpRf.roiY[1], tmpRf.roiX[0]:tmpRf.roiX[1]] maxDataWindow = region for r in range(3): dataWindow = maxDataWindow[psdPoints*r:psdPoints*r + psdPoints, :]*windowFuncPsd fourierData = np.zeros( dataWindow.shape ) for l in range(maxDataWindow.shape[1]): fourierData[:,l] = abs(chirpz(dataWindow[:,l], cztA, cztW, psdPoints)) powerSpectrum += fourierData.mean(axis = 1) powerSpectrum/= maxDataWindow.shape[1]*3 #now fit the spectrum to a gaussian errfunc = lambda param,x,y: y - np.exp(- (x - param[0])**2/(2*param[1]**2) ) param0 = (5., 3.0) args = (spectrumFreq,powerSpectrum/powerSpectrum.max() ) param, message = optimize.leastsq(errfunc, param0, args) mu = param[0] sigma = param[1] runningTotalMu += mu runningTotalSigma += sigma counter += 1
def CalculateGeneralizedSpectrum(self, region): '''Use 3 50% overlapping windows axially to compute PSD. Calculate Power spectrum first. Normalize it to have a max value of 1. Fit this to a Gaussian. f(x) = exp[ -(x - mu)**2 / 2 (sigma**2)] A Gaussian falls to -6 dB (1/4 of max value) at a little more than one sigma. Use full window length to compute axial signal.''' from scipy.signal import hann,convolve from scipy import optimize, interpolate import numpy from chirpz import chirpz maxDataWindow = region[0:self.gsPoints, :] windowFuncPsd = hann(self.psdPoints+2)[1:-1].reshape(self.psdPoints,1) powerSpectrum = numpy.zeros( self.psdPoints ) #first compute the power spectrum, normalize it to maximum value for r in range(3): dataWindow = maxDataWindow[self.psdPoints/2*r:self.psdPoints/2*r + self.psdPoints, :]*windowFuncPsd fourierData = numpy.zeros( dataWindow.shape ) for l in range(maxDataWindow.shape[1]): fourierData[:,l] = abs(chirpz(dataWindow[:,l], self.cztA, self.cztW, self.psdPoints)) powerSpectrum += fourierData.mean(axis = 1) powerSpectrum /= powerSpectrum.max() #now fit the spectrum to a gaussian errfunc = lambda param,x,y: y - numpy.exp(- (x - param[0])**2/(2*param[1]**2) ) param0 = (5., 1.0) args = (self.psdFreq,powerSpectrum) param, message = optimize.leastsq(errfunc, param0, args) mu = param[0] sigma = param[1] if sigma < .75: sigma = .75 #set low and high frequency cutoffs based on output mu, sigma #3 Sigmas is 1% intensity on PSD is -20 dB lowCut = mu - 3*sigma if lowCut < 0: lowCut = 0 highCut = mu + 3*sigma if highCut > self.fs/10**6: highCut = self.fs/10**6 freqStep = (highCut - lowCut)/self.psdPoints spectrumFreq = numpy.arange(0,self.psdPoints)*freqStep + lowCut fracUnitCircle = (highCut - lowCut)/(self.fs/10**6) cztW = numpy.exp(1j* (-2*numpy.pi*fracUnitCircle)/self.psdPoints ) cztA = numpy.exp(1j* (2*numpy.pi*lowCut/(self.fs/10**6) ) ) self.gsFreq = spectrumFreq ########################### ###Time averaged GS######## ########################### GS = numpy.zeros( (self.psdPoints, self.psdPoints,3 ) , numpy.complex128) for r in range(3): dataWindow = maxDataWindow[self.psdPoints/2*r:self.psdPoints/2*r + self.psdPoints, :] maxPointDelay = dataWindow.argmax(axis = 0 )/self.fs dataWindow*=windowFuncPsd tempPoints = dataWindow.shape[0] tempLines = dataWindow.shape[1] fourierData = numpy.zeros( dataWindow.shape, numpy.complex128 ) for l in range(tempLines): fourierData[:,l] = chirpz(dataWindow[:,l], cztA, cztW, self.psdPoints) #get point delay in seconds delta = numpy.outer(spectrumFreq*10**6,maxPointDelay) phase = numpy.exp(1j*2*numpy.pi*delta) for l in range(tempLines): outerProd = numpy.outer(fourierData[:,l]*phase[:,l], fourierData[:,l].conjugate()*phase[:,l].conjugate() ) GS[:,:,r] += outerProd/abs(outerProd) numSegs = tempLines*3 GS = GS.sum(axis = 2)/numSegs ############################################## ########COMPUTE COLLAPSED AVERAGE############# ############################################## #Along diagonal lines the scatterer spacing/frequency difference is the same #exclude all the entries that have a larger scatter spacing/lower frequency difference #than 1.5 mm: .51 MHz #Exclude all sizes less than .5 mm, which is 1.54 MHz numFreq = len(spectrumFreq) self.CA = numpy.zeros(numFreq) counts = numpy.zeros(numFreq) for f1 in range(numFreq): for f2 in range(numFreq): d = abs(f1-f2) self.CA[d] += abs(GS[f1,f2]) counts[d] += 1 self.CA /= counts self.CAaxis = numpy.arange(numFreq)*freqStep self.GS = GS ####################################### ########COMPUTE THE AVERAGE OF THE##### ########COLLAPSED AVERAGE############## ###########AND THE SLOPE############### ###CA avg. bins: ###0.0-1.0 sigma ###1.0-2.0 sigma ###2.0-3.0 sigma ###3.0-4.0 sigma ###4.0-5.0 sigma ###5.0-6.0 sigma ###0.0-6.0 sigma ####################################### ###Work out leakage location from PSD####### self.psdLimit = (1540./(2*self.gsWindowYmm*10**-3/2 ))/10**6 secondDeriv = self.CA[0:-2] - 2*self.CA[1:-1] + self.CA[2:] psdInd = int(self.psdLimit/freqStep) psdInd = secondDeriv[0:psdInd+10].argmax() + 1 print "The PSD leakage stops at: " + str(secondDeriv[0:psdInd+10].argmax() +1 ) CAbinned = numpy.zeros(7) CAcounts = numpy.zeros(7) CAslope = numpy.zeros(7) for f, val in enumerate(self.CA): if f*freqStep > self.psdLimit and f*freqStep < 1.0*sigma: CAbinned[0] += val CAcounts[0] += 1 elif f*freqStep > 1.0*sigma and f*freqStep < 2.0*sigma: CAbinned[1] += val CAcounts[1] += 1 elif f*freqStep > 2.0*sigma and f*freqStep < 3.0*sigma: CAbinned[2] += val CAcounts[2] += 1 elif f*freqStep > 3.0*sigma and f*freqStep < 4.0*sigma: CAbinned[3] += val CAcounts[3] += 1 elif f*freqStep > 4.0*sigma and f*freqStep < 5.0*sigma: CAbinned[4] += val CAcounts[4] += 1 elif f*freqStep > 5.0*sigma and f*freqStep < 6.0*sigma: CAbinned[5] += val CAcounts[5] += 1 if f*freqStep > self.psdLimit: CAbinned[6] += val CAcounts[6] += 1 for ind,count in enumerate(CAcounts): if val > 0: CAbinned[ind] /= count ##compute slope of Collapsed Average for ind in range(7): if ind == 0 or ind == 6: lowCut = int(self.psdLimit/freqStep) else: lowCut = int(sigma*ind/freqStep) highCut = int(sigma*(ind+1)/freqStep) CAslope[ind] = self.ComputeSlope( self.CA[lowCut:highCut], self.CAaxis[lowCut:highCut]) return CAbinned, CAslope, mu, sigma
tmpRf.ReadFrame() #PSD fit windowFuncPsd = hann(psdPoints + 2)[1:-1].reshape(psdPoints, 1) powerSpectrum = np.zeros(psdPoints) region = tmpRf.data[tmpRf.roiY[0]:tmpRf.roiY[1], tmpRf.roiX[0]:tmpRf.roiX[1]] maxDataWindow = region for r in range(3): dataWindow = maxDataWindow[psdPoints * r:psdPoints * r + psdPoints, :] * windowFuncPsd fourierData = np.zeros(dataWindow.shape) for l in range(maxDataWindow.shape[1]): fourierData[:, l] = abs( chirpz(dataWindow[:, l], cztA, cztW, psdPoints)) powerSpectrum += fourierData.mean(axis=1) powerSpectrum /= maxDataWindow.shape[1] * 3 #now fit the spectrum to a gaussian errfunc = lambda param, x, y: y - np.exp(-(x - param[0])**2 / (2 * param[1]**2)) param0 = (5., 3.0) args = (spectrumFreq, powerSpectrum / powerSpectrum.max()) param, message = optimize.leastsq(errfunc, param0, args) mu = param[0] sigma = param[1] ##Set up CZT parameters for GS calculation### lowCut = mu - GS_BW
def CalculateGeneralizedSpectrum(self, region): '''Use 3 50% overlapping windows axially to compute PSD. Calculate Power spectrum first. Normalize it to have a max value of 1. Fit this to a Gaussian. f(x) = exp[ -(x - mu)**2 / 2 (sigma**2)] A Gaussian falls to -6 dB (1/4 of max value) at a little more than one sigma. Use full window length to compute axial signal.''' from scipy.signal import hann, convolve from scipy import optimize, interpolate import numpy from chirpz import chirpz maxDataWindow = region[0:self.gsPoints, :] windowFuncPsd = hann(self.psdPoints + 2)[1:-1].reshape( self.psdPoints, 1) powerSpectrum = numpy.zeros(self.psdPoints) #first compute the power spectrum, normalize it to maximum value for r in range(3): dataWindow = maxDataWindow[self.psdPoints / 2 * r:self.psdPoints / 2 * r + self.psdPoints, :] * windowFuncPsd fourierData = numpy.zeros(dataWindow.shape) for l in range(maxDataWindow.shape[1]): fourierData[:, l] = abs( chirpz(dataWindow[:, l], self.cztA, self.cztW, self.psdPoints)) powerSpectrum += fourierData.mean(axis=1) powerSpectrum /= powerSpectrum.max() #now fit the spectrum to a gaussian errfunc = lambda param, x, y: y - numpy.exp(-(x - param[0])**2 / (2 * param[1]**2)) param0 = (5., 1.0) args = (self.psdFreq, powerSpectrum) param, message = optimize.leastsq(errfunc, param0, args) mu = param[0] sigma = param[1] if sigma < .75: sigma = .75 #set low and high frequency cutoffs based on output mu, sigma #3 Sigmas is 1% intensity on PSD is -20 dB lowCut = mu - 3 * sigma if lowCut < 0: lowCut = 0 highCut = mu + 3 * sigma if highCut > self.fs / 10**6: highCut = self.fs / 10**6 freqStep = (highCut - lowCut) / self.psdPoints spectrumFreq = numpy.arange(0, self.psdPoints) * freqStep + lowCut fracUnitCircle = (highCut - lowCut) / (self.fs / 10**6) cztW = numpy.exp(1j * (-2 * numpy.pi * fracUnitCircle) / self.psdPoints) cztA = numpy.exp(1j * (2 * numpy.pi * lowCut / (self.fs / 10**6))) self.gsFreq = spectrumFreq ########################### ###Time averaged GS######## ########################### GS = numpy.zeros((self.psdPoints, self.psdPoints, 3), numpy.complex128) for r in range(3): dataWindow = maxDataWindow[self.psdPoints / 2 * r:self.psdPoints / 2 * r + self.psdPoints, :] maxPointDelay = dataWindow.argmax(axis=0) / self.fs dataWindow *= windowFuncPsd tempPoints = dataWindow.shape[0] tempLines = dataWindow.shape[1] fourierData = numpy.zeros(dataWindow.shape, numpy.complex128) for l in range(tempLines): fourierData[:, l] = chirpz(dataWindow[:, l], cztA, cztW, self.psdPoints) #get point delay in seconds delta = numpy.outer(spectrumFreq * 10**6, maxPointDelay) phase = numpy.exp(1j * 2 * numpy.pi * delta) for l in range(tempLines): outerProd = numpy.outer( fourierData[:, l] * phase[:, l], fourierData[:, l].conjugate() * phase[:, l].conjugate()) GS[:, :, r] += outerProd / abs(outerProd) numSegs = tempLines * 3 GS = GS.sum(axis=2) / numSegs ############################################## ########COMPUTE COLLAPSED AVERAGE############# ############################################## #Along diagonal lines the scatterer spacing/frequency difference is the same #exclude all the entries that have a larger scatter spacing/lower frequency difference #than 1.5 mm: .51 MHz #Exclude all sizes less than .5 mm, which is 1.54 MHz numFreq = len(spectrumFreq) self.CA = numpy.zeros(numFreq) counts = numpy.zeros(numFreq) for f1 in range(numFreq): for f2 in range(numFreq): d = abs(f1 - f2) self.CA[d] += abs(GS[f1, f2]) counts[d] += 1 self.CA /= counts self.CAaxis = numpy.arange(numFreq) * freqStep self.GS = GS ####################################### ########COMPUTE THE AVERAGE OF THE##### ########COLLAPSED AVERAGE############## ###########AND THE SLOPE############### ###CA avg. bins: ###0.0-1.0 sigma ###1.0-2.0 sigma ###2.0-3.0 sigma ###3.0-4.0 sigma ###4.0-5.0 sigma ###5.0-6.0 sigma ###0.0-6.0 sigma ####################################### ###Work out leakage location from PSD####### self.psdLimit = (1540. / (2 * self.gsWindowYmm * 10**-3 / 2)) / 10**6 secondDeriv = self.CA[0:-2] - 2 * self.CA[1:-1] + self.CA[2:] psdInd = int(self.psdLimit / freqStep) psdInd = secondDeriv[0:psdInd + 10].argmax() + 1 print "The PSD leakage stops at: " + str(secondDeriv[0:psdInd + 10].argmax() + 1) CAbinned = numpy.zeros(7) CAcounts = numpy.zeros(7) CAslope = numpy.zeros(7) for f, val in enumerate(self.CA): if f * freqStep > self.psdLimit and f * freqStep < 1.0 * sigma: CAbinned[0] += val CAcounts[0] += 1 elif f * freqStep > 1.0 * sigma and f * freqStep < 2.0 * sigma: CAbinned[1] += val CAcounts[1] += 1 elif f * freqStep > 2.0 * sigma and f * freqStep < 3.0 * sigma: CAbinned[2] += val CAcounts[2] += 1 elif f * freqStep > 3.0 * sigma and f * freqStep < 4.0 * sigma: CAbinned[3] += val CAcounts[3] += 1 elif f * freqStep > 4.0 * sigma and f * freqStep < 5.0 * sigma: CAbinned[4] += val CAcounts[4] += 1 elif f * freqStep > 5.0 * sigma and f * freqStep < 6.0 * sigma: CAbinned[5] += val CAcounts[5] += 1 if f * freqStep > self.psdLimit: CAbinned[6] += val CAcounts[6] += 1 for ind, count in enumerate(CAcounts): if val > 0: CAbinned[ind] /= count ##compute slope of Collapsed Average for ind in range(7): if ind == 0 or ind == 6: lowCut = int(self.psdLimit / freqStep) else: lowCut = int(sigma * ind / freqStep) highCut = int(sigma * (ind + 1) / freqStep) CAslope[ind] = self.ComputeSlope(self.CA[lowCut:highCut], self.CAaxis[lowCut:highCut]) return CAbinned, CAslope, mu, sigma