Esempio n. 1
0
def butter(signal,
           highpass_freq=None,
           lowpass_freq=None,
           order=4,
           filter_function='filtfilt',
           fs=1.0,
           axis=-1):
    """
    Butterworth filtering function for neo.AnalogSignal. Filter type is
    determined according to how values of `highpass_freq` and `lowpass_freq`
    are given (see Parameters section for details).

    Parameters
    ----------
    signal : AnalogSignal or Quantity array or NumPy ndarray
        Time series data to be filtered. When given as Quantity array or NumPy
        ndarray, the sampling frequency should be given through the keyword
        argument `fs`.
    highpass_freq, lowpass_freq : Quantity or float
        High-pass and low-pass cut-off frequencies, respectively. When given as
        float, the given value is taken as frequency in Hz.
        Filter type is determined depending on values of these arguments:
            * highpass_freq only (lowpass_freq = None):    highpass filter
            * lowpass_freq only (highpass_freq = None):    lowpass filter
            * highpass_freq < lowpass_freq:    bandpass filter
            * highpass_freq > lowpass_freq:    bandstop filter
    order : int
        Order of Butterworth filter. Default is 4.
    filter_function : string
        Filtering function to be used. Available filters:
            * 'filtfilt': `scipy.signal.filtfilt()`;
            * 'lfilter': `scipy.signal.lfilter()`;
            * 'sosfiltfilt': `scipy.signal.sosfiltfilt()`.
        In most applications 'filtfilt' should be used, because it doesn't
        bring about phase shift due to filtering. For numerically stable
        filtering, in particular higher order filters, use 'sosfiltfilt'
        (see issue
        https://github.com/NeuralEnsemble/elephant/issues/220).
        Default is 'filtfilt'.
    fs : Quantity or float
        The sampling frequency of the input time series. When given as float,
        its value is taken as frequency in Hz. When the input is given as neo
        AnalogSignal, its attribute is used to specify the sampling
        frequency and this parameter is ignored. Default is 1.0.
    axis : int
        Axis along which filter is applied. Default is -1.

    Returns
    -------
    filtered_signal : AnalogSignal or Quantity array or NumPy ndarray
        Filtered input data. The shape and type is identical to those of the
        input.

    Raises
    ------
    ValueError
        If `filter_function` is not one of 'lfilter', 'filtfilt',
        or 'sosfiltfilt'.
        When both `highpass_freq` and `lowpass_freq` are None.

    """
    available_filters = 'lfilter', 'filtfilt', 'sosfiltfilt'
    if filter_function not in available_filters:
        raise ValueError("Invalid `filter_function`: {filter_function}. "
                         "Available filters: {available_filters}".format(
                             filter_function=filter_function,
                             available_filters=available_filters))
    # design filter
    if hasattr(signal, 'sampling_rate'):
        fs = signal.sampling_rate.rescale(pq.Hz).magnitude
    if isinstance(highpass_freq, pq.quantity.Quantity):
        highpass_freq = highpass_freq.rescale(pq.Hz).magnitude
    if isinstance(lowpass_freq, pq.quantity.Quantity):
        lowpass_freq = lowpass_freq.rescale(pq.Hz).magnitude
    Fn = fs / 2.
    # filter type is determined according to the values of cut-off
    # frequencies
    if lowpass_freq and highpass_freq:
        if highpass_freq < lowpass_freq:
            Wn = (highpass_freq / Fn, lowpass_freq / Fn)
            btype = 'bandpass'
        else:
            Wn = (lowpass_freq / Fn, highpass_freq / Fn)
            btype = 'bandstop'
    elif lowpass_freq:
        Wn = lowpass_freq / Fn
        btype = 'lowpass'
    elif highpass_freq:
        Wn = highpass_freq / Fn
        btype = 'highpass'
    else:
        raise ValueError("Either highpass_freq or lowpass_freq must be given")
    if filter_function == 'sosfiltfilt':
        output = 'sos'
    else:
        output = 'ba'
    designed_filter = scipy.signal.butter(order,
                                          Wn,
                                          btype=btype,
                                          output=output)

    # When the input is AnalogSignal, the axis for time index (i.e. the
    # first axis) needs to be rolled to the last
    data = np.asarray(signal)
    if isinstance(signal, neo.AnalogSignal):
        data = np.rollaxis(data, 0, len(data.shape))

    # apply filter
    if filter_function == 'lfilter':
        b, a = designed_filter
        filtered_data = scipy.signal.lfilter(b=b, a=a, x=data, axis=axis)
    elif filter_function == 'filtfilt':
        b, a = designed_filter
        filtered_data = scipy.signal.filtfilt(b=b, a=a, x=data, axis=axis)
    else:
        filtered_data = scipy.signal.sosfiltfilt(sos=designed_filter,
                                                 x=data,
                                                 axis=axis)

    if isinstance(signal, neo.AnalogSignal):
        filtered_data = np.rollaxis(filtered_data, -1, 0)
        return signal.duplicate_with_new_data(filtered_data)
    elif isinstance(signal, pq.quantity.Quantity):
        return filtered_data * signal.units
    else:
        return filtered_data
