Exemple #1
0
def optimal_fft_size(target, real = False):
    """Wrapper around scipy function next_fast_len() for calculating optimal FFT padding.
    scipy.fft was only added in 1.4.0, so we fall back to scipy.fftpack
    if it is not available. The main difference is that next_fast_len()
    does not take a second argument in the older implementation.

    Parameters
    ----------
    target : int
        Length to start searching from. Must be a positive integer.
    real : bool, optional
        True if the FFT involves real input or output, only available
        for scipy > 1.4.0
    Returns
    -------
    int
        Optimal FFT size.
    """

    try: # pragma: no cover
        from scipy.fft import next_fast_len

        support_real = True

    except ImportError: # pragma: no cover
        from scipy.fftpack import next_fast_len

        support_real = False

    if support_real: # pragma: no cover
        return next_fast_len(target, real)
    else: # pragma: no cover
        return next_fast_len(target)
def self_convolve(
    input_list: typing.List[float],
    num_times: int,
    tail_mass_truncation: float = 0) -> typing.Tuple[int, typing.List[float]]:
  """Computes a convolution of the input list with itself num_times times.

  Args:
    input_list: The input list to be convolved.
    num_times: The number of times the list is to be convolved with itself.
    tail_mass_truncation: an upper bound on the tails of the output that might
      be truncated.

  Returns:
    A pair of truncation_lower_bound, output_list, where the i-th entry of
    output_list is approximately the sum, over all i_1, i_2, ..., i_num_times
    such that i_1 + i_2 + ... + i_num_times = i + truncation_lower_bound,
    of input_list[i_1] * input_list[i_2] * ... * input_list[i_num_times].
  """
  truncation_lower_bound, truncation_upper_bound = compute_self_convolve_bounds(
      input_list, num_times, tail_mass_truncation)

  # Use FFT to compute the convolution
  fast_len = fft.next_fast_len(truncation_upper_bound -
                                          truncation_lower_bound + 1)
  truncated_convolution_output = np.real(
      fft.ifft(fft.fft(input_list, fast_len)**num_times))

  # Discrete Fourier Transform wraps around module fast_len. Extract the output
  # values in the range of interest.
  output_list = [
      truncated_convolution_output[i % fast_len]
      for i in range(truncation_lower_bound, truncation_upper_bound + 1)
  ]

  return truncation_lower_bound, output_list
Exemple #3
0
 def test_new_shape_no_size(self):
     """Test the make a new shape with even and odd numbers when no size is specified, i.e. test auto padding."""
     oldshape = (2 * 17, 17)
     data = np.zeros(oldshape)
     newshape = tuple(next_fast_len(s) for s in oldshape)
     newdata = fft_pad(data)
     assert newshape == newdata.shape
Exemple #4
0
    def __init__(self, n, fn, m=None, *, fs=2, endpoint=False):
        m = _validate_sizes(n, m)

        k = arange(max(m, n), dtype=np.min_scalar_type(-max(m, n)**2))

        if np.size(fn) == 2:
            f1, f2 = fn
        elif np.size(fn) == 1:
            f1, f2 = 0.0, fn
        else:
            raise ValueError('fn must be a scalar or 2-length sequence')

        self.f1, self.f2, self.fs = f1, f2, fs

        if endpoint:
            scale = ((f2 - f1) * m) / (fs * (m - 1))
        else:
            scale = (f2 - f1) / fs
        a = cmath.exp(2j * pi * f1 / fs)
        wk2 = np.exp(-(1j * pi * scale * k**2) / m)

        self.w = cmath.exp(-2j * pi / m * scale)
        self.a = a
        self.m, self.n = m, n

        ak = np.exp(-2j * pi * f1 / fs * k[:n])
        self._Awk2 = ak * wk2[:n]

        nfft = next_fast_len(n + m - 1)
        self._nfft = nfft
        self._Fwk2 = fft(1 / np.hstack((wk2[n - 1:0:-1], wk2[:m])), nfft)
        self._wk2 = wk2[:m]
        self._yidx = slice(n - 1, n + m - 1)
Exemple #5
0
def test_rfft(Nmax=1_000_000):
    """ измерить скорость RFFT - БПФ для действительных чисел.
    сравнивает варианты с неизменной длиной входных данных и дополненных
    до оптимальной длины .next_fast_len() """

    fig, ax = plt.subplots()
    for k in ('len(N)', 'next_fast_len()'):
        Nmark = []
        speedMark = []
        N = 2
        while int(N) < Nmax:
            if k == 'len(N)':
                L = int(N)
            else:
                L = fft.next_fast_len(int(N), False)

            r = test_time_rfft(int(N), L)
            print("{} N = {} time = {}".format(k, N, r))
            Nmark.append(int(N))
            speedMark.append(r)
            N = N * 1.01

        ax.plot(Nmark, speedMark, label=k)

    ax.legend()
    plt.title('scipy rfft speed test')
    plt.xlabel('array len, [N]')
    plt.ylabel('time, [s]')
    plt.savefig('rfft_speed_test.png')
    plt.show()
Exemple #6
0
    def cwt(self, signal: np.ndarray, scales: np.ndarray, dt=1):
        """Continuous wavelet transform.

        Parameters
        ----------
        signal : np.ndarray
            The signal to transform.
        scales : np.ndarray
            Wavelet scales.
        dt : float
            The sampling period of the signal.
        """
        # Find the fast length for the FFT
        n = len(signal)
        fast_len = fft.next_fast_len(n)

        # Signal in frequency domain
        fs = fft.fftfreq(fast_len, d=dt)
        f_sig = fft.fft(signal, n=fast_len)

        # Compute the wavelet transform
        psi = np.array([self.psi_f(fs, scale) for scale in scales])
        W = fft.ifft(f_sig * psi, workers=-1)[..., :n]

        freqs = self.central_freq(scales)

        return freqs, W
Exemple #7
0
def fft_gaussian_filter(img, sigma):
    """FFT gaussian convolution.

    Parameters
    ----------
    img : ndarray
        Image to convolve with a gaussian kernel
    sigma : int or sequence
        The sigma(s) of the gaussian kernel in _real space_

    Returns
    -------
    filt_img : ndarray
        The filtered image
    """
    # This doesn't help agreement but it will make things faster
    # pull the shape
    s1 = np.array(img.shape)
    # s2 = np.array([int(s * 4) for s in _normalize_sequence(sigma, img.ndim)])
    shape = s1  # + s2 - 1
    # calculate a nice shape
    fshape = [next_fast_len(int(d)) for d in shape]
    # pad out with reflection
    pad_img = fft_pad(img, fshape, "reflect")
    # calculate the padding
    padding = tuple(_calc_pad(o, n) for o, n in zip(img.shape, pad_img.shape))
    # so that we can calculate the cropping, maybe this should be integrated
    # into `fft_pad` ...
    fslice = tuple(
        slice(s, -e) if e != 0 else slice(s, None) for s, e in padding)
    # fourier transfrom and apply the filter
    kimg = rfftn(pad_img, fshape)
    filt_kimg = fourier_gaussian(kimg, sigma, pad_img.shape[-1])
    # inverse FFT and return.
    return irfftn(filt_kimg, fshape)[fslice]
