def stfft(metAry, tres=100, fres=None, window='blackmanharris', \ fmin=None, fmax=None, mag=True, debug=False): """ Simple implementation of short time fast Fourier transform on metaArray object. metAry Input metaArray tres Temporal resolution fres Frequency resolution window Window function or None fmin Cut-off frequency for the return data, default to the 0 fmax Cut-off frequency for the return data, default to the Nyquist mag The default is to return the abs() value, return complex array if false Each window will overlap 50% with its immediate neighbours. e.g.: |_____________________________________________| | | 1 | 3 | 5 | 7 | 9 | 11| 13| 15| 17| 19| 21| | 0 | 2 | 4 | 6 | 8 | 10| 12| 14| 16| 18| 20| | """ f1 = fmax # Length of the (short) time window l = int(round(2 * len(metAry) / float(tres))) # List of (short) time window starting points winlst = linspace(0, len(metAry) - l, tres).round().astype(int) # Native RFFT frequency resolution to Nyquist lfres = int(floor(l/2.0)+1) Nyquist = 0.5 * len(metAry) / (metAry.get_range(0, 'end') - metAry.get_range(0, 'begin')) # RFFT len, use native resolution by default n = None # Generate the (short) time window function if window is None: win = 1 else: win = get_window(window, l) # Decide where to slice the rfft output as a ratio to Nyquist # Make fmax < 1 or None if fmax is not None: if fmax < Nyquist: fmax = fmax / Nyquist elif fmax >= Nyquist: fmax = None if debug: print("*** Warning, spec frequency range beyond Nyquist limit") # Check whether padding is needed # If fres is not specified, use the native resolution if fres is None: if fmax is None: # No freq limit, use native resolution fres = lfres else: # Still on native resolution, but truncated to fmax fres = int(round(fmax * lfres)) else: # fres is specified if fmax is not None: # freq limit specified, work out global freq resolution gfres = int(round(fres / fmax)) else: # No freq limit, global freq resolution is same as fres gfres = fres # Global freq resolution is greater than native freq resolution # Need padding for rfft if gfres > lfres: n = (gfres - 1) * 2 elif gfres < lfres: # No need for padding, but throwing away freq resolution for nothing if debug: print("*** Warning, frequency resolution is artificially limited") # else gfres = lfres, no need for padding, native fres is just right # Convert fmax to array length if specified if fmax is not None: # If rfft is padded if n is not None: fmax = int(round(int(floor(n/2.0)+1) * fmax)) else: # Otherwise just truncate from native output fmax = int(round(lfres * fmax)) if debug: src_len = len(metAry.data[:l]*win) rfft_len = len(np_rfft(metAry.data[:l]*win, n=n)) print("*** l: " + str(l)) print("*** lfres: " + str(lfres)) print("*** Nyquist: " + str(Nyquist)) print("*** n: " + str(n)) print("*** fmax: " + str(fmax)) print("*** fres: " + str(fres)) print("*** src_len: " + str(src_len)) print("*** rfft_len: " + str(rfft_len)) if mag: # Construct a place holder of the 2D time-freq output tfary = zeros((tres, fres)).astype(float) for i in range(len(winlst)): t = winlst[i] # Where the (short) time window starts # Do the rfft to length n, and slice to fmax, then take abs() tfary[i] = spline_resize(abs(np_rfft(metAry.data[t:t+l]*win, n=n)[:fmax]), fres) else: # Construct a place holder of the 2D time-freq output tfary = zeros((tres,fres)).astype(complex) for i in range(len(winlst)): t = winlst[i] # Do the rfft to length n, and slice to fmax tfary[i] = spline_resize(np_rfft(metAry.data[t:t+l]*win, n=n)[:fmax], fres) tfary = metaArray(tfary) try: tfary['name'] = 'STFFT{ ' + metAry['name'] + ' }' except: tfary['name'] = 'STFFT{ }' tfary['unit'] = metAry['unit'] tfary['label'] = metAry['label'] # Per axis definitions tfary.set_range(0, 'begin', metAry.get_range(0, 'begin')) tfary.set_range(0, 'end', metAry.get_range(0, 'end')) tfary.set_range(0, 'unit', metAry.get_range(0, 'unit')) tfary.set_range(0, 'label', metAry.get_range(0, 'label')) tfary.set_range(1, 'begin', 0) if f1 is None: tfary.set_range(1, 'end', Nyquist) else: tfary.set_range(1, 'end', f1) tfary.set_range(1, 'unit', 'Hz') tfary.set_range(1, 'label', 'Frequency') return tfary
def meta_resample(metAry, rate=False, l=0.005, window='hamming', order = 5): """ Resample 1D metaArray data into the given sampling rate, this is implemented using misc.spline_resize() This function distinct from the scipy.signal.resample function that, it uses spline for resampling, instead of FFT based method. Periodicity of the metAry content is not implied, or required. Inputs: metAry Input metaArray rate Sampling rate (float, in metaArray unit) l Length of the FIR filter, default to 0.5% len(metAry) mimimum 3 window Window method to generate the FIR filter order Order of spline polynomial, default to 5 Output: metaArray A resampled copy of the input metAry If upsampling, quintic spline interpolation will be used. If downsampling, two pass anti-aliasing FIR filter will be applied, once forward and once reverse to null the group delay, then quintic spline interpolation will be used. If target sampling rate is not given, it will try to find the next highest sampling rate by default. The resampled data will always align at time 0, and never exceed the duration of the given data. The sampling rate will come in multiples of 1, 2, or 5Hz, this function will modify the input array in place. """ assert metAry.ndim is 1, "Only 1D metaArray accepted, there are %i dimemsions in the given data." % metAry.ndim ary = metAry.copy() if rate == False: # Target sampling rate is not specified r = len(ary) / float(abs(ary.get_range(0, 'end') - ary.get_range(0, 'begin'))) # Find out the exponent of the current sampling rate exponent = Decimal(str(r)).adjusted() # Remove the exponent scale = r * 10**(0 - exponent) # make the standard scale slightly larger (1e-5) so numerical # error (rounding error) do not come in to play and force it up # to the next sampling scale if scale > 5.00005: scale = 10 elif scale > 2.00002: scale = 5 elif scale > 1.00001: scale = 2 else: # This really shouldnt happen, but just in case the Decimal # function return numbers like 0.123e+45 instead of 1.23e+45 scale = 1 print "Warning!! Unexpected values for scale evaluation!" + \ 'scale variable (' + str(scale) + ') should be greater than 1.' # This is what the sampling rate should be rate = scale * 10**exponent # Target size of the ary n = float(abs(ary.get_range(0, 'end') - ary.get_range(0, 'begin'))) * rate if type(l) is float: l = meta_fir_len(ary, l) # resize the data ary.data = spline_resize(ary.data, n, l=l, window=window, order = order) # Update the meta info ary.update_range() return ary