_upfirdn.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  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. import numpy as np
  34. from ._upfirdn_apply import _output_len, _apply
  35. __all__ = ['upfirdn', '_output_len']
  36. def _pad_h(h, up):
  37. """Store coefficients in a transposed, flipped arrangement.
  38. For example, suppose upRate is 3, and the
  39. input number of coefficients is 10, represented as h[0], ..., h[9].
  40. Then the internal buffer will look like this::
  41. h[9], h[6], h[3], h[0], // flipped phase 0 coefs
  42. 0, h[7], h[4], h[1], // flipped phase 1 coefs (zero-padded)
  43. 0, h[8], h[5], h[2], // flipped phase 2 coefs (zero-padded)
  44. """
  45. h_padlen = len(h) + (-len(h) % up)
  46. h_full = np.zeros(h_padlen, h.dtype)
  47. h_full[:len(h)] = h
  48. h_full = h_full.reshape(-1, up).T[:, ::-1].ravel()
  49. return h_full
  50. class _UpFIRDn(object):
  51. def __init__(self, h, x_dtype, up, down):
  52. """Helper for resampling"""
  53. h = np.asarray(h)
  54. if h.ndim != 1 or h.size == 0:
  55. raise ValueError('h must be 1D with non-zero length')
  56. self._output_type = np.result_type(h.dtype, x_dtype, np.float32)
  57. h = np.asarray(h, self._output_type)
  58. self._up = int(up)
  59. self._down = int(down)
  60. if self._up < 1 or self._down < 1:
  61. raise ValueError('Both up and down must be >= 1')
  62. # This both transposes, and "flips" each phase for filtering
  63. self._h_trans_flip = _pad_h(h, self._up)
  64. self._h_trans_flip = np.ascontiguousarray(self._h_trans_flip)
  65. def apply_filter(self, x, axis=-1):
  66. """Apply the prepared filter to the specified axis of a nD signal x"""
  67. output_len = _output_len(len(self._h_trans_flip), x.shape[axis],
  68. self._up, self._down)
  69. output_shape = np.asarray(x.shape)
  70. output_shape[axis] = output_len
  71. out = np.zeros(output_shape, dtype=self._output_type, order='C')
  72. axis = axis % x.ndim
  73. _apply(np.asarray(x, self._output_type),
  74. self._h_trans_flip, out,
  75. self._up, self._down, axis)
  76. return out
  77. def upfirdn(h, x, up=1, down=1, axis=-1):
  78. """Upsample, FIR filter, and downsample
  79. Parameters
  80. ----------
  81. h : array_like
  82. 1-dimensional FIR (finite-impulse response) filter coefficients.
  83. x : array_like
  84. Input signal array.
  85. up : int, optional
  86. Upsampling rate. Default is 1.
  87. down : int, optional
  88. Downsampling rate. Default is 1.
  89. axis : int, optional
  90. The axis of the input data array along which to apply the
  91. linear filter. The filter is applied to each subarray along
  92. this axis. Default is -1.
  93. Returns
  94. -------
  95. y : ndarray
  96. The output signal array. Dimensions will be the same as `x` except
  97. for along `axis`, which will change size according to the `h`,
  98. `up`, and `down` parameters.
  99. Notes
  100. -----
  101. The algorithm is an implementation of the block diagram shown on page 129
  102. of the Vaidyanathan text [1]_ (Figure 4.3-8d).
  103. .. [1] P. P. Vaidyanathan, Multirate Systems and Filter Banks,
  104. Prentice Hall, 1993.
  105. The direct approach of upsampling by factor of P with zero insertion,
  106. FIR filtering of length ``N``, and downsampling by factor of Q is
  107. O(N*Q) per output sample. The polyphase implementation used here is
  108. O(N/P).
  109. .. versionadded:: 0.18
  110. Examples
  111. --------
  112. Simple operations:
  113. >>> from scipy.signal import upfirdn
  114. >>> upfirdn([1, 1, 1], [1, 1, 1]) # FIR filter
  115. array([ 1., 2., 3., 2., 1.])
  116. >>> upfirdn([1], [1, 2, 3], 3) # upsampling with zeros insertion
  117. array([ 1., 0., 0., 2., 0., 0., 3., 0., 0.])
  118. >>> upfirdn([1, 1, 1], [1, 2, 3], 3) # upsampling with sample-and-hold
  119. array([ 1., 1., 1., 2., 2., 2., 3., 3., 3.])
  120. >>> upfirdn([.5, 1, .5], [1, 1, 1], 2) # linear interpolation
  121. array([ 0.5, 1. , 1. , 1. , 1. , 1. , 0.5, 0. ])
  122. >>> upfirdn([1], np.arange(10), 1, 3) # decimation by 3
  123. array([ 0., 3., 6., 9.])
  124. >>> upfirdn([.5, 1, .5], np.arange(10), 2, 3) # linear interp, rate 2/3
  125. array([ 0. , 1. , 2.5, 4. , 5.5, 7. , 8.5, 0. ])
  126. Apply a single filter to multiple signals:
  127. >>> x = np.reshape(np.arange(8), (4, 2))
  128. >>> x
  129. array([[0, 1],
  130. [2, 3],
  131. [4, 5],
  132. [6, 7]])
  133. Apply along the last dimension of ``x``:
  134. >>> h = [1, 1]
  135. >>> upfirdn(h, x, 2)
  136. array([[ 0., 0., 1., 1.],
  137. [ 2., 2., 3., 3.],
  138. [ 4., 4., 5., 5.],
  139. [ 6., 6., 7., 7.]])
  140. Apply along the 0th dimension of ``x``:
  141. >>> upfirdn(h, x, 2, axis=0)
  142. array([[ 0., 1.],
  143. [ 0., 1.],
  144. [ 2., 3.],
  145. [ 2., 3.],
  146. [ 4., 5.],
  147. [ 4., 5.],
  148. [ 6., 7.],
  149. [ 6., 7.]])
  150. """
  151. x = np.asarray(x)
  152. ufd = _UpFIRDn(h, x.dtype, up, down)
  153. # This is equivalent to (but faster than) using np.apply_along_axis
  154. return ufd.apply_filter(x, axis)