Exemple #8
0
def convolve_images(image, kernel, mode='full', method='direct'):
    """
    Convolves image and kernel using square differences sums
    only 'full' mode is available
    :param image: gray image where we detect the kernel
    :param kernel: gray image which we detect in the image
    :param mode: mode of convolution "valid" or "full" (just for fft)
    :param method: method of convolution "direct" or "fft"
    :return: gray image, where black color shows the overlap
    """

    image_vect, kernel_vect = construct_loss_func(image, kernel, method)

    shape = tuple([
        image.shape[i] + kernel.shape[i] - 1 for i in range(len(image.shape))
    ])
    conv_shape = tuple([
        image.shape[i] - kernel.shape[i] + 1 for i in range(len(image.shape))
    ])
    # print(f"shape: {shape}\nconv_shape: {conv_shape}\n")

    if method == "direct":
        if mode == 'valid':
            convolution = np.zeros(conv_shape)
        else:
            assert False, "!!!No such mode available!!!"

        for y in range(image_vect.shape[2]):
            if y > image_vect.shape[2] - kernel.shape[1]:
                break

            for x in range(image_vect.shape[1]):
                if x > image_vect.shape[1] - kernel.shape[0]:
                    break

                curr_image_vect = image_vect[:, x:x + kernel.shape[0],
                                             y:y + kernel.shape[1]]
                convolution[x, y] = (curr_image_vect * kernel_vect).sum()

    elif method == "fft":
        full_shape = tuple(
            [next_fast_len(shape[i], True) for i in range(len(image.shape))])
        convolution = np.zeros(full_shape)

        for idx in range(image_vect.shape[0]):
            fft_image = rfftn(image_vect[idx, :, :], full_shape)
            fft_kernel = rfftn(kernel_vect[idx, :, :], full_shape)
            conv = irfftn(fft_image * fft_kernel, full_shape)
            convolution += conv

        conv_slice = tuple([slice(sz) for sz in shape])  # to get back to shape
        convolution = convolution[conv_slice]  # needed full_shape for speed

        if mode == 'valid':
            convolution = get_centered_image(convolution, conv_shape)

    else:
        assert False, "!!!No such method for convolution!!!"

    return convolution
Exemple #9
0
def spectrum(a, delta=1, **kwargs):
    """
    Return frequencies, amplitude and phase of FFT.
    """
    nfreq = kwargs.get('nfreq')
    if nfreq is None:
        nfreq = next_fast_len(a.size)
    spectrum = rfft(a, nfreq)
    freqs = rfftfreq(n=nfreq, d=delta)

    am = np.abs(spectrum)
    ph = np.angle(spectrum)

    return freqs, am, ph
Exemple #10
0
def optimize_dims(dims, mode):
    """Computes FFT Length for data padded out to optimize FFT implementations"""
    from scipy import fft
    mode = mode.upper()

    # Round FFT Length up to next nearest optimal value based on mode given
    if mode == OPM_LOG2:
        return 2**np.ceil(np.log2(dims)).astype(int)
    elif mode == OPM_2357:
        return np.array([_rainbow_next_largest(d) for d in dims])
    elif mode == OPM_FFTP:
        return np.array([fft.next_fast_len(int(sz)) for sz in dims])
    elif mode != OPM_NONE:
        raise ValueError('Padding mode "{}" invalid'.format(mode))
    return dims
Exemple #11
0
def overlap_save_convolution(xt, ht):
    _M = len(ht)
    overlap = _M - 1
    _N = fft.next_fast_len(8 * overlap)
    step_size = _N - overlap
    _H = np.fft.rfft(ht, _N)
    pos = 0

    yt = np.zeros(len(xt))

    while pos + _N <= len(xt):
        yt_k = np.fft.irfft(np.fft.rfft(xt[pos:pos+_N]) * _H)
        yt[pos:pos+step_size] = np.real(yt_k[_M-1:_N])
        pos += step_size

    return yt, pos
Exemple #12
0
def self_convolve(input_list: typing.List[float],
                  num_times: int) -> typing.List[float]:
    """Computes a convolution of the input list with itself num_times times.

  Args:
    input_list: The input list to be convolved.
    num_times: The number of times the list is to be convolved with itself.

  Returns:
    The list where for each i-th entry its corresponding value is the sum, over
    all i_1, i_2, ..., i_num_times such that i_1 + i_2 + ... + i_num_times = i,
    of input_list[i_1] * input_list[i_2] * ... * input_list[i_num_times].
  """
    # Use FFT to compute the convolution
    output_len = num_times * len(input_list) - 1
    fast_len = fft.next_fast_len(output_len)
    return numpy.real(fft.ifft(fft.fft(input_list,
                                       fast_len)**num_times))[:output_len]
Exemple #13
0
def phase_shift(delta, dr, per, pv, spc=None, data=None):
    """
    Perfrom phase shift.

    :param delta: Sample spacing.
    :param dr: Difference of differential or sum of source-receiver distances
        and receiver-distance.
    """
    if spc is None:
        nfreq = next_fast_len(data.size)
        spc = fft(data, nfreq)
    else:
        nfreq = spc.size
    freq = fftfreq(nfreq, d=delta)
    dph = _phase(freq, dr, per, pv)
    spc = spc * np.exp(1j * dph)
    data_ps = ifft(spc, nfreq).real

    return data_ps
Exemple #14
0
    def run(self, win_data: WindowedData) -> SpectraData:
        """
        Perform the FFT

        Data is padded to the next fast length before performing the FFT to
        speed up processing. Therefore, the output length may not be as
        expected.

        Parameters
        ----------
        win_data : WindowedData
            The input windowed data

        Returns
        -------
        SpectraData
            The Fourier transformed output
        """
        from scipy.fft import next_fast_len, rfftfreq

        metadata_dict = win_data.metadata.dict()
        data = {}
        spectra_levels_metadata = []
        messages = []
        logger.info("Performing fourier transforms of windowed decimated data")
        for ilevel in range(win_data.metadata.n_levels):
            logger.info(f"Transforming level {ilevel}")
            level_metadata = win_data.metadata.levels_metadata[ilevel]
            win_size = level_metadata.win_size
            n_transform = next_fast_len(win_size, real=True)
            logger.debug(
                f"Padding size {win_size} to next fast len {n_transform}")
            freqs = rfftfreq(n=n_transform, d=1.0 / level_metadata.fs).tolist()
            data[ilevel] = self._get_level_data(level_metadata,
                                                win_data.get_level(ilevel),
                                                n_transform)
            spectra_levels_metadata.append(
                self._get_level_metadata(level_metadata, freqs))
            messages.append(f"Calculated spectra for level {ilevel}")
        metadata = self._get_metadata(metadata_dict, spectra_levels_metadata)
        metadata.history.add_record(self._get_record(messages))
        logger.info("Fourier transforms completed")
        return SpectraData(metadata, data)
