def _Pad(self, P): """Pads the impulse response The impulse response is padded to P points with an equal number of points before and after time zero. @param P integer number of points to pad the impulse response to. @return an instance of class ImpulseResponse with the zero padded points. @attention P must be even - not checked - it must add equal points to the left and right of the impulse response. @note if K is the number of points in the selfs frequency response then: | P and K | outcome |:--------:|:--------------------------------------------:| | P==K | the original response is returned | | P<K | the response is truncated to P time points | | P>K | the response is zero padded to P time points | """ K = len(self) if P == K: x = self.Values() elif P < K: x = [self[k] for k in range((K - P) // 2, K - (K - P) // 2)] else: x = [0 for p in range((P - K) // 2)] x = x + self.Values() + x td = self.td return ImpulseResponse( TimeDescriptor(td.H - (P - K) / 2. / td.Fs, P, td.Fs), x)
def TrimToThreshold(self, threshold): """truncates an impulse response, keeping only the values above or equal to specified threshold. This is useful for reducing computational complexity in processing. @param threshold float threshold to apply to the values in the waveform. @attention the threshold specified is a fraction of the maximum absolute value of the values in the waveform. @note that absolute values are utilized in value/threshold comparison. @note the trimming is performed by finding the lowest and highest time value above or equal to the threshold - all values between these times are retained. """ x = self.Values() td = self.td maxabsx = max(self.Values('abs')) minv = maxabsx * threshold for k in range(len(x)): if abs(x[k]) >= minv: startidx = k break for k in range(len(x)): ki = len(x) - 1 - k if abs(x[ki]) >= minv: endidx = ki break if (endidx - startidx + 1) // 2 * 2 != endidx - startidx + 1: # the result would not have an even number of points if endidx < len(x) - 1: # keep a point at the end if possible endidx = endidx + 1 elif startidx > 0: # keep a point at the beginning if possible startidx = startidx - 1 else: # append a zero to the end and calculate number of # points with endidx+1 return ImpulseResponse( TimeDescriptor(td[startidx], (endidx + 1) - startidx + 1, td.Fs), [x[k] for k in range(startidx, endidx + 1)] + [0.]) return ImpulseResponse( TimeDescriptor(td[startidx], endidx - startidx + 1, td.Fs), [x[k] for k in range(startidx, endidx + 1)])
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 TimeDescriptor(self, Keven=True): """associated time descriptor @param Keven boolean (optional) Whether N is from an even K points in the time domain (i.e. K/2) or from an odd K points in the time-domain (i.e. (K+1)/2). Defaults to True. @return an instance of class TimeDescriptor that corresponds to the time descriptor that would generate this frequency descriptor. @note this is assumed to be a time descriptor that would produce a frequency descriptor with self's end frequency and number of points. It does not check whether the list is evenly spaced.""" # pragma: silent exclude from SignalIntegrity.Lib.TimeDomain.Waveform.TimeDescriptor import TimeDescriptor # pragma: include N = self.N K = 2 * N if not Keven: K = K + 1 Fs = self.Fe * K / N return TimeDescriptor(-K / 2. / Fs, K, Fs)
def __init__(self, wf, fd=None): """Constructor @param wf in instance of class Waveform @param fd (optional) an instance of class FrequencyList (defaults to None) @remark initializes itself internally by computing the frequency content of the waveform. If fd is None then the frequency descriptor is simply the frequency descriptor corresponding to the time descriptor of the waveform and the frequency content is computed from the DFT. Otherwise, the CZT is used to compute the frequency content and the time descriptor corresponds to the frequency descriptor. the time descriptor and frequency descriptor are retained so a waveform can be obtained from the frequency content. @note the frequency content is scaled differently from the raw DFT or CZT outputs in that the absolute value of each complex number in the frequency content represents the amplitude of a cosine wave. This is not true with the raw DFT output and scaling things this way helps in the proper interpretation of the frequency content without having to think about the vagaries of the DFT. @see TimeDescriptor @see FrequencyList @see ChirpZTransform """ td = wf.td if fd is None: X = fft.fft(wf.Values()) K = int(td.K) Keven = (K // 2) * 2 == K fd = td.FrequencyList() else: # pragma: silent exclude if not fd.EvenlySpaced(): raise SignalIntegrityExceptionWaveform( 'cannot generate frequency content') # pragma: include K = fd.N * 2 Keven = True X = CZT(wf.Values(), td.Fs, 0, fd.Fe, fd.N, True) td = TimeDescriptor(td.H, fd.N * 2, fd.Fe * 2.) FrequencyDomain.__init__(self,fd,[X[n]/K*\ (1. if (n==0 or ((n==fd.N) and Keven)) else 2.)*\ cmath.exp(-1j*2.*math.pi*fd[n]*td.H) for n in range(fd.N+1)]) self.td = td
def __init__(self,fileName,td=None): if not td is None: HorOffset=td.H NumPts=td.K SampleRate=td.Fs else: HorOffset=0.0 NumPts=0 SampleRate=1. with open(fileName,'rb') as f: wf = [float(line) for line in f] if NumPts==0: NumPts=len(wf) else: if len(wf) > NumPts: wf = [wf[k] for k in range(NumPts)] else: NumPts=len(wf) Waveform.__init__(self,TimeDescriptor(HorOffset,NumPts,SampleRate),wf)
def ReadFromFile(self,fileName): """reads a waveform from a file @param fileName string name of file to read @return self @note this DOES affect self @note the normal waveform format is one number per line starting with the horizontal offset followed by the number of points followed by the sample rate. The remaining lines contain one waveform point per line. This is the format output by SignalIntegrity. However, if the data is all on one line, then the format is assumed to be LeCroy MathPack format with the first point being the number of points, and the remaining points being time and value. @note if the file extension is '.trc', then LeCroy waveform format is assumed """ # pragma: silent exclude _, file_extension = os.path.splitext(fileName) if file_extension == '.trc': self.ReadLeCroyWaveform(fileName) return self try: # pragma: include outdent with open(fileName,'rU' if sys.version_info.major < 3 else 'r') as f: data=f.readlines() # pragma: silent exclude if len(data)==1: data=data[0].split() NumPts=int(float(data[0])+0.5) HorOffset=float(data[1]) SampleRate=1./(float(data[3])-HorOffset) Values=[float(data[k*2+2]) for k in range(NumPts)] else: # pragma: silent include outdent HorOffset=float(data[0]) NumPts=int(float(data[1])+0.5) SampleRate=float(data[2]) Values=[float(data[k+3]) for k in range(NumPts)] # pragma: silent indent self.td=TimeDescriptor(HorOffset,NumPts,SampleRate) list.__init__(self,Values) # pragma: silent exclude indent except IOError: raise SignalIntegrityExceptionWaveformFile(fileName+' not found') # pragma: include return self
def ImpulseResponse(self,td=None,adjustDelay=True): """the time-domain impulse response @param td (optional) instance of class TimeDescriptor. @param adjustDelay (optional) bool whether to adjust the delay. @return instance of class ImpulseResponse corresponding to the frequency response. @remark If the optional time descriptor is supplied, the resulting impulse response is resampled onto that time descriptor. @note internally, the frequency response is either evenly spaced or not. whether evenly spaced, whether a time descriptor is specified and whether to adjust delay determines all possibilities. | evenly spaced | time descriptor | adjust delay | Situation | |:------------: |:---------------:|:------------:|:--------------------------------------------------- | | False | False | X | Cannot be done | | False | True | X | Spline resamples to time descriptor | | True | False | False | generic impulse response | | True | False | True | impulse response with delay adjusted | | True | True | X | CZT resamples to td - ad forced to T | Much of these options are meant for internal use. Mostly you should simply use ImpulseResponse() with the default arguments. @remark td can also be supplied as a float or int. In this situation, the TimeDescriptor corresponding to the internal FrequencyList is used, but the td supplied supplants the sample rate of the TimeDescriptor. In this way, only the sample rate can be specified in the resampling, and all processing as shown in the table above will assume that a time descriptor has been supplied of this calculated type. """ fd = self.FrequencyList() if isinstance(td,float) or isinstance(td,int): Fs=float(td) td=fd.TimeDescriptor() td = TimeDescriptor(0.,2*int(math.ceil(Fs*td.K/2./td.Fs)),Fs) evenlySpaced = fd.CheckEvenlySpaced() if not evenlySpaced and td is None: return None if not evenlySpaced and not td is None: newfd = td.FrequencyList() oldfd = fd Poly=Spline(oldfd,self.Response()) newresp=[Poly.Evaluate(f) if f <= oldfd[-1] else 0.0001 for f in newfd] newfr=FrequencyResponse(newfd,newresp) return newfr.ImpulseResponse(None,adjustDelay) if evenlySpaced and td is None and not adjustDelay: yfp=self.Response() ynp=[yfp[fd.N-nn].conjugate() for nn in range(1,fd.N)] y=yfp+ynp y[0]=y[0].real y[fd.N]=y[fd.N].real Y=fft.ifft(y) td=fd.TimeDescriptor() tp=[Y[k].real for k in range(td.K//2)] tn=[Y[k].real for k in range(td.K//2,td.K)] Y=tn+tp return ImpulseResponse(td,Y) if evenlySpaced and td is None and adjustDelay: TD=self._FractionalDelayTime() return self._DelayBy(-TD).ImpulseResponse(None,False).DelayBy(TD) if evenlySpaced and not td is None: # if td is a float and not a time descriptor, it's assumed to be a # sample rate. In this case, the number of points in a # time descriptor are filled in representing the time content of self return self.Resample(td.FrequencyList()).ImpulseResponse()
def TimeDescriptor(bitRate, sampleRate, patternLength): timeLength = patternLength / bitRate numPoints = int(math.floor(timeLength * sampleRate + 0.5)) return TimeDescriptor(0., numPoints, sampleRate)
def __init__(self, sp, port=1, method='exact', align='middle', includePortZ=True, adjustForDelay=True): """Constructor @param sp instance of class SParameters of the device @param port (optional) integer 1 based port number (defaults to port 1) @param method (optional) string method for computation (defaults to 'exact') @param align (optional) string alignment of impedancance in waveform (defaults to 'middle') @param includePortZ (optional) boolean whether to put the port reference impedance as the first point. (defaults to True) @param adjustForDelay (optional) boolean whether to adjust for the delay in the impulse response (defaults to True) @remark computation methods include: 'exact' (default) - calculates using reflection coefficient of first point computed from DFT and deembedding. (this method takes longer and can diverge due to buildup of numerical inaccuracies.) 'estimated' - calculates the reflection coefficients directly from the step response. 'approximate' - calculates the reflection coefficients from the step response using an approximation. @remark alignment methods include: 'middle' (default) - shows the impedance in the middle of the sample period distance along the line. 'front' - shows the impedance measured at the front of the line. """ tdsp = sp.m_f.TimeDescriptor() # assumes middle and no portZ tdip = TimeDescriptor(1. / (tdsp.Fs * 4), tdsp.K / 2, tdsp.Fs * 2) if not align == 'middle': tdip.H = tdip.H - 1. / (tdsp.Fs * 4) if method == 'exact': ip = ImpedanceProfile(sp, tdip.K, port) Z = ip.Z() delayAdjust = ip.m_fracD elif method == 'estimated' or method == 'approximate': fr = sp.FrequencyResponse(port, port) rho = fr.ImpulseResponse().Integral(addPoint=True, scale=False) delayAdjust = fr._FractionalDelayTime() finished = False for m in range(len(rho)): if finished: rho[m] = rho[m - 1] continue rho[m] = max(-self.rhoLimit, min(self.rhoLimit, rho[m])) if abs(rho[m]) == self.rhoLimit: finished = True if method == 'estimated': Z = [ max( 0., min( sp.m_Z0 * (1 + rho[tdsp.K // 2 + 1 + k]) / (1 - rho[tdsp.K // 2 + 1 + k]), self.ZLimit)) for k in range(tdip.K) ] else: Z = [ max( 0., min(sp.m_Z0 + 2 * sp.m_Z0 * rho[tdsp.K // 2 + 1 + k], self.ZLimit)) for k in range(tdip.K) ] if includePortZ: tdip.H = tdip.H - 1. / (tdsp.Fs * 2) tdip.K = tdip.K + 1 Z = [sp.m_Z0] + Z if adjustForDelay: tdip.H = tdip.H + delayAdjust / 2 Waveform.__init__(self, tdip, Z)