def Waveform(self, td=None): """Computes the time-domain waveform using IDFT methods @param td (optional) instance of class TimeDescriptor declaring the time descriptor of the waveform to produce. @return wf instance of class Waveform corresponding to the frequency content. @note If td is None then the time descriptor corresponding to the frequency descriptor is used.\n The waveform produced is essentially the inverse process of class initialization.\n @see WaveformFromDefinition() """ Keven = (self.td.K // 2) * 2 == self.td.K X = self.Values() X=[X[n]*self.td.K*\ (1. if (n==0 or ((n==self.m_f.N) and Keven)) else 0.5)*\ cmath.exp(1j*2.*math.pi*self.m_f[n]*self.td.H) for n in range(self.m_f.N+1)] if Keven: X2 = [X[self.m_f.N - n].conjugate() for n in range(1, self.m_f.N)] else: X2 = [ X[self.m_f.N - n + 1].conjugate() for n in range(1, self.m_f.N + 1) ] X.extend(X2) x = [xk.real for xk in fft.ifft(X).tolist()] wf = Waveform(self.td, x) if not td is None: wf = wf.Adapt(td) return wf
def CheckWaveformResult(self, wf, fileName, text): #path=os.getcwd() #os.chdir(os.path.dirname(os.path.realpath(__file__))) if not os.path.exists(fileName): wf.WriteToFile(fileName) self.assertTrue(False, fileName + ' not found') regression = Waveform().ReadFromFile(fileName) wfsAreEqual = (regression == wf) if not wfsAreEqual: if ResponseTesterHelper.plotErrors: import matplotlib.pyplot as plt plt.clf() plt.title(fileName) plt.xlabel('time (s)') plt.ylabel('amplitude') plt.semilogy(regression.Times(), [ abs(wf[k] - regression[k]) for k in range(len(regression)) ]) plt.grid(True) plt.show() plt.clf() plt.title(fileName) plt.xlabel('time (s)') plt.ylabel('amplitude') plt.plot(wf.Times(), wf.Values(), label='calculated') plt.plot(regression.Times(), regression.Values(), label='regression') plt.legend(loc='upper right') plt.grid(True) plt.show() #os.chdir(path) self.assertTrue(wfsAreEqual, text + ' incorrect')
def __init__(self, td, Amplitude=1., Frequency=1e6, Phase=0., StartTime=-100., StopTime=100.): """Constructor constructs a sinewave waveform. @param td instance of class TimeDescriptor containing time axis of waveform. @param Amplitude (optional) float amplitude of sine wave (defaults to unity). @param Frequency (optional) float frequency of sine wave (defaults to 1 MHz). @param Phase (optional) float phase of sine wave in degrees (defaults to zero). @param StartTime (optional) float start time of sine wave (defaults to -100 s). @param StopTime (optional) float stop time of the sine wave (defaults to 100 s). """ x = [ Amplitude * math.sin(2. * math.pi * Frequency * t + Phase / 180. * math.pi) for t in td.Times() ] sw = Waveform(td, x) * PulseWaveform( td, Amplitude=1., StartTime=StartTime, PulseWidth=max(StartTime, StopTime) - min(StartTime, StopTime), Risetime=0.) Waveform.__init__(self, sw.td, sw.Values())
def __init__(self,pattern,bitRate,amplitude=1.0,risetime=0.,delay=0.,tdOrFs=None): """constructor @param pattern list of bits in pattern which must be 0 or 1 @param bitrate float bitrate in bits/s @param amplitude (optional, defaults to 1.0) float amplitude of zero centered serial data waveform (pk-pk amplitude is twice this value) @param risetime (optional, defaults to 0.0) float risetime of a 1/2 cosine response. @param delay (optional, defaults to 0,0) float delay in time into the pattern. @param tdOrFs (optional, defaults to None) time descriptor or float sample rate. If None is specified, then the descriptor is generated for one pattern length with a sample rate of 10x the bitrate. Otherwise, if a float is specified, then a time descriptor for one pattern length is generated with the sample rate specified. Otherwise the time descriptor specified is used. @return self, a waveform. @throw SignalIntegrityExceptionWaveform exception is raised if the risetime exceeds 59% of the unit interval as this causes the calculation to fail. @todo the failure for a given risetime is due to the simplicity of the algorithm which should be improved at some point. this simplicity is that it only looks at adjacent bits to determine the intersymbol effect of the risetime. @note the left edge of the first bit in the pattern is at the delay time. But because a cosine is used for the risetime emulation, the 50% point of the cosine is reached at this edge. @note the risetime is adjusted if it is too low relative to the sample period. """ if tdOrFs is None: sampleRate=10.*bitRate td=self.TimeDescriptor(bitRate, sampleRate, len(pattern)) elif isinstance(tdOrFs,float): sampleRate=tdOrFs td=self.TimeDescriptor(bitRate, sampleRate, len(pattern)) else: td=tdOrFs T=max(risetime/self.rtvsT,1/td.Fs) patternTimeLength=len(pattern)/bitRate unitInterval=1./bitRate if T>unitInterval: # todo: would like to handle this case in the future raise SignalIntegrityExceptionWaveform('PRBS risetime too high\nfor waveform generation. '+\ 'Limit is '+ToSI(unitInterval*self.rtvsT,'s')+' for given bitrate') v=[0. for _ in range(len(td))] for k in range(len(td)): t=td[k] timeInPattern=((t-delay)%patternTimeLength+patternTimeLength)%patternTimeLength thisBit=int(math.floor(timeInPattern/unitInterval)) timeInBit=timeInPattern-thisBit*unitInterval thisBitValue=pattern[thisBit] if timeInBit>=T/2. and (timeInBit-unitInterval)<=-T/2.: v[k]=2.*amplitude*(thisBitValue-0.5) elif timeInBit<T/2.: previousBit=(thisBit-1+len(pattern))%len(pattern) previousBitTransition=thisBitValue-pattern[previousBit] if previousBitTransition==0: v[k]=2.*amplitude*(thisBitValue-0.5) else: v[k]=amplitude*previousBitTransition*math.cos(math.pi*(timeInBit/T+3./2.)) elif (timeInBit-unitInterval)>-T/2.: nextBit=(thisBit+1)%len(pattern) nextBitTransition=pattern[nextBit]-thisBitValue if nextBitTransition==0: v[k]=2.*amplitude*(thisBitValue-0.5) else: v[k]=amplitude*nextBitTransition*math.cos(math.pi*((timeInBit-unitInterval)/T+3./2.)) Waveform.__init__(self,Waveform(td,v))
def GetWaveformResult(self, fileName): #path=os.getcwd() #os.chdir(os.path.dirname(os.path.realpath(__file__))) if not os.path.exists(fileName): return None regression = Waveform().ReadFromFile(fileName) #os.chdir(path) return regression
def DecimateWaveform(self,wf): """decimates a waveform @param wf instance of class Waveform of waveform to be decimated @return intance of class Waveform the decimated waveform """ # pragma: silent exclude from SignalIntegrity.Lib.TimeDomain.Waveform.Waveform import Waveform # pragma: include td=wf.td*self return Waveform(td,[wf[k*self.df+self.dph] for k in range(td.K)])
def from_trc(filename): """Read a waveform in lecroy trc format @param filename String name of the filename to read. Should have a .trc extension @return a waveform in SignalIntegrity format """ try: fname, file_extension = os.path.splitext(filename) extensionCorrect = True if file_extension == '' or file_extension is None: filename = filename + '.trc' else: file_extension = file_extension.lower() if file_extension != '.trc': extensionCorrect = False if not extensionCorrect: raise SignalIntegrityExceptionWaveformFile( 'incorrect extension LeCroy trace file name in ' + filename + '. Should be .trc.') except: raise SignalIntegrityExceptionWaveformFile( 'incorrect extension in LeCroy trace file name in ' + filename + '. Should be .trc.') try: with open(filename, "rb") as f: header = f.read(11).decode() start = header[0:2] if start != '#9': raise SignalIntegrityExceptionWaveformFile( 'Incorrect LeCroy trace file header in: ' + filename) count = int(header[2:11]) descData = np.frombuffer(f.read(346), np.dtype(WFM_DESC)) wfBuffer = np.frombuffer(f.read(count - 346), 'int16') if descData["NOM_SUBARRAY_COUNT"] != 1: raise SignalIntegrityExceptionWaveformFile( 'Cannot read multi-segment waveform in: ' + filename) vertScale = descData['VERTICAL_GAIN'][0] vertOffset = -descData['VERTICAL_OFFSET'][0] horizontalOffset = descData['HORIZ_OFFSET'][0] sampleRate = 1. / descData['HORIZ_INTERVAL'][0] Exp = 10**round(math.log10(sampleRate), 6) sampleRate = sampleRate / Exp sampleRate = round(sampleRate, 6) sampleRate = sampleRate * Exp numPoints = descData['WAVE_ARRAY_COUNT'][0] from SignalIntegrity.Lib.TimeDomain.Waveform.Waveform import Waveform from SignalIntegrity.Lib.TimeDomain.Waveform.TimeDescriptor import TimeDescriptor wf = Waveform( TimeDescriptor(horizontalOffset, numPoints, sampleRate), [v * vertScale + vertOffset for v in wfBuffer]) return wf except: raise SignalIntegrityExceptionWaveformFile( 'LeCroy trace file could not be read: ' + filename)
def FilterWaveform(self,wf): """filters a waveform @param wf instance of class Waveform @return instance of class Waveform containing this filter applied to wf """ # pragma: silent exclude from SignalIntegrity.Lib.TimeDomain.Waveform.Waveform import Waveform # pragma: include td = wf.td*self.FilterDescriptor() # pragma: silent exclude #filteredwf=PySIConvolve(wf.Values(),self.FilterTaps()) # pragma: include filteredwf=convolve(wf.Values(),self.FilterTaps(),'valid').tolist() return Waveform(td,filteredwf)
def TrimWaveform(self, wf): """trim a waveform @param wf instance of class Waveform of waveform to trim @return instance of class Waveform containing trimmed waveform """ # pragma: silent exclude from SignalIntegrity.Lib.TimeDomain.Waveform.Waveform import Waveform # pragma: include K = wf.td.K TL = self.TrimLeft() TT = self.TrimTotal() return Waveform( wf.td * self, [wf[k + TL] if 0 <= k + TL < K else 0. for k in range(K - TT)])
def __init__(self, filename, wfProjName=None, normalizedDCGain=None, multiplyByTs=True, derivative=False): """Constructor @param filename string file name of s-parameter file to read. @param normalizedDCGain (optional, defaults to None) DC gain to normalize response to. @param multiplyByTs (optional, defaults to True) whether to multiply the waveform by the sample period @param derivative (optional, defaults to False) whether to use the derivative of the waveform Reads the waveform file and produces SParameters. The s-parameters are for a two-port device that is "amplifier-like", meaning it has infinite input impedance, zero output impedance, the impulse response forms s21, and has infinite reverse isolation. If normalizedDCGain is 0 or None, the DC gain is not normalized, otherwise the filter is normalized by setting the sum of the values in the impulse response equal to this gain value. if multiplyByTs is True, it assumes that the impulse response waveform is an ordinary waveform, and the impulse response is formed by multiplying every value by the sample period. if derivative is True, the derivative of the waveform is taken. This is useful when you are provided a step response. """ ext = str.lower(filename).split('.')[-1] if ext == 'si': from SignalIntegrity.App.SignalIntegrityAppHeadless import ProjectWaveform wf = ProjectWaveform(filename, wfProjName) if wf == None: raise SignalIntegrityExceptionWaveformFile( 'waveform could not be produced by ' + filename) else: try: wf = Waveform().ReadFromFile(filename) except: raise SignalIntegrityExceptionWaveformFile( 'waveform could not be produced by ' + filename) if derivative: wf = wf.Derivative() if multiplyByTs: wf = wf * (1. / wf.TimeDescriptor().Fs) if not ((normalizedDCGain == None) or (normalizedDCGain == 0)): wf = wf * (1. / sum(wf)) fr = ImpulseResponse(wf).FrequencyResponse() fl = fr.Frequencies() SParameters.__init__(self, fl, [[[1., 0], [2. * r, -1]] for r in fr])
def FilterWaveform(self, wf): """overloads base class FilterWaveform @param wf instance of class Waveform @return instance of class Waveform containing the upsampled, interpolated wf @remark This method first classically upsamples the waveform by inserting zeros between the samples and then passes the upsampled waveform through the sinx/x interpolation filter. """ # pragma: silent exclude from SignalIntegrity.Lib.TimeDomain.Waveform.Waveform import Waveform # pragma: include fd = self.FilterDescriptor() us = [0. for k in range(len(wf) * fd.U)] for k in range(len(wf)): us[k * fd.U] = wf[k] return FirFilter.FilterWaveform(self, Waveform(wf.td, us))
def DenoisedWaveform(wf, pct=30., mult=5., isDerivative=True): """ Denoises a waveform. @details The noise is estimated by the amount of noise in the last pct percent of the last scale of the wavelet transform. The threshold is set as a multiplier on the noise, generally 5 is used. Thus wavelets more than 5 times the noise floor are kept, while all others are deleted (this is called hard-thresholding).\n Usually this algorithm is used to denoise TDR waveforms, which are usually steps and the denoising is performed on the derivative of the step waveform. Therefore, the noise threshold needs to be shaped based on the effect of the derivative on the noise shape. For impulsive waveforms, no derivative is performed and isDerivative is set to False, causing a white noise threshold to be assumed. @param wf instance of class Waveform to be denoised. @param pct (optional) float percentage (defaults to 30). @param mult (optional) float noise multiplier for threshold (defaults to 5). @param isDerivative (optional) bool whether the waveform is a derivative (defaults to True). @return the denoised waveform. @remark the algorithm assumes there is no signal in the last pct percent of the last scale of the dwt @see Wavelet """ w = WaveletDenoiser.wavelet Ki = wf.td.K Kf = int(pow(2, math.ceil(log2(wf.td.K)))) PadLeft = Kf - Ki pct = pct * Ki / Kf pwf = wf * WaveformTrimmer(-PadLeft, 0) X = w.DWT(pwf.Values()) T = WaveletDenoiser.DerivativeThresholdCalc(X, w.h, pct, isDerivative) # pragma: silent exclude # import matplotlib.pyplot as plt # plt.clf() # plt.title('denoising') # plt.loglog([k for k in range(len(X))],[abs(x) for x in X],label='dwt') # plt.loglog([k for k in range(len(X))],[t*mult for t in T],label='threshold') # plt.xlabel('samples') # plt.ylabel('amplitude') # plt.legend(loc='upper right') # plt.grid(True) # plt.show() # pragma: include dwf = Waveform( pwf.td, w.IDWT([0 if abs(x) < t * mult else x for (x, t) in zip(X, T)])) return dwf * WaveformTrimmer(PadLeft, 0)
def WaveformFromDefinition(self, td=None): """Computes the time-domain waveform using sums of cosines @param td instance of class TimeDescriptor declaring the time descriptor of the waveform to produce. @return wf instance of class Waveform corresponding to the frequency content. @note If td is None then the time descriptor corresponding to the frequency descriptor is used.\n The waveform produced is essentially the inverse process of __init__().\n This function should produce the exact same result as the Waveform() method, and is slow, but clearly written out to see how the waveform is produced by summing sinusoids. It used to essentially document the class.\n @see Waveform(). """ absX = self.Values('mag') theta = self.Values('deg') wf = Waveform(self.td) for n in range(self.m_f.N + 1): wf = wf + SineWaveform(self.td, Frequency=self.m_f[n], Amplitude=absX[n], Phase=theta[n] + 90) if not td is None: wf = wf.Adapt(td) return wf