Exemple #15
0
def fft_pad(array, newshape=None, mode="median", **kwargs):
    """Pad an array to prep it for FFT."""
    # pull the old shape
    oldshape = array.shape
    if newshape is None:
        # update each dimension to a 5-smooth hamming number
        newshape = tuple(next_fast_len(n) for n in oldshape)
    else:
        if hasattr(newshape, "__iter__"):
            # are we iterable?
            newshape = tuple(newshape)
        elif isinstance(newshape, int) or np.issubdtype(newshape, np.integer):
            # test for regular python int, then numpy ints
            newshape = tuple(newshape for _ in oldshape)
        else:
            raise ValueError(f"{newshape} is not a recognized shape")
    # generate padding and slices
    padding, slices = _padding_slices(oldshape, newshape)
    return np.pad(array[slices], padding, mode=mode, **kwargs)
Exemple #16
0
def main():
    if len(sys.argv)>1:
        fileName = sys.argv[1]
    else:
        fileName = None
        
    """
    write_wav_test('wav_test.wav')
    wav2guitar_distortion('wav_test.wav')
    """
    fileName = 'music.wav'
    
    with CPlotter(fileName) as plot,CAudioSource(fileName) as sound:
        FFTLEN = fft.next_fast_len(sound.blockLen,True)
        image_freq = fft.rfftfreq(FFTLEN,1./sound.fps)
        repeat = True
        while repeat!=False:
            data,repeat = sound.readData()
            w = hamming(len(data),False)
            image_fft = fft.rfft(data*w,FFTLEN)
            plot.replot(image_fft,image_freq,FFTLEN)
Exemple #17
0
def fft_hankel(integrator,
               integrator_args,
               integrator_kwargs,
               log_limit,
               npoints,
               in_ndim=0,
               is_complex=True,
               fft=scipy.fft):
    """Inverse HankelTransform using Fast Fourier Transform"""
    if hasattr(fft, "next_fast_len"):
        npoints = fft.next_fast_len(npoints, not is_complex)
    sp = np.linspace(-log_limit, log_limit, npoints)
    dt = sp[1] - sp[0]
    wq = 2 * pi * (fft.fftfreq if is_complex else fft.rfftfreq)(len(sp), dt)
    dw = wq[1] - wq[0]
    sp = _expand_first_ndim(sp, in_ndim)
    wq = _expand_first_ndim(wq, in_ndim)
    s = np.exp(-sp)
    r = np.exp(sp)
    # Weird tricks ahead to reduce memory usage
    res = np.nan_to_num(integrator(s, *integrator_args, **integrator_kwargs),
                        copy=False)
    res *= s
    shifted = fft.ifftshift(res, axes=-1)
    del res
    fres = (fft.fft if is_complex else fft.rfft)(shifted,
                                                 norm="ortho",
                                                 axis=-1)
    del shifted
    fres *= dt
    fres *= fourierBesselJv(wq)
    shifted_hres = (fft.ifft if is_complex else fft.irfft)(fres,
                                                           norm="ortho",
                                                           axis=-1)
    del fres
    hres = fft.ifftshift(shifted_hres, axes=-1)
    del shifted_hres
    hres *= sp.size * dw / (2 * pi)
    hres /= r
    return sp, hres
Exemple #18
0
def gpu_fftconvolve(in1, in2, axes=None):
    _, axes = _init_nd_shape_and_axes(in1, shape=None, axes=axes)

    s1 = in1.shape
    s2 = in2.shape

    shape = [
        max((s1[i], s2[i])) if i not in axes else s1[i] + s2[i] - 1
        for i in range(in1.ndim)
    ]

    fshape = [next_fast_len(shape[a], True) for a in axes]

    sp1 = cp.fft.rfft2(in1, fshape, axes=axes)
    sp2 = cp.fft.rfft2(in2, fshape, axes=axes)

    ret = cp.fft.irfft2(sp1 * sp2, fshape, axes=axes)

    fslice = tuple([slice(sz) for sz in shape])
    ret = ret[fslice]

    return _centered(ret, s1).copy()
def cube_spectral_density(
    signal: np.ndarray,
    square=False,
    sample_freq: float = 20.0,
) -> np.ndarray:
    """calculate custom power spectral density with cubed or squared coefficients

    Args:
        signal (np.ndarray): input samples. No gaps or NaNs.
        square (bool, optional): whether to square or cube the coefficients. Defaults to False.
        sample_freq (float, optional): sample frequency of input signal, in Hz. Defaults to 20.0.

    Returns:
        np.ndarray: cubed (or squared) and normalized DFT coefficients
    """
    N = len(signal)
    if square:
        exponent = 2
    else:
        exponent = 3
    coeffs = rfft(signal, norm="backward", n=next_fast_len(N)) / np.sqrt(N)
    coeffs = np.power(np.absolute(coeffs), exponent) / sample_freq
    if not square:
        # rfft default normalization doesn't anticipate cubing, so has an extra factor of sqrt(2)
        # Not sure derivation but confirmed via manual test of E[x^3] - E[x]^3
        coeffs /= np.sqrt(2)

    # one-sidedness correction due to rfft omitting negative frequencies.
    # Explanation here: https://www.reddit.com/r/matlab/comments/4cqa10/fft_dc_component_scaling/
    if N % 2:  # odd, no nyquist coeff
        coeffs[1:] = coeffs[1:] * 2
    else:  # even, don't double Nyquist (last) coeff
        coeffs[1:-1] = coeffs[1:-1] * 2

    # remove meaningless DC term. DC component is just sum(signal), which then gets cubed to a meaningless value. Have to remove in downstream integration anyway.
    coeffs[0] = 0

    return coeffs
Exemple #20
0
def conv_spc(in1, in2):
    """
    Return spectra of convolution (spectral multiplication)
    of two 1-D signals.

    https://github.com/scipy/scipy/blob/v1.6.0/scipy/signal/signaltools.py#L551-L663
    """
    in1 = np.asarray(in1)
    in2 = np.asarray(in2)

    if in1.ndim == in2.ndim == 0:  # scalar inputs
        return in1 * in2
    elif in1.ndim != in2.ndim:
        raise ValueError("in1 and in2 should have the same dimensionality")
    elif in1.size == 0 or in2.size == 0:  # empty arrays
        return np.array([])

    # Speed up FFT by padding to optimal size
    fshape = next_fast_len(in1.size + in2.size - 1)
    sp1 = fft(in1, fshape)
    sp2 = fft(in2, fshape)

    return sp1 * sp2
