1# Code adapted from "upfirdn" python library with permission:
2#
3# Copyright (c) 2009, Motorola, Inc
4#
5# All Rights Reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions are
9# met:
10#
11# * Redistributions of source code must retain the above copyright notice,
12# this list of conditions and the following disclaimer.
13#
14# * Redistributions in binary form must reproduce the above copyright
15# notice, this list of conditions and the following disclaimer in the
16# documentation and/or other materials provided with the distribution.
17#
18# * Neither the name of Motorola nor the names of its contributors may be
19# used to endorse or promote products derived from this software without
20# specific prior written permission.
21#
22# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
23# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
24# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
25# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
26# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
27# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
28# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
29# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
30# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
31# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
32# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
34import numpy as np
35
36from ._upfirdn_apply import _output_len, _apply, mode_enum
37
38__all__ = ['upfirdn', '_output_len']
39
40_upfirdn_modes = [
41    'constant', 'wrap', 'edge', 'smooth', 'symmetric', 'reflect',
42    'antisymmetric', 'antireflect', 'line',
43]
44
45
46def _pad_h(h, up):
47    """Store coefficients in a transposed, flipped arrangement.
48
49    For example, suppose upRate is 3, and the
50    input number of coefficients is 10, represented as h[0], ..., h[9].
51
52    Then the internal buffer will look like this::
53
54       h[9], h[6], h[3], h[0],   // flipped phase 0 coefs
55       0,    h[7], h[4], h[1],   // flipped phase 1 coefs (zero-padded)
56       0,    h[8], h[5], h[2],   // flipped phase 2 coefs (zero-padded)
57
58    """
59    h_padlen = len(h) + (-len(h) % up)
60    h_full = np.zeros(h_padlen, h.dtype)
61    h_full[:len(h)] = h
62    h_full = h_full.reshape(-1, up).T[:, ::-1].ravel()
63    return h_full
64
65
66def _check_mode(mode):
67    mode = mode.lower()
68    enum = mode_enum(mode)
69    return enum
70
71
72class _UpFIRDn:
73    """Helper for resampling."""
74
75    def __init__(self, h, x_dtype, up, down):
76        h = np.asarray(h)
77        if h.ndim != 1 or h.size == 0:
78            raise ValueError('h must be 1-D with non-zero length')
79        self._output_type = np.result_type(h.dtype, x_dtype, np.float32)
80        h = np.asarray(h, self._output_type)
81        self._up = int(up)
82        self._down = int(down)
83        if self._up < 1 or self._down < 1:
84            raise ValueError('Both up and down must be >= 1')
85        # This both transposes, and "flips" each phase for filtering
86        self._h_trans_flip = _pad_h(h, self._up)
87        self._h_trans_flip = np.ascontiguousarray(self._h_trans_flip)
88        self._h_len_orig = len(h)
89
90    def apply_filter(self, x, axis=-1, mode='constant', cval=0):
91        """Apply the prepared filter to the specified axis of N-D signal x."""
92        output_len = _output_len(self._h_len_orig, x.shape[axis],
93                                 self._up, self._down)
94        # Explicit use of np.int64 for output_shape dtype avoids OverflowError
95        # when allocating large array on platforms where np.int_ is 32 bits
96        output_shape = np.asarray(x.shape, dtype=np.int64)
97        output_shape[axis] = output_len
98        out = np.zeros(output_shape, dtype=self._output_type, order='C')
99        axis = axis % x.ndim
100        mode = _check_mode(mode)
101        _apply(np.asarray(x, self._output_type),
102               self._h_trans_flip, out,
103               self._up, self._down, axis, mode, cval)
104        return out
105
106
107def upfirdn(h, x, up=1, down=1, axis=-1, mode='constant', cval=0):
108    """Upsample, FIR filter, and downsample.
109
110    Parameters
111    ----------
112    h : array_like
113        1-D FIR (finite-impulse response) filter coefficients.
114    x : array_like
115        Input signal array.
116    up : int, optional
117        Upsampling rate. Default is 1.
118    down : int, optional
119        Downsampling rate. Default is 1.
120    axis : int, optional
121        The axis of the input data array along which to apply the
122        linear filter. The filter is applied to each subarray along
123        this axis. Default is -1.
124    mode : str, optional
125        The signal extension mode to use. The set
126        ``{"constant", "symmetric", "reflect", "edge", "wrap"}`` correspond to
127        modes provided by `numpy.pad`. ``"smooth"`` implements a smooth
128        extension by extending based on the slope of the last 2 points at each
129        end of the array. ``"antireflect"`` and ``"antisymmetric"`` are
130        anti-symmetric versions of ``"reflect"`` and ``"symmetric"``. The mode
131        `"line"` extends the signal based on a linear trend defined by the
132        first and last points along the ``axis``.
133
134        .. versionadded:: 1.4.0
135    cval : float, optional
136        The constant value to use when ``mode == "constant"``.
137
138        .. versionadded:: 1.4.0
139
140    Returns
141    -------
142    y : ndarray
143        The output signal array. Dimensions will be the same as `x` except
144        for along `axis`, which will change size according to the `h`,
145        `up`,  and `down` parameters.
146
147    Notes
148    -----
149    The algorithm is an implementation of the block diagram shown on page 129
150    of the Vaidyanathan text [1]_ (Figure 4.3-8d).
151
152    The direct approach of upsampling by factor of P with zero insertion,
153    FIR filtering of length ``N``, and downsampling by factor of Q is
154    O(N*Q) per output sample. The polyphase implementation used here is
155    O(N/P).
156
157    .. versionadded:: 0.18
158
159    References
160    ----------
161    .. [1] P. P. Vaidyanathan, Multirate Systems and Filter Banks,
162           Prentice Hall, 1993.
163
164    Examples
165    --------
166    Simple operations:
167
168    >>> from scipy.signal import upfirdn
169    >>> upfirdn([1, 1, 1], [1, 1, 1])   # FIR filter
170    array([ 1.,  2.,  3.,  2.,  1.])
171    >>> upfirdn([1], [1, 2, 3], 3)  # upsampling with zeros insertion
172    array([ 1.,  0.,  0.,  2.,  0.,  0.,  3.,  0.,  0.])
173    >>> upfirdn([1, 1, 1], [1, 2, 3], 3)  # upsampling with sample-and-hold
174    array([ 1.,  1.,  1.,  2.,  2.,  2.,  3.,  3.,  3.])
175    >>> upfirdn([.5, 1, .5], [1, 1, 1], 2)  # linear interpolation
176    array([ 0.5,  1. ,  1. ,  1. ,  1. ,  1. ,  0.5,  0. ])
177    >>> upfirdn([1], np.arange(10), 1, 3)  # decimation by 3
178    array([ 0.,  3.,  6.,  9.])
179    >>> upfirdn([.5, 1, .5], np.arange(10), 2, 3)  # linear interp, rate 2/3
180    array([ 0. ,  1. ,  2.5,  4. ,  5.5,  7. ,  8.5,  0. ])
181
182    Apply a single filter to multiple signals:
183
184    >>> x = np.reshape(np.arange(8), (4, 2))
185    >>> x
186    array([[0, 1],
187           [2, 3],
188           [4, 5],
189           [6, 7]])
190
191    Apply along the last dimension of ``x``:
192
193    >>> h = [1, 1]
194    >>> upfirdn(h, x, 2)
195    array([[ 0.,  0.,  1.,  1.],
196           [ 2.,  2.,  3.,  3.],
197           [ 4.,  4.,  5.,  5.],
198           [ 6.,  6.,  7.,  7.]])
199
200    Apply along the 0th dimension of ``x``:
201
202    >>> upfirdn(h, x, 2, axis=0)
203    array([[ 0.,  1.],
204           [ 0.,  1.],
205           [ 2.,  3.],
206           [ 2.,  3.],
207           [ 4.,  5.],
208           [ 4.,  5.],
209           [ 6.,  7.],
210           [ 6.,  7.]])
211    """
212    x = np.asarray(x)
213    ufd = _UpFIRDn(h, x.dtype, up, down)
214    # This is equivalent to (but faster than) using np.apply_along_axis
215    return ufd.apply_filter(x, axis, mode, cval)
216