Esempio n. 2
0
def hilbert(signal, N='nextpow'):
    '''
    Apply a Hilbert transform to an AnalogSignal object in order to obtain its
    (complex) analytic signal.

    The time series of the instantaneous angle and amplitude can be obtained as
    the angle (np.angle) and absolute value (np.abs) of the complex analytic
    signal, respectively.

    By default, the function will zero-pad the signal to a length corresponding
    to the next higher power of 2. This will provide higher computational
    efficiency at the expense of memory. In addition, this circumvents a
    situation where for some specific choices of the length of the input,
    scipy.signal.hilbert() will not terminate.

    Parameters
    -----------
    signal : neo.AnalogSignal
        Signal(s) to transform
    N : string or int
        Defines whether the signal is zero-padded.
            'none': no padding
            'nextpow':  zero-pad to the next length that is a power of 2
            int: directly specify the length to zero-pad to (indicates the
                number of Fourier components, see parameter N of
                scipy.signal.hilbert()).
        Default: 'nextpow'.

    Returns
    -------
    neo.AnalogSignal
        Contains the complex analytic signal(s) corresponding to the input
        signals. The unit of the analytic signal is dimensionless.

    Example
    -------
    Create a sine signal at 5 Hz with increasing amplitude and calculate the
    instantaneous phases

    >>> t = np.arange(0, 5000) * ms
    >>> f = 5. * Hz
    >>> a = neo.AnalogSignal(
    ...       np.array(
    ...           (1 + t.magnitude / t[-1].magnitude) * np.sin(
    ...               2. * np.pi * f * t.rescale(s))).reshape((-1,1))*mV,
    ...       t_start=0*s, sampling_rate=1000*Hz)

    >>> analytic_signal = hilbert(a, N='nextpow')
    >>> angles = np.angle(analytic_signal)
    >>> amplitudes = np.abs(analytic_signal)
    >>> print angles
            [[-1.57079633]
             [-1.51334228]
             [-1.46047675]
             ...,
             [-1.73112977]
             [-1.68211683]
             [-1.62879501]]
    >>> plt.plot(t,angles)
    '''
    # Length of input signals
    n_org = signal.shape[0]

    # Right-pad signal to desired length using the signal itself
    if type(N) == int:
        # User defined padding
        n = N
    elif N == 'nextpow':
        # To speed up calculation of the Hilbert transform, make sure we change
        # the signal to be of a length that is a power of two. Failure to do so
        # results in computations of certain signal lengths to not finish (or
        # finish in absurd time). This might be a bug in scipy (0.16), e.g.,
        # the following code will not terminate for this value of k:
        #
        # import numpy
        # import scipy.signal
        # k=679346
        # t = np.arange(0, k) / 1000.
        # a = (1 + t / t[-1]) * np.sin(2 * np.pi * 5 * t)
        # analytic_signal = scipy.signal.hilbert(a)
        #
        # For this reason, nextpow is the default setting for now.

        n = 2**(int(np.log2(n_org - 1)) + 1)
    elif N == 'none':
        # No padding
        n = n_org
    else:
        raise ValueError("'{}' is an unknown N.".format(N))

    output = signal.duplicate_with_new_data(
        scipy.signal.hilbert(signal.magnitude, N=n, axis=0)[:n_org])
    return output / output.units