Exemple #21
0
    def __init__(self, n, m=None, w=None, a=1 + 0j):
        m = _validate_sizes(n, m)

        k = arange(max(m, n), dtype=np.min_scalar_type(-max(m, n)**2))

        if w is None:
            # Nothing specified, default to FFT-like
            w = cmath.exp(-2j * pi / m)
            wk2 = np.exp(-(1j * pi * ((k**2) % (2 * m))) / m)
        else:
            # w specified
            wk2 = w**(k**2 / 2.)

        a = 1.0 * a  # at least float

        self.w, self.a = w, a
        self.m, self.n = m, n

        nfft = next_fast_len(n + m - 1)
        self._Awk2 = a**-k[:n] * wk2[:n]
        self._nfft = nfft
        self._Fwk2 = fft(1 / np.hstack((wk2[n - 1:0:-1], wk2[:m])), nfft)
        self._wk2 = wk2[:m]
        self._yidx = slice(n - 1, n + m - 1)
Exemple #22
0
def fftconvolve_fast(data, kernel, **kwargs):
    """FFT convolution, a faster version than scipy.

    In this case the kernel ifftshifted before FFT but the data is not.
    This can be done because the effect of fourier convolution is to
    "wrap" around the data edges so whether we ifftshift before FFT
    and then fftshift after it makes no difference so we can skip the
    step entirely.
    """
    # TODO: add error checking like in the above and add functionality
    # for complex inputs. Also could add options for different types of
    # padding.
    dshape = np.array(data.shape)
    kshape = np.array(kernel.shape)
    # find maximum dimensions
    maxshape = np.max((dshape, kshape), 0)
    # calculate a nice shape
    fshape = [next_fast_len(int(d)) for d in maxshape]
    # pad out with reflection
    pad_data = fft_pad(data, fshape, "reflect")
    # calculate padding
    padding = tuple(
        _calc_pad(o, n) for o, n in zip(data.shape, pad_data.shape))
    # so that we can calculate the cropping, maybe this should be integrated
    # into `fft_pad` ...
    fslice = tuple(
        slice(s, -e) if e != 0 else slice(s, None) for s, e in padding)
    if kernel.shape != pad_data.shape:
        # its been assumed that the background of the kernel has already been
        # removed and that the kernel has already been centered
        kernel = fft_pad(kernel, pad_data.shape, mode="constant")
    k_kernel = rfftn(ifftshift(kernel), pad_data.shape, **kwargs)
    k_data = rfftn(pad_data, pad_data.shape, **kwargs)
    convolve_data = irfftn(k_kernel * k_data, pad_data.shape, **kwargs)
    # return data with same shape as original data
    return convolve_data[fslice]
def spec_pgram(x,
               xfreq=1,
               spans=None,
               kernel=None,
               taper=0.1,
               pad=0,
               fast=True,
               demean=False,
               detrend=True,
               plot=True,
               **kwargs):
    """
    Computes the spectral density estimate using a periodogram.  Optionally, it also:
    - Uses a provided kernel window, or a sequence of spans for convoluted modified Daniell kernels.
    - Tapers the start and end of the series to avoid end-of-signal effects.
    - Pads the provided series before computation, adding pad*(length of series) zeros at the end.
    - Pads the provided series before computation to speed up FFT calculation.
    - Performs demeaning or detrending on the series.
    - Plots results.
    
    Implemented to ensure compatibility with R's spectral functions, as opposed to reusing scipy's periodogram.
    
    Adapted from R's stats::spec.pgram.
    """
    def daniell_window_modified(m):
        """ Single-pass modified Daniell kernel window.
        
        Weight is normalized to add up to 1, and all values are the same, other than the first and the
        last, which are divided by 2.
        """
        def w(k):
            return np.where(
                np.abs(k) < m, 1 / (2 * m),
                np.where(np.abs(k) == m, 1 / (4 * m), 0))

        return w(np.arange(-m, m + 1))

    def daniell_window_convolve(v):
        """ Convolved version of multiple modified Daniell kernel windows.
        
        Parameter v should be an iterable of m values.
        """

        if len(v) == 0:
            return np.r_[1]

        if len(v) == 1:
            return daniell_window_modified(v[0])

        return signal.convolve(daniell_window_modified(v[0]),
                               daniell_window_convolve(v[1:]))

    # Ensure we can store non-integers in x, and that it is a numpy object
    x = np.r_[x].astype('float64')
    original_shape = x.shape

    # Ensure correct dimensions
    assert len(original_shape) <= 2, "'x' must have at most 2 dimensions"
    while len(x.shape) < 2:
        x = np.expand_dims(x, axis=1)

    N, nser = x.shape
    N0 = N

    # Ensure only one of spans, kernel is provided, and build the kernel window if needed
    assert (spans is None) or (
        kernel is None), "must specify only one of 'spans' or 'kernel'"
    if spans is not None:
        kernel = daniell_window_convolve(np.floor_divide(np.r_[spans], 2))

    # Detrend or demean the series
    if detrend:
        t = np.arange(N) - (N - 1) / 2
        sumt2 = N * (N**2 - 1) / 12
        x -= (np.repeat(np.expand_dims(np.mean(x, axis=0), 0), N, axis=0) +
              np.outer(np.sum(x.T * t, axis=1), t / sumt2).T)
    elif demean:
        x -= np.mean(x, axis=0)

    # Compute taper and taper adjustment variables
    x = spec_taper(x, taper)
    u2 = (1 - (5 / 8) * taper * 2)
    u4 = (1 - (93 / 128) * taper * 2)

    # Pad the series with copies of the same shape, but filled with zeroes
    if pad > 0:
        x = np.r_[x, np.zeros((pad * x.shape[0], x.shape[1]))]
        N = x.shape[0]

    # Further pad the series to accelerate FFT computation
    if fast:
        newN = fft.next_fast_len(N, True)
        x = np.r_[x, np.zeros((newN - N, x.shape[1]))]
        N = newN

    # Compute the Fourier frequencies (R's spec.pgram convention style)
    Nspec = int(np.floor(N / 2))
    freq = (np.arange(Nspec) + 1) * xfreq / N

    # Translations to keep same row / column convention as stats::mvfft
    xfft = fft.fft(x.T).T

    # Compute the periodogram for each i, j
    pgram = np.empty((N, nser, nser), dtype='complex')
    for i in range(nser):
        for j in range(nser):
            pgram[:, i, j] = xfft[:, i] * np.conj(xfft[:, j]) / (N0 * xfreq)
            pgram[0, i, j] = 0.5 * (pgram[1, i, j] + pgram[-1, i, j])

    if kernel is None:
        # Values pre-adjustment
        df = 2
        bandwidth = np.sqrt(1 / 12)
    else:

        def conv_circular(signal, kernel):
            """
            Performs 1D circular convolution, in the same style as R::kernapply,
            assuming the kernel window is centered at 0.
            """
            pad = len(signal) - len(kernel)
            half_window = int((len(kernel) + 1) / 2)
            indexes = range(-half_window, len(signal) - half_window)
            orig_conv = np.real(
                fft.ifft(
                    fft.fft(signal) * fft.fft(np.r_[np.zeros(pad), kernel])))
            return orig_conv.take(indexes, mode='wrap')

        # Convolve pgram with kernel with circular conv
        for i in range(nser):
            for j in range(nser):
                pgram[:, i, j] = conv_circular(pgram[:, i, j], kernel)

        df = 2 / np.sum(kernel**2)
        m = (len(kernel) - 1) / 2
        k = np.arange(-m, m + 1)
        bandwidth = np.sqrt(np.sum((1 / 12 + k**2) * kernel))

    df = df / (u4 / u2**2) * (N0 / N)
    bandwidth = bandwidth * xfreq / N

    # Remove padded results
    pgram = pgram[1:(Nspec + 1), :, :]

    spec = np.empty((Nspec, nser))
    for i in range(nser):
        spec[:, i] = np.real(pgram[:, i, i])

    if nser == 1:
        coh = None
        phase = None
    else:
        coh = np.empty((Nspec, int(nser * (nser - 1) / 2)))
        phase = np.empty((Nspec, int(nser * (nser - 1) / 2)))
        for i in range(nser):
            for j in range(i + 1, nser):
                index = int(i + j * (j - 1) / 2)
                coh[:, index] = np.abs(pgram[:, i,
                                             j])**2 / (spec[:, i] * spec[:, j])
                phase[:, index] = np.angle(pgram[:, i, j])

    spec = spec / u2
    spec = spec.squeeze()

    results = {
        'freq': freq,
        'spec': spec,
        'coh': coh,
        'phase': phase,
        'kernel': kernel,
        'df': df,
        'bandwidth': bandwidth,
        'n.used': N,
        'orig.n': N0,
        'taper': taper,
        'pad': pad,
        'detrend': detrend,
        'demean': demean,
        'method':
        'Raw Periodogram' if kernel is None else 'Smoothed Periodogram'
    }

    if plot:
        plot_spec(results, coverage=0.95, **kwargs)

    return results
