Esempio n. 1
0
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
Esempio n. 2
0
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