def hilbert(signal, padding='nextpow'):
    """
    Apply a Hilbert transform to a `neo.AnalogSignal` object in order to
    obtain its (complex) analytic signal.

    The time series of the instantaneous angle and amplitude can be obtained
    as the angle (`np.angle` function) and absolute value (`np.abs` function)
    of the complex analytic signal, respectively.

    By default, the function will zero-pad the signal to a length
    corresponding to the next higher power of 2. This will provide higher
    computational efficiency at the expense of memory. In addition, this
    circumvents a situation where, for some specific choices of the length of
    the input, `scipy.signal.hilbert` function will not terminate.

    Parameters
    ----------
    signal : neo.AnalogSignal
        Signal(s) to transform.
    padding : int, {'none', 'nextpow'}, or None, optional
        Defines whether the signal is zero-padded.
        The `padding` argument corresponds to `N` in
        `scipy.signal.hilbert(signal, N=padding)` function.
        If 'none' or None, no padding.
        If 'nextpow', zero-pad to the next length that is a power of 2.
        If it is an `int`, directly specify the length to zero-pad to
        (indicates the number of Fourier components).
        Default: 'nextpow'

    Returns
    -------
    neo.AnalogSignal
        Contains the complex analytic signal(s) corresponding to the input
        `signal`. The unit of the returned `neo.AnalogSignal` is
        dimensionless.

    Raises
    ------
    ValueError:
        If `padding` is not an integer or neither 'nextpow' nor 'none' (None).

    Examples
    --------
    Create a sine signal at 5 Hz with increasing amplitude and calculate the
    instantaneous phases:

    >>> import neo
    >>> import numpy as np
    >>> import quantities as pq
    >>> import matplotlib.pyplot as plt
    >>> from elephant.signal_processing import hilbert
    >>> t = np.arange(0, 5000) * pq.ms
    >>> f = 5. * pq.Hz
    >>> a = neo.AnalogSignal(
    ...       np.array(
    ...           (1 + t.magnitude / t[-1].magnitude) * np.sin(
    ...               2. * np.pi * f * t.rescale(pq.s))).reshape(
    ...                   (-1,1)) * pq.mV,
    ...       t_start=0*pq.s,
    ...       sampling_rate=1000*pq.Hz)
    ...
    >>> analytic_signal = hilbert(a, padding='nextpow')
    >>> angles = np.angle(analytic_signal)
    >>> amplitudes = np.abs(analytic_signal)
    >>> print(angles)
    [[-1.57079633]
     [-1.51334228]
     [-1.46047675]
     ...,
     [-1.73112977]
     [-1.68211683]
     [-1.62879501]]
    >>> plt.plot(t, angles)

    """
    # Length of input signals
    n_org = signal.shape[0]

    # Right-pad signal to desired length using the signal itself
    if isinstance(padding, int):
        # User defined padding
        n = padding
    elif padding == 'nextpow':
        # To speed up calculation of the Hilbert transform, make sure we change
        # the signal to be of a length that is a power of two. Failure to do so
        # results in computations of certain signal lengths to not finish (or
        # finish in absurd time). This might be a bug in scipy (0.16), e.g.,
        # the following code will not terminate for this value of k:
        #
        # import numpy
        # import scipy.signal
        # k=679346
        # t = np.arange(0, k) / 1000.
        # a = (1 + t / t[-1]) * np.sin(2 * np.pi * 5 * t)
        # analytic_signal = scipy.signal.hilbert(a)
        #
        # For this reason, nextpow is the default setting for now.

        n = 2**(int(np.log2(n_org - 1)) + 1)
    elif padding == 'none' or padding is None:
        # No padding
        n = n_org
    else:
        raise ValueError("Invalid padding '{}'.".format(padding))

    output = signal.duplicate_with_new_data(
        scipy.signal.hilbert(signal.magnitude, N=n, axis=0)[:n_org])
    # todo use flag once is fixed
    #      https://github.com/NeuralEnsemble/python-neo/issues/752
    output.array_annotate(**signal.array_annotations)
    return output / output.units