Exemple #24
0
def test_next_fast_len():
    for n in _5_smooth_numbers:
        assert_equal(next_fast_len(n), n)
Exemple #25
0
def spec_pgram(x,
               xfreq=1,
               spans=None,
               kernel=None,
               taper=0.1,
               pad=0,
               fast=True,
               demean=False,
               detrend=True,
               minimal=True,
               option_summary=False,
               **kwargs):
    """Computes the spectral density estimate using a periodogram.

    Args:
        x (numpy array): Univariate or multivariate time series.
        xfreq (optional): Number of samples per unit time. Defaults to 1.
        spans (optional): Sequence of spans for convoluted Daniell smoothers. Defaults to None.
        kernel (optional): Defines Kernel for smoothing. Defaults to None.
        taper (optional): Defines proportion for tapering start and end of series to avoud end-of-signal effects. Defaults to 0.1.
        pad (optional): Pads the provided series before computation, adding pad*(length of series) zeros at the end. Defaults to 0.
        fast (optional): [description]. Defaults to True.
        demean (optional): Demeans series. Defaults to False.
        detrend (optional): Detrends series. Defaults to True.
        minimal (optional): Returns only frequency and spectrum. Overrides option_summary. Defaults to True.
        option_summary (optional): Returns specified options alongside results. Defaults to False.

    Adapted from R's stats::spec.pgram.

    Based on versions at https://github.com/SurajGupta/r-source/blob/master/src/library/stats/R/spectrum.R and
    https://github.com/telmo-correa/time-series-analysis/blob/master/Python/spectrum.py
    """
    def spec_taper(x, p=0.1):
        """
        Apply a cosine-bell taper to a time series.
        
        Computes a tapered version of x, with tapering proportion p at each end of x.
    
        Adapted from R's stats::spec.taper.
        """
        p = np.r_[p]
        assert np.all((p >= 0) & (p < 0.5)), "'p' must be between 0 and 0.5"

        x = np.r_[x].astype("float64")
        original_shape = x.shape

        assert len(original_shape) <= 2, "'x' must have at most 2 dimensions"
        while len(x.shape) < 2:
            x = np.expand_dims(x, axis=1)

        nrow, ncol = x.shape
        if len(p) == 1:
            p = p * np.ones(ncol)
        else:
            assert (
                len(p) == nc
            ), "length of 'p' must be 1 or equal the number of columns of 'x'"

        for i in range(ncol):
            m = int(np.floor(nrow * p[i]))
            if m == 0:
                continue
            w = 0.5 * (1 - np.cos(np.pi * np.arange(1, 2 * m, step=2) /
                                  (2 * m)))
            x[:, i] = np.r_[w, np.ones(nrow - 2 * m), w[::-1]] * x[:, i]

        x = np.reshape(x, original_shape)
        return x

    def daniell_window_modified(m):
        """ Single-pass modified Daniell kernel window.
        
        Weight is normalized to add up to 1, and all values are the same, other than the first and the
        last, which are divided by 2.
        """
        def w(k):
            return np.where(
                np.abs(k) < m, 1 / (2 * m),
                np.where(np.abs(k) == m, 1 / (4 * m), 0))

        return w(np.arange(-m, m + 1))

    def daniell_window_convolve(v):
        """ Convolved version of multiple modified Daniell kernel windows.
        
        Parameter v should be an iterable of m values.
        """

        if len(v) == 0:
            return np.r_[1]

        if len(v) == 1:
            return daniell_window_modified(v[0])

        return signal.convolve(daniell_window_modified(v[0]),
                               daniell_window_convolve(v[1:]))

    # Ensure we can store non-integers in x, and that it is a numpy object
    x = np.r_[x].astype("float64")
    original_shape = x.shape

    # Ensure correct dimensions
    assert len(original_shape) <= 2, "'x' must have at most 2 dimensions"
    while len(x.shape) < 2:
        x = np.expand_dims(x, axis=1)

    # N/N0 = number of rows
    N, nser = x.shape
    N0 = N

    # Ensure only one of spans, kernel is provided, and build the kernel window if needed
    assert (spans is None) or (
        kernel is None), "must specify only one of 'spans' or 'kernel'"
    if spans is not None:
        kernel = daniell_window_convolve(np.floor_divide(np.r_[spans], 2))

    # Detrend and/or demean the series
    if detrend:
        t = np.arange(N) - (N - 1) / 2
        sumt2 = N * (N**2 - 1) / 12
        x -= (np.repeat(np.expand_dims(np.mean(x, axis=0), 0), N, axis=0) +
              np.outer(np.sum(x.T * t, axis=1), t / sumt2).T)
    elif demean:
        x -= np.mean(x, axis=0)

    # Compute taper and taper adjustment variables
    x = spec_taper(x, taper)
    u2 = 1 - (5 / 8) * taper * 2
    u4 = 1 - (93 / 128) * taper * 2

    # Pad the series with copies of the same shape, but filled with zeroes
    if pad > 0:
        x = np.r_[x, np.zeros((np.int(pad * x.shape[0]), x.shape[1]))]
        N = x.shape[0]

    # Further pad the series to accelerate FFT computation
    if fast:
        newN = fft.next_fast_len(N, True)
        x = np.r_[x, np.zeros((newN - N, x.shape[1]))]
        N = newN

    # Compute the Fourier frequencies (R's spec.pgram convention style)
    Nspec = int(np.floor(N / 2))
    freq = (np.arange(Nspec) + 1) * xfreq / N

    # Translations to keep same row / column convention as stats::mvfft
    xfft = fft.fft(x.T).T

    # Compute the periodogram for each i, j
    pgram = np.empty((N, nser, nser), dtype="complex")
    for i in range(nser):
        for j in range(nser):
            pgram[:, i, j] = xfft[:, i] * np.conj(xfft[:, j]) / (N0 * xfreq)
            pgram[0, i, j] = 0.5 * (pgram[1, i, j] + pgram[-1, i, j])

    if kernel is None:
        # Values pre-adjustment
        df = 2
        bandwidth = np.sqrt(1 / 12)
    else:

        def conv_circular(signal, kernel):
            """
            Performs 1D circular convolution, in the same style as R::kernapply,
            assuming the kernel window is centered at 0.
            """
            pad = len(signal) - len(kernel)
            half_window = int((len(kernel) + 1) / 2)
            indexes = range(-half_window, len(signal) - half_window)
            orig_conv = np.real(
                fft.ifft(
                    fft.fft(signal) * fft.fft(np.r_[np.zeros(pad), kernel])))
            return orig_conv.take(indexes, mode="wrap")

        # Convolve pgram with kernel with circular conv
        for i in range(nser):
            for j in range(nser):
                pgram[:, i, j] = conv_circular(pgram[:, i, j], kernel)

        df = 2 / np.sum(kernel**2)
        m = (len(kernel) - 1) / 2
        k = np.arange(-m, m + 1)
        bandwidth = np.sqrt(np.sum((1 / 12 + k**2) * kernel))

    df = df / (u4 / u2**2) * (N0 / N)
    bandwidth = bandwidth * xfreq / N

    # Remove padded results
    pgram = pgram[1:(Nspec + 1), :, :]

    spec = np.empty((Nspec, nser))
    for i in range(nser):
        spec[:, i] = np.real(pgram[:, i, i])

    if minimal == True:
        spec = spec / u2
        spec = spec.squeeze()

        results = {
            "freq": freq,
            "spec": spec,
        }

    else:
        if nser == 1:
            coh = None
            phase = None
        else:
            coh = np.empty((Nspec, int(nser * (nser - 1) / 2)))
            phase = np.empty((Nspec, int(nser * (nser - 1) / 2)))
            for i in range(nser):
                for j in range(i + 1, nser):
                    index = int(i + j * (j - 1) / 2)
                    coh[:, index] = np.abs(
                        pgram[:, i, j])**2 / (spec[:, i] * spec[:, j])
                    phase[:, index] = np.angle(pgram[:, i, j])

        spec = spec / u2
        spec = spec.squeeze()

        if option_summary == True:
            results = {
                "freq":
                freq,
                "spec":
                spec,
                "coherency":
                coh,
                "phase":
                phase,
                "kernel":
                kernel,
                "degrees of freedom":
                df,
                "bandwidth":
                bandwidth,
                "n.used":
                N,
                "orig.n":
                N0,
                "taper":
                taper,
                "pad":
                pad,
                "detrend":
                detrend,
                "demean":
                demean,
                "method":
                "Raw Periodogram"
                if kernel is None else "Smoothed Periodogram",
            }
        else:
            results = {
                "freq": freq,
                "spec": spec,
                "coherency": coh,
                "phase": phase,
                "kernel": kernel,
                "degrees of freedom": df,
                "bandwidth": bandwidth,
                "n.used": N,
                "orig.n": N0,
            }

    return results
