CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
_cwt.py207 linesDownload Raw Back to pywt
1from math import ceil, floor
2
3from ._extensions._pywt import (
4    ContinuousWavelet,
5    DiscreteContinuousWavelet,
6    Wavelet,
7    _check_dtype,
8)
9from ._functions import integrate_wavelet, scale2frequency
10from ._utils import AxisError
11
12__all__ = ["cwt"]
13
14
15import numpy as np
16
17try:
18    import scipy
19    fftmodule = scipy.fft
20    next_fast_len = fftmodule.next_fast_len
21except ImportError:
22    fftmodule = np.fft
23
24    # provide a fallback so scipy is an optional requirement
25    # note: numpy.fft in numpy 2.0 is as fast as scipy.fft, so could be used
26    # unconditionally once the minimum supported numpy version is >=2.0
27    def next_fast_len(n):
28        """Round up size to the nearest power of two.
29
30        Given a number of samples `n`, returns the next power of two
31        following this number to take advantage of FFT speedup.
32        This fallback is less efficient than `scipy.fftpack.next_fast_len`
33        """
34        return 2**ceil(np.log2(n))
35
36
37def cwt(data, scales, wavelet, sampling_period=1., method='conv', axis=-1):
38    """
39    cwt(data, scales, wavelet)
40
41    One dimensional Continuous Wavelet Transform.
42
43    Parameters
44    ----------
45    data : array_like
46        Input signal
47    scales : array_like
48        The wavelet scales to use. One can use
49        ``f = scale2frequency(wavelet, scale)/sampling_period`` to determine
50        what physical frequency, ``f``. Here, ``f`` is in hertz when the
51        ``sampling_period`` is given in seconds.
52    wavelet : Wavelet object or name
53        Wavelet to use
54    sampling_period : float
55        Sampling period for the frequencies output (optional).
56        The values computed for ``coefs`` are independent of the choice of
57        ``sampling_period`` (i.e. ``scales`` is not scaled by the sampling
58        period).
59    method : {'conv', 'fft'}, optional
60        The method used to compute the CWT. Can be any of:
61            - ``conv`` uses ``numpy.convolve``.
62            - ``fft`` uses frequency domain convolution.
63            - ``auto`` uses automatic selection based on an estimate of the
64              computational complexity at each scale.
65
66        The ``conv`` method complexity is ``O(len(scale) * len(data))``.
67        The ``fft`` method is ``O(N * log2(N))`` with
68        ``N = len(scale) + len(data) - 1``. It is well suited for large size
69        signals but slightly slower than ``conv`` on small ones.
70    axis: int, optional
71        Axis over which to compute the CWT. If not given, the last axis is
72        used.
73
74    Returns
75    -------
76    coefs : array_like
77        Continuous wavelet transform of the input signal for the given scales
78        and wavelet. The first axis of ``coefs`` corresponds to the scales.
79        The remaining axes match the shape of ``data``.
80    frequencies : array_like
81        If the unit of sampling period are seconds and given, then frequencies
82        are in hertz. Otherwise, a sampling period of 1 is assumed.
83
84    Notes
85    -----
86    Size of coefficients arrays depends on the length of the input array and
87    the length of given scales.
88
89    Examples
90    --------
91    >>> import pywt
92    >>> import numpy as np
93    >>> import matplotlib.pyplot as plt
94    >>> x = np.arange(512)
95    >>> y = np.sin(2*np.pi*x/32)
96    >>> coef, freqs=pywt.cwt(y,np.arange(1,129),'gaus1')
97    >>> plt.matshow(coef)
98    >>> plt.show()
99
100    >>> import pywt
101    >>> import numpy as np
102    >>> import matplotlib.pyplot as plt
103    >>> t = np.linspace(-1, 1, 200, endpoint=False)
104    >>> sig  = np.cos(2 * np.pi * 7 * t) + np.real(np.exp(-7*(t-0.4)**2)*np.exp(1j*2*np.pi*2*(t-0.4)))
105    >>> widths = np.arange(1, 31)
106    >>> cwtmatr, freqs = pywt.cwt(sig, widths, 'mexh')
107    >>> plt.imshow(cwtmatr, extent=[-1, 1, 1, 31], cmap='PRGn', aspect='auto',
108    ...            vmax=abs(cwtmatr).max(), vmin=-abs(cwtmatr).max())
109    >>> plt.show()
110    """
111
112    # accept array_like input; make a copy to ensure a contiguous array
113    dt = _check_dtype(data)
114    data = np.asarray(data, dtype=dt)
115    dt_cplx = np.result_type(dt, np.complex64)
116    if not isinstance(wavelet, (ContinuousWavelet, Wavelet)):
117        wavelet = DiscreteContinuousWavelet(wavelet)
118
119    scales = np.atleast_1d(scales)
120    if np.any(scales <= 0):
121        raise ValueError("`scales` must only include positive values")
122
123    if not np.isscalar(axis):
124        raise AxisError("axis must be a scalar.")
125
126    dt_out = dt_cplx if wavelet.complex_cwt else dt
127    out = np.empty((np.size(scales),) + data.shape, dtype=dt_out)
128    precision = 10
129    int_psi, x = integrate_wavelet(wavelet, precision=precision)
130    int_psi = np.conj(int_psi) if wavelet.complex_cwt else int_psi
131
132    # convert int_psi, x to the same precision as the data
133    dt_psi = dt_cplx if int_psi.dtype.kind == 'c' else dt
134    int_psi = np.asarray(int_psi, dtype=dt_psi)
135    x = np.asarray(x, dtype=data.real.dtype)
136
137    if method == 'fft':
138        size_scale0 = -1
139        fft_data = None
140    elif method != "conv":
141        raise ValueError("method must be 'conv' or 'fft'")
142
143    if data.ndim > 1:
144        # move axis to be transformed last (so it is contiguous)
145        data = data.swapaxes(-1, axis)
146
147        # reshape to (n_batch, data.shape[-1])
148        data_shape_pre = data.shape
149        data = data.reshape((-1, data.shape[-1]))
150
151    for i, scale in enumerate(scales):
152        step = x[1] - x[0]
153        j = np.arange(scale * (x[-1] - x[0]) + 1) / (scale * step)
154        j = j.astype(int)  # floor
155        if j[-1] >= int_psi.size:
156            j = np.extract(j < int_psi.size, j)
157        int_psi_scale = int_psi[j][::-1]
158
159        if method == 'conv':
160            if data.ndim == 1:
161                conv = np.convolve(data, int_psi_scale)
162            else:
163                # batch convolution via loop
164                conv_shape = list(data.shape)
165                conv_shape[-1] += int_psi_scale.size - 1
166                conv_shape = tuple(conv_shape)
167                conv = np.empty(conv_shape, dtype=dt_out)
168                for n in range(data.shape[0]):
169                    conv[n, :] = np.convolve(data[n], int_psi_scale)
170        else:
171            # The padding is selected for:
172            # - optimal FFT complexity
173            # - to be larger than the two signals length to avoid circular
174            #   convolution
175            size_scale = next_fast_len(
176                data.shape[-1] + int_psi_scale.size - 1
177            )
178            if size_scale != size_scale0:
179                # Must recompute fft_data when the padding size changes.
180                fft_data = fftmodule.fft(data, size_scale, axis=-1)
181            size_scale0 = size_scale
182            fft_wav = fftmodule.fft(int_psi_scale, size_scale, axis=-1)
183            conv = fftmodule.ifft(fft_wav * fft_data, axis=-1)
184            conv = conv[..., :data.shape[-1] + int_psi_scale.size - 1]
185
186        coef = - np.sqrt(scale) * np.diff(conv, axis=-1)
187        if out.dtype.kind != 'c':
188            coef = coef.real
189        # transform axis is always -1 due to the data reshape above
190        d = (coef.shape[-1] - data.shape[-1]) / 2.
191        if d > 0:
192            coef = coef[..., floor(d):-ceil(d)]
193        elif d < 0:
194            raise ValueError(
195                f"Selected scale of {scale} too small.")
196        if data.ndim > 1:
197            # restore original data shape and axis position
198            coef = coef.reshape(data_shape_pre)
199            coef = coef.swapaxes(axis, -1)
200        out[i, ...] = coef
201
202    frequencies = scale2frequency(wavelet, scales, precision)
203    if np.isscalar(frequencies):
204        frequencies = np.array([frequencies])
205    frequencies /= sampling_period
206    return out, frequencies
207 
Aluode/PerceptionLabPortable · CoolFace