def butter(signal,
           highpass_frequency=None,
           lowpass_frequency=None,
           order=4,
           filter_function='filtfilt',
           sampling_frequency=1.0,
           axis=-1):
    """
    Butterworth filtering function for `neo.AnalogSignal`.

    Filter type is determined according to how values of `highpass_frequency`
    and `lowpass_frequency` are given (see "Parameters" section for details).

    Parameters
    ----------
    signal : neo.AnalogSignal or pq.Quantity or np.ndarray
        Time series data to be filtered.
        If `pq.Quantity` or `np.ndarray`, the sampling frequency should be
        given through the keyword argument `fs`.
    highpass_frequency : pq.Quantity of float, optional
        High-pass cut-off frequency. If `float`, the given value is taken as
        frequency in Hz.
        Default: None
    lowpass_frequency : pq.Quantity or float, optional
        Low-pass cut-off frequency. If `float`, the given value is taken as
        frequency in Hz.
        Filter type is determined depending on the values of
        `lowpass_frequency` and `highpass_frequency`:

        * `highpass_frequency` only (`lowpass_frequency` is None):
        highpass filter

        * `lowpass_frequency` only (`highpass_frequency` is None):
        lowpass filter

        * `highpass_frequency` < `lowpass_frequency`: bandpass filter

        * `highpass_frequency` > `lowpass_frequency`: bandstop filter

        Default: None
    order : int, optional
        Order of the Butterworth filter.
        Default: 4
    filter_function : {'filtfilt', 'lfilter', 'sosfiltfilt'}, optional
        Filtering function to be used. Available filters:

        * 'filtfilt': `scipy.signal.filtfilt`;

        * 'lfilter': `scipy.signal.lfilter`;

        * 'sosfiltfilt': `scipy.signal.sosfiltfilt`.

        In most applications 'filtfilt' should be used, because it doesn't
        bring about phase shift due to filtering. For numerically stable
        filtering, in particular higher order filters, use 'sosfiltfilt'
        (see https://github.com/NeuralEnsemble/elephant/issues/220).
        Default: 'filtfilt'
    sampling_frequency : pq.Quantity or float, optional
        The sampling frequency of the input time series. When given as
        `float`, its value is taken as frequency in Hz. When `signal` is given
        as `neo.AnalogSignal`, its attribute is used to specify the sampling
        frequency and this parameter is ignored.
        Default: 1.0
    axis : int, optional
        Axis along which filter is applied.
        Default: last axis (-1)

    Returns
    -------
    filtered_signal : neo.AnalogSignal or pq.Quantity or np.ndarray
        Filtered input data. The shape and type is identical to those of the
        input `signal`.

    Raises
    ------
    ValueError
        If `filter_function` is not one of 'lfilter', 'filtfilt',
        or 'sosfiltfilt'.

        If both `highpass_frequency` and `lowpass_frequency` are None.

    Examples
    --------
    >>> import neo
    >>> import numpy as np
    >>> import quantities as pq
    >>> from elephant.signal_processing import butter
    >>> noise = neo.AnalogSignal(np.random.normal(size=5000),
    ...     sampling_rate=1000 * pq.Hz, units='mV')
    >>> filtered_noise = butter(noise, highpass_frequency=250.0 * pq.Hz)
    >>> filtered_noise
    AnalogSignal with 1 channels of length 5000; units mV; datatype float64
    sampling rate: 1000.0 Hz
    time: 0.0 s to 5.0 s

    Let's check that the normal noise power spectrum at zero frequency is close
    to zero.

    >>> from elephant.spectral import welch_psd
    >>> freq, psd = welch_psd(filtered_noise, fs=1000.0)
    >>> psd.shape
    (1, 556)
    >>> freq[0], psd[0, 0]
    (array(0.) * Hz, array(7.21464674e-08) * mV**2/Hz)

    """
    available_filters = 'lfilter', 'filtfilt', 'sosfiltfilt'
    if filter_function not in available_filters:
        raise ValueError("Invalid `filter_function`: {filter_function}. "
                         "Available filters: {available_filters}".format(
                             filter_function=filter_function,
                             available_filters=available_filters))
    # design filter
    if hasattr(signal, 'sampling_rate'):
        sampling_frequency = signal.sampling_rate.rescale(pq.Hz).magnitude
    if isinstance(highpass_frequency, pq.quantity.Quantity):
        highpass_frequency = highpass_frequency.rescale(pq.Hz).magnitude
    if isinstance(lowpass_frequency, pq.quantity.Quantity):
        lowpass_frequency = lowpass_frequency.rescale(pq.Hz).magnitude
    Fn = sampling_frequency / 2.
    # filter type is determined according to the values of cut-off
    # frequencies
    if lowpass_frequency and highpass_frequency:
        if highpass_frequency < lowpass_frequency:
            Wn = (highpass_frequency / Fn, lowpass_frequency / Fn)
            btype = 'bandpass'
        else:
            Wn = (lowpass_frequency / Fn, highpass_frequency / Fn)
            btype = 'bandstop'
    elif lowpass_frequency:
        Wn = lowpass_frequency / Fn
        btype = 'lowpass'
    elif highpass_frequency:
        Wn = highpass_frequency / Fn
        btype = 'highpass'
    else:
        raise ValueError(
            "Either highpass_frequency or lowpass_frequency must be given")
    if filter_function == 'sosfiltfilt':
        output = 'sos'
    else:
        output = 'ba'
    designed_filter = scipy.signal.butter(order,
                                          Wn,
                                          btype=btype,
                                          output=output)

    # When the input is AnalogSignal, the axis for time index (i.e. the
    # first axis) needs to be rolled to the last
    data = np.asarray(signal)
    if isinstance(signal, neo.AnalogSignal):
        data = np.rollaxis(data, 0, len(data.shape))

    # apply filter
    if filter_function == 'lfilter':
        b, a = designed_filter
        filtered_data = scipy.signal.lfilter(b=b, a=a, x=data, axis=axis)
    elif filter_function == 'filtfilt':
        b, a = designed_filter
        filtered_data = scipy.signal.filtfilt(b=b, a=a, x=data, axis=axis)
    else:
        filtered_data = scipy.signal.sosfiltfilt(sos=designed_filter,
                                                 x=data,
                                                 axis=axis)

    if isinstance(signal, neo.AnalogSignal):
        filtered_data = np.rollaxis(filtered_data, -1, 0)
        signal_out = signal.duplicate_with_new_data(filtered_data)
        # todo use flag once is fixed
        #      https://github.com/NeuralEnsemble/python-neo/issues/752
        signal_out.array_annotate(**signal.array_annotations)
        return signal_out
    elif isinstance(signal, pq.quantity.Quantity):
        return filtered_data * signal.units
    else:
        return filtered_data