Exemple #26
0
def plot_filter(b, a=1, worN=512, whole=False, plot=None, fs=2 * pi):
    """
    Compute the frequency response of a digital filter.
    Given the M-order numerator `b` and N-order denominator `a` of a digital
    filter, compute its frequency response::
                 jw                 -jw              -jwM
        jw    B(e  )    b[0] + b[1]e    + ... + b[M]e
     H(e  ) = ------ = -----------------------------------
                 jw                 -jw              -jwN
              A(e  )    a[0] + a[1]e    + ... + a[N]e
    Parameters
    ----------
    b : array_like
        Numerator of a linear filter. If `b` has dimension greater than 1,
        it is assumed that the coefficients are stored in the first dimension,
        and ``b.shape[1:]``, ``a.shape[1:]``, and the shape of the frequencies
        array must be compatible for broadcasting.
    a : array_like
        Denominator of a linear filter. If `b` has dimension greater than 1,
        it is assumed that the coefficients are stored in the first dimension,
        and ``b.shape[1:]``, ``a.shape[1:]``, and the shape of the frequencies
        array must be compatible for broadcasting.
    worN : {None, int, array_like}, optional
        If a single integer, then compute at that many frequencies (default is
        N=512). This is a convenient alternative to::
            np.linspace(0, fs if whole else fs/2, N, endpoint=False)
        Using a number that is fast for FFT computations can result in
        faster computations (see Notes).
        If an array_like, compute the response at the frequencies given.
    whole : bool, optional
        Normally, frequencies are computed from 0 to the Nyquist frequency,
        fs/2 (upper-half of unit-circle). If `whole` is True, compute
        frequencies from 0 to fs. Ignored if w is array_like.
    plot : callable
        A callable that takes two arguments. If given, the return parameters
        `w` and `h` are passed to plot.
    fs : float, optional
        The sampling frequency of the digital system. Defaults to 2*pi
        radians/sample (so w is from 0 to pi).
    Returns
    -------
    w : ndarray
        The frequencies at which `h` was computed, in the same units as `fs`.
        By default, `w` is normalized to the range [0, pi) (radians/sample).
    h : ndarray
        The frequency response, as complex numbers.
    """
    def _is_int_type(x):
        """
        Check if input is of a scalar integer type (so ``5`` and ``array(5)`` will
        pass, while ``5.0`` and ``array([5])`` will fail.
        """
        if np.ndim(x) != 0:
            # Older versions of NumPy did not raise for np.array([1]).__index__()
            # This is safe to remove when support for those versions is dropped
            return False
        try:
            operator.index(x)
        except TypeError:
            return False
        else:
            return True

    b = atleast_1d(b)
    a = atleast_1d(a)

    if worN is None:
        # For backwards compatibility
        worN = 512

    h = None

    if _is_int_type(worN):
        N = operator.index(worN)
        del worN
        if N < 0:
            raise ValueError('worN must be nonnegative, got %s' % (N, ))
        lastpoint = 2 * pi if whole else pi
        w = np.linspace(0, lastpoint, N, endpoint=False)
        if (a.size == 1 and N >= b.shape[0] and sp_fft.next_fast_len(N) == N
                and (b.ndim == 1 or (b.shape[-1] == 1))):
            # if N is fast, 2 * N will be fast, too, so no need to check
            n_fft = N if whole else N * 2
            if np.isrealobj(b) and np.isrealobj(a):
                fft_func = sp_fft.rfft
            else:
                fft_func = sp_fft.fft
            h = fft_func(b, n=n_fft, axis=0)[:N]
            h /= a
            if fft_func is sp_fft.rfft and whole:
                # exclude DC and maybe Nyquist (no need to use axis_reverse
                # here because we can build reversal with the truncation)
                stop = -1 if n_fft % 2 == 1 else -2
                h_flip = slice(stop, 0, -1)
                h = np.concatenate((h, h[h_flip].conj()))
            if b.ndim > 1:
                # Last axis of h has length 1, so drop it.
                h = h[..., 0]
                # Rotate the first axis of h to the end.
                h = np.rollaxis(h, 0, h.ndim)
    else:
        w = atleast_1d(worN)
        del worN
        w = 2 * pi * w / fs

    if h is None:  # still need to compute using freqs w
        zm1 = exp(-1j * w)
        h = (npp_polyval(zm1, b, tensor=False) /
             npp_polyval(zm1, a, tensor=False))

    w = w * fs / (2 * pi)

    if plot is not None:
        plot(w, h)
        return None

    return w, h
def cross_correlate_masked(arr1, arr2, m1, m2, mode='full', axes=(-2, -1),
                           overlap_ratio=0.3):
    """
    Masked normalized cross-correlation between arrays.

    Parameters
    ----------
    arr1 : ndarray
        First array.
    arr2 : ndarray
        Seconds array. The dimensions of `arr2` along axes that are not
        transformed should be equal to that of `arr1`.
    m1 : ndarray
        Mask of `arr1`. The mask should evaluate to `True`
        (or 1) on valid pixels. `m1` should have the same shape as `arr1`.
    m2 : ndarray
        Mask of `arr2`. The mask should evaluate to `True`
        (or 1) on valid pixels. `m2` should have the same shape as `arr2`.
    mode : {'full', 'same'}, optional
        'full':
            This returns the convolution at each point of overlap. At
            the end-points of the convolution, the signals do not overlap
            completely, and boundary effects may be seen.
        'same':
            The output is the same size as `arr1`, centered with respect
            to the `‘full’` output. Boundary effects are less prominent.
    axes : tuple of ints, optional
        Axes along which to compute the cross-correlation.
    overlap_ratio : float, optional
        Minimum allowed overlap ratio between images. The correlation for
        translations corresponding with an overlap ratio lower than this
        threshold will be ignored. A lower `overlap_ratio` leads to smaller
        maximum translation, while a higher `overlap_ratio` leads to greater
        robustness against spurious matches due to small overlap between
        masked images.

    Returns
    -------
    out : ndarray
        Masked normalized cross-correlation.

    Raises
    ------
    ValueError : if correlation `mode` is not valid, or array dimensions along
        non-transformation axes are not equal.

    References
    ----------
    .. [1] Dirk Padfield. Masked Object Registration in the Fourier Domain.
           IEEE Transactions on Image Processing, vol. 21(5),
           pp. 2706-2718 (2012). :DOI:`10.1109/TIP.2011.2181402`
    .. [2] D. Padfield. "Masked FFT registration". In Proc. Computer Vision and
           Pattern Recognition, pp. 2918-2925 (2010).
           :DOI:`10.1109/CVPR.2010.5540032`
    """
    if mode not in {'full', 'same'}:
        raise ValueError(f"Correlation mode '{mode}' is not valid.")

    fixed_image = np.asarray(arr1)
    moving_image = np.asarray(arr2)
    float_dtype = _supported_float_type(
        [fixed_image.dtype, moving_image.dtype]
    )
    if float_dtype.kind == 'c':
        raise ValueError("complex-valued arr1, arr2 are not supported")

    fixed_image = fixed_image.astype(float_dtype)
    fixed_mask = np.array(m1, dtype=bool)
    moving_image = moving_image.astype(float_dtype)
    moving_mask = np.array(m2, dtype=bool)
    eps = np.finfo(float_dtype).eps

    # Array dimensions along non-transformation axes should be equal.
    all_axes = set(range(fixed_image.ndim))
    for axis in (all_axes - set(axes)):
        if fixed_image.shape[axis] != moving_image.shape[axis]:
            raise ValueError(
                f'Array shapes along non-transformation axes should be '
                f'equal, but dimensions along axis {axis} are not.')

    # Determine final size along transformation axes
    # Note that it might be faster to compute Fourier transform in a slightly
    # larger shape (`fast_shape`). Then, after all fourier transforms are done,
    # we slice back to`final_shape` using `final_slice`.
    final_shape = list(arr1.shape)
    for axis in axes:
        final_shape[axis] = fixed_image.shape[axis] + \
            moving_image.shape[axis] - 1
    final_shape = tuple(final_shape)
    final_slice = tuple([slice(0, int(sz)) for sz in final_shape])

    # Extent transform axes to the next fast length (i.e. multiple of 3, 5, or
    # 7)
    fast_shape = tuple([next_fast_len(final_shape[ax]) for ax in axes])

    # We use the new scipy.fft because they allow leaving the transform axes
    # unchanged which was not possible with scipy.fftpack's
    # fftn/ifftn in older versions of SciPy.
    # E.g. arr shape (2, 3, 7), transform along axes (0, 1) with shape (4, 4)
    # results in arr_fft shape (4, 4, 7)
    fft = partial(fftmodule.fftn, s=fast_shape, axes=axes)
    _ifft = partial(fftmodule.ifftn, s=fast_shape, axes=axes)

    def ifft(x):
        return _ifft(x).real

    fixed_image[np.logical_not(fixed_mask)] = 0.0
    moving_image[np.logical_not(moving_mask)] = 0.0

    # N-dimensional analog to rotation by 180deg is flip over all relevant axes.
    # See [1] for discussion.
    rotated_moving_image = _flip(moving_image, axes=axes)
    rotated_moving_mask = _flip(moving_mask, axes=axes)

    fixed_fft = fft(fixed_image)
    rotated_moving_fft = fft(rotated_moving_image)
    fixed_mask_fft = fft(fixed_mask.astype(float_dtype))
    rotated_moving_mask_fft = fft(rotated_moving_mask.astype(float_dtype))

    # Calculate overlap of masks at every point in the convolution.
    # Locations with high overlap should not be taken into account.
    number_overlap_masked_px = ifft(rotated_moving_mask_fft * fixed_mask_fft)
    number_overlap_masked_px[:] = np.round(number_overlap_masked_px)
    number_overlap_masked_px[:] = np.fmax(number_overlap_masked_px, eps)
    masked_correlated_fixed_fft = ifft(rotated_moving_mask_fft * fixed_fft)
    masked_correlated_rotated_moving_fft = ifft(
        fixed_mask_fft * rotated_moving_fft)

    numerator = ifft(rotated_moving_fft * fixed_fft)
    numerator -= masked_correlated_fixed_fft * \
        masked_correlated_rotated_moving_fft / number_overlap_masked_px

    fixed_squared_fft = fft(np.square(fixed_image))
    fixed_denom = ifft(rotated_moving_mask_fft * fixed_squared_fft)
    fixed_denom -= np.square(masked_correlated_fixed_fft) / \
        number_overlap_masked_px
    fixed_denom[:] = np.fmax(fixed_denom, 0.0)

    rotated_moving_squared_fft = fft(np.square(rotated_moving_image))
    moving_denom = ifft(fixed_mask_fft * rotated_moving_squared_fft)
    moving_denom -= np.square(masked_correlated_rotated_moving_fft) / \
        number_overlap_masked_px
    moving_denom[:] = np.fmax(moving_denom, 0.0)

    denom = np.sqrt(fixed_denom * moving_denom)

    # Slice back to expected convolution shape.
    numerator = numerator[final_slice]
    denom = denom[final_slice]
    number_overlap_masked_px = number_overlap_masked_px[final_slice]

    if mode == 'same':
        _centering = partial(_centered,
                             newshape=fixed_image.shape, axes=axes)
        denom = _centering(denom)
        numerator = _centering(numerator)
        number_overlap_masked_px = _centering(number_overlap_masked_px)

    # Pixels where `denom` is very small will introduce large
    # numbers after division. To get around this problem,
    # we zero-out problematic pixels.
    tol = 1e3 * eps * np.max(np.abs(denom), axis=axes, keepdims=True)
    nonzero_indices = denom > tol

    # explicitly set out dtype for compatibility with SciPy < 1.4, where
    # fftmodule will be numpy.fft which always uses float64 dtype.
    out = np.zeros_like(denom, dtype=float_dtype)
    out[nonzero_indices] = numerator[nonzero_indices] / denom[nonzero_indices]
    np.clip(out, a_min=-1, a_max=1, out=out)

    # Apply overlap ratio threshold
    number_px_threshold = overlap_ratio * np.max(number_overlap_masked_px,
                                                 axis=axes, keepdims=True)
    out[number_overlap_masked_px < number_px_threshold] = 0.0

    return out