def butter(signal,
           highpass_frequency=None,
           lowpass_frequency=None,
           order=4,
           filter_function='filtfilt',
           sampling_frequency=1.0,
           axis=-1):
    """
    Butterworth filtering function for `neo.AnalogSignal`.

    Filter type is determined according to how values of `highpass_frequency`
    and `lowpass_frequency` are given (see "Parameters" section for details).

    Parameters
    ----------
    signal : neo.AnalogSignal or pq.Quantity or np.ndarray
        Time series data to be filtered.
        If `pq.Quantity` or `np.ndarray`, the sampling frequency should be
        given through the keyword argument `fs`.
    highpass_frequency : pq.Quantity of float, optional
        High-pass cut-off frequency. If `float`, the given value is taken as
        frequency in Hz.
        Default: None.
    lowpass_frequency : pq.Quantity or float, optional
        Low-pass cut-off frequency. If `float`, the given value is taken as
        frequency in Hz.
        Filter type is determined depending on the values of
        `lowpass_frequency` and `highpass_frequency`:

        * `highpass_frequency` only (`lowpass_frequency` is None):
        highpass filter

        * `lowpass_frequency` only (`highpass_frequency` is None):
        lowpass filter

        * `highpass_frequency` < `lowpass_frequency`: bandpass filter

        * `highpass_frequency` > `lowpass_frequency`: bandstop filter

        Default: None.
    order : int, optional
        Order of the Butterworth filter.
        Default: 4.
    filter_function : {'filtfilt', 'lfilter', 'sosfiltfilt'}, optional
        Filtering function to be used. Available filters:

        * 'filtfilt': `scipy.signal.filtfilt`;

        * 'lfilter': `scipy.signal.lfilter`;

        * 'sosfiltfilt': `scipy.signal.sosfiltfilt`.

        In most applications 'filtfilt' should be used, because it doesn't
        bring about phase shift due to filtering. For numerically stable
        filtering, in particular higher order filters, use 'sosfiltfilt'
        (see [1]_).
        Default: 'filtfilt'.
    sampling_frequency : pq.Quantity or float, optional
        The sampling frequency of the input time series. When given as
        `float`, its value is taken as frequency in Hz. When `signal` is given
        as `neo.AnalogSignal`, its attribute is used to specify the sampling
        frequency and this parameter is ignored.
        Default: 1.0.
    axis : int, optional
        Axis along which filter is applied.
        Default: last axis (-1).

    Returns
    -------
    filtered_signal : neo.AnalogSignal or pq.Quantity or np.ndarray
        Filtered input data. The shape and type is identical to those of the
        input `signal`.

    Raises
    ------
    ValueError
        If `filter_function` is not one of 'lfilter', 'filtfilt',
        or 'sosfiltfilt'.

        If both `highpass_frequency` and `lowpass_frequency` are None.

    References
    ----------
    .. [1] https://github.com/NeuralEnsemble/elephant/issues/220

    """
    available_filters = 'lfilter', 'filtfilt', 'sosfiltfilt'
    if filter_function not in available_filters:
        raise ValueError("Invalid `filter_function`: {filter_function}. "
                         "Available filters: {available_filters}".format(
                             filter_function=filter_function,
                             available_filters=available_filters))
    # design filter
    if hasattr(signal, 'sampling_rate'):
        sampling_frequency = signal.sampling_rate.rescale(pq.Hz).magnitude
    if isinstance(highpass_frequency, pq.quantity.Quantity):
        highpass_frequency = highpass_frequency.rescale(pq.Hz).magnitude
    if isinstance(lowpass_frequency, pq.quantity.Quantity):
        lowpass_frequency = lowpass_frequency.rescale(pq.Hz).magnitude
    Fn = sampling_frequency / 2.
    # filter type is determined according to the values of cut-off
    # frequencies
    if lowpass_frequency and highpass_frequency:
        if highpass_frequency < lowpass_frequency:
            Wn = (highpass_frequency / Fn, lowpass_frequency / Fn)
            btype = 'bandpass'
        else:
            Wn = (lowpass_frequency / Fn, highpass_frequency / Fn)
            btype = 'bandstop'
    elif lowpass_frequency:
        Wn = lowpass_frequency / Fn
        btype = 'lowpass'
    elif highpass_frequency:
        Wn = highpass_frequency / Fn
        btype = 'highpass'
    else:
        raise ValueError(
            "Either highpass_frequency or lowpass_frequency must be given")
    if filter_function == 'sosfiltfilt':
        output = 'sos'
    else:
        output = 'ba'
    designed_filter = scipy.signal.butter(order,
                                          Wn,
                                          btype=btype,
                                          output=output)

    # When the input is AnalogSignal, the axis for time index (i.e. the
    # first axis) needs to be rolled to the last
    data = np.asarray(signal)
    if isinstance(signal, neo.AnalogSignal):
        data = np.rollaxis(data, 0, len(data.shape))

    # apply filter
    if filter_function == 'lfilter':
        b, a = designed_filter
        filtered_data = scipy.signal.lfilter(b=b, a=a, x=data, axis=axis)
    elif filter_function == 'filtfilt':
        b, a = designed_filter
        filtered_data = scipy.signal.filtfilt(b=b, a=a, x=data, axis=axis)
    else:
        filtered_data = scipy.signal.sosfiltfilt(sos=designed_filter,
                                                 x=data,
                                                 axis=axis)

    if isinstance(signal, neo.AnalogSignal):
        filtered_data = np.rollaxis(filtered_data, -1, 0)
        signal_out = signal.duplicate_with_new_data(filtered_data)
        # todo use flag once is fixed
        #      https://github.com/NeuralEnsemble/python-neo/issues/752
        signal_out.array_annotate(**signal.array_annotations)
        return signal_out
    elif isinstance(signal, pq.quantity.Quantity):
        return filtered_data * signal.units
    else:
        return filtered_data
Esempio n. 6
0
def hilbert(signal, N='nextpow'):
    '''
    Apply a Hilbert transform to an AnalogSignal object in order to obtain its
    (complex) analytic signal.

    The time series of the instantaneous angle and amplitude can be obtained as
    the angle (np.angle) and absolute value (np.abs) of the complex analytic
    signal, respectively.

    By default, the function will zero-pad the signal to a length corresponding
    to the next higher power of 2. This will provide higher computational
    efficiency at the expense of memory. In addition, this circumvents a
    situation where for some specific choices of the length of the input,
    scipy.signal.hilbert() will not terminate.

    Parameters
    -----------
    signal : neo.AnalogSignal
        Signal(s) to transform
    N : string or int
        Defines whether the signal is zero-padded.
            'none': no padding
            'nextpow':  zero-pad to the next length that is a power of 2
            int: directly specify the length to zero-pad to (indicates the
                number of Fourier components, see parameter N of
                scipy.signal.hilbert()).
        Default: 'nextpow'.

    Returns
    -------
    neo.AnalogSignal
        Contains the complex analytic signal(s) corresponding to the input
        signals. The unit of the analytic signal is dimensionless.

    Example
    -------
    Create a sine signal at 5 Hz with increasing amplitude and calculate the
    instantaneous phases

    >>> t = np.arange(0, 5000) * ms
    >>> f = 5. * Hz
    >>> a = neo.AnalogSignal(
    ...       np.array(
    ...           (1 + t.magnitude / t[-1].magnitude) * np.sin(
    ...               2. * np.pi * f * t.rescale(s))).reshape((-1,1))*mV,
    ...       t_start=0*s, sampling_rate=1000*Hz)

    >>> analytic_signal = hilbert(a, N='nextpow')
    >>> angles = np.angle(analytic_signal)
    >>> amplitudes = np.abs(analytic_signal)
    >>> print angles
            [[-1.57079633]
             [-1.51334228]
             [-1.46047675]
             ...,
             [-1.73112977]
             [-1.68211683]
             [-1.62879501]]
    >>> plt.plot(t,angles)
    '''
    # Length of input signals
    n_org = signal.shape[0]

    # Right-pad signal to desired length using the signal itself
    if type(N) == int:
        # User defined padding
        n = N
    elif N == 'nextpow':
        # To speed up calculation of the Hilbert transform, make sure we change
        # the signal to be of a length that is a power of two. Failure to do so
        # results in computations of certain signal lengths to not finish (or
        # finish in absurd time). This might be a bug in scipy (0.16), e.g.,
        # the following code will not terminate for this value of k:
        #
        # import numpy
        # import scipy.signal
        # k=679346
        # t = np.arange(0, k) / 1000.
        # a = (1 + t / t[-1]) * np.sin(2 * np.pi * 5 * t)
        # analytic_signal = scipy.signal.hilbert(a)
        #
        # For this reason, nextpow is the default setting for now.

        n = 2 ** (int(np.log2(n_org - 1)) + 1)
    elif N == 'none':
        # No padding
        n = n_org
    else:
        raise ValueError("'{}' is an unknown N.".format(N))

    output = signal.duplicate_with_new_data(
        scipy.signal.hilbert(signal.magnitude, N=n, axis=0)[:n_org])
    return output / output.units