Exemple #28
0
def whiten(tr,
           Tmin=1,
           Tmax=150,
           freq_width=.0004,
           brute=True,
           frac=.2,
           epsilon=1e-8,
           **kwargs):
    """
    Spectral whitening using running absolute mean in frequency domain.

    https://github.com/NoiseCIEI/Seed2Cor/blob/master/src/Whiten.c
    https://github.com/bgoutorbe/seismic-noise-tomography/blob/8f26ff827bee8a411038e33d93b59bffbc88c0a7/pysismo/pscrosscorr.py#L306
    https://www.mathworks.com/matlabcentral/fileexchange/65345-spectral-whitening
    https://github.com/ROBelgium/MSNoise/blob/27749e2914b30b1ab8278054645677444f10e309/msnoise/move2obspy.py#L135

    :param freq_width: length of averaging window in frequency domain
    :param epsilon: minimum value to avoid zero division
    :param return_spc: if return spectra for plot
    """
    npts = tr.stats.npts
    sr = tr.stats.sampling_rate
    dom = sr / npts
    winlen = int(round(freq_width / dom))

    nfreq = next_fast_len(npts)
    spc = fft(tr.data, nfreq)
    spc_am = np.abs(spc)
    spc_ph = np.unwrap(np.angle(spc))

    if brute:
        weight = spc_am
        spc_w = np.exp(1j * spc_ph)
    elif winlen < 2:
        weight = spc_am
        spc_w = np.exp(1j * spc_ph)
        logger.debug('Window too short')
    else:
        weight = bn.move_mean(spc_am, winlen, 1)
        ind = weight < epsilon
        weight[ind] = epsilon
        spc_w = spc / weight
        spc_w[ind] = 0

    f2 = 1 / Tmax
    f3 = 1 / Tmin
    f1 = f2 * (1 - frac)
    f4 = f3 * (1 + frac)
    if f4 > sr / 2:
        logger.warning(
            'Whiten band upper end out of range! Corrected to Nyquist.')
        f4 = sr / 2
        if f3 >= f4:
            f3 = f4 * (1 - frac)
            if f3 < f2:
                f3 = f2
    freqs = fftfreq(nfreq, d=tr.stats.delta)
    spc_w *= obspy.signal.invsim.cosine_taper(
        npts=nfreq,
        freqs=freqs,
        flimit=[f1, f2, f3, f4],
    )

    # Hermitian symmetry (because the input is real)
    spc_w[-(nfreq // 2) + 1:] = spc_w[1:(nfreq // 2)].conjugate()[::-1]

    whitened = ifft(spc_w, nfreq)[:npts]

    if kwargs.get('plot', False):
        xlim = [0, .2]
        npts = tr.stats.npts
        delta = tr.stats.delta
        t = np.arange(0, npts * delta, delta)

        fig = plt.figure(figsize=(12, 8))
        gs = mpl.gridspec.GridSpec(3, 1)
        ax1 = plt.subplot(gs[0, 0])
        ax2 = plt.subplot(gs[1, 0])
        ax3 = plt.subplot(gs[2, 0], sharex=ax2)

        tr.filter('bandpass', freqmin=f2, freqmax=f3, zerophase=True)
        ax1.plot(t, tr.normalize().data, c='k', label="Raw", ls='--', lw=1)
        ax1.plot(t,
                 whitened / np.abs(whitened).max(),
                 c='r',
                 label="Whitened",
                 lw=1)
        ax1.set_xlabel("Time (s)")
        ax1.set_xlim(0, tr.stats.sac.dist)
        ax1.set_ylim(-1, 1)

        ifreq = np.argsort(freqs)
        ax2.plot(freqs[ifreq],
                 spc_am[ifreq],
                 alpha=.5,
                 lw=1,
                 c='gray',
                 ls='--',
                 label="Raw")
        ax2.plot(freqs[ifreq],
                 weight[ifreq],
                 alpha=.5,
                 lw=2,
                 c='g',
                 label="Weight")
        ax2.set_xlim(xlim[0], xlim[1])

        ax3.plot(freqs[ifreq],
                 np.abs(spc_w)[ifreq],
                 lw=1,
                 c='b',
                 label="Whitened")
        for ax in [ax2, ax3]:
            ax.set_ylim(0)
            ax.set_xlabel("Frequency (Hz)")
            for per in [8, 16, 26]:
                ax.axvline(1 / per, ls='--', alpha=.5, c='k')
        for ax in [ax1, ax2, ax3]:
            ax.legend(loc='upper right')

        pair = f'{tr.stats.sac.kevnm.strip()}_{tr.stats.sac.kstnm.strip()}'
        fig.suptitle(pair, y=1.02)
        # fig.savefig(f'/work2/szhang/US/Plot/fig4exp/US/sw_{pair}.pdf')
        plt.show()

    tr.data = whitened
    tr.taper(max_percentage=0.05, type='hann', side='both')

    return tr
Exemple #29
0
 def time_next_fast_len_cached(self, size):
     scipy_fft.next_fast_len(size)