Esempio n. 7
0
def butter(signal, highpass_freq=None, lowpass_freq=None, order=4,
           filter_function='filtfilt', fs=1.0, axis=-1):
    """
    Butterworth filtering function for neo.AnalogSignal. Filter type is
    determined according to how values of `highpass_freq` and `lowpass_freq`
    are given (see Parameters section for details).

    Parameters
    ----------
    signal : AnalogSignal or Quantity array or NumPy ndarray
        Time series data to be filtered. When given as Quantity array or NumPy
        ndarray, the sampling frequency should be given through the keyword
        argument `fs`.
    highpass_freq, lowpass_freq : Quantity or float
        High-pass and low-pass cut-off frequencies, respectively. When given as
        float, the given value is taken as frequency in Hz.
        Filter type is determined depending on values of these arguments:
            * highpass_freq only (lowpass_freq = None):    highpass filter
            * lowpass_freq only (highpass_freq = None):    lowpass filter
            * highpass_freq < lowpass_freq:    bandpass filter
            * highpass_freq > lowpass_freq:    bandstop filter
    order : int
        Order of Butterworth filter. Default is 4.
    filter_function : string
        Filtering function to be used. Either 'filtfilt'
        (`scipy.signal.filtfilt()`) or 'lfilter' (`scipy.signal.lfilter()`). In
        most applications 'filtfilt' should be used, because it doesn't bring
        about phase shift due to filtering. Default is 'filtfilt'.
    fs : Quantity or float
        The sampling frequency of the input time series. When given as float,
        its value is taken as frequency in Hz. When the input is given as neo
        AnalogSignal, its attribute is used to specify the sampling
        frequency and this parameter is ignored. Default is 1.0.
    axis : int
        Axis along which filter is applied. Default is -1.

    Returns
    -------
    filtered_signal : AnalogSignal or Quantity array or NumPy ndarray
        Filtered input data. The shape and type is identical to those of the
        input.

    """

    def _design_butterworth_filter(Fs, hpfreq=None, lpfreq=None, order=4):
        # set parameters for filter design
        Fn = Fs / 2.
        # - filter type is determined according to the values of cut-off
        # frequencies
        if lpfreq and hpfreq:
            if hpfreq < lpfreq:
                Wn = (hpfreq / Fn, lpfreq / Fn)
                btype = 'bandpass'
            else:
                Wn = (lpfreq / Fn, hpfreq / Fn)
                btype = 'bandstop'
        elif lpfreq:
            Wn = lpfreq / Fn
            btype = 'lowpass'
        elif hpfreq:
            Wn = hpfreq / Fn
            btype = 'highpass'
        else:
            raise ValueError(
                "Either highpass_freq or lowpass_freq must be given"
            )

        # return filter coefficients
        return scipy.signal.butter(order, Wn, btype=btype)

    # design filter
    Fs = signal.sampling_rate.rescale(pq.Hz).magnitude \
        if hasattr(signal, 'sampling_rate') else fs
    Fh = highpass_freq.rescale(pq.Hz).magnitude \
        if isinstance(highpass_freq, pq.quantity.Quantity) else highpass_freq
    Fl = lowpass_freq.rescale(pq.Hz).magnitude \
        if isinstance(lowpass_freq, pq.quantity.Quantity) else lowpass_freq
    b, a = _design_butterworth_filter(Fs, Fh, Fl, order)

    # When the input is AnalogSignal, the axis for time index (i.e. the
    # first axis) needs to be rolled to the last
    data = np.asarray(signal)
    if isinstance(signal, neo.AnalogSignal):
        data = np.rollaxis(data, 0, len(data.shape))

    # apply filter
    if filter_function is 'lfilter':
        filtered_data = scipy.signal.lfilter(b, a, data, axis=axis)
    elif filter_function is 'filtfilt':
        filtered_data = scipy.signal.filtfilt(b, a, data, axis=axis)
    else:
        raise ValueError(
            "filter_func must to be either 'filtfilt' or 'lfilter'"
        )

    if isinstance(signal, neo.AnalogSignal):
        return signal.duplicate_with_new_data(np.rollaxis(filtered_data, -1, 0))
    elif isinstance(signal, pq.quantity.Quantity):
        return filtered_data * signal.units
    else:
        return filtered_data