bsplines.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. from __future__ import division, print_function, absolute_import
  2. from scipy._lib.six import xrange
  3. from numpy import (logical_and, asarray, pi, zeros_like,
  4. piecewise, array, arctan2, tan, zeros, arange, floor)
  5. from numpy.core.umath import (sqrt, exp, greater, less, cos, add, sin,
  6. less_equal, greater_equal)
  7. # From splinemodule.c
  8. from .spline import cspline2d, sepfir2d
  9. from scipy.special import comb, gamma
  10. __all__ = ['spline_filter', 'bspline', 'gauss_spline', 'cubic', 'quadratic',
  11. 'cspline1d', 'qspline1d', 'cspline1d_eval', 'qspline1d_eval']
  12. def factorial(n):
  13. return gamma(n + 1)
  14. def spline_filter(Iin, lmbda=5.0):
  15. """Smoothing spline (cubic) filtering of a rank-2 array.
  16. Filter an input data set, `Iin`, using a (cubic) smoothing spline of
  17. fall-off `lmbda`.
  18. """
  19. intype = Iin.dtype.char
  20. hcol = array([1.0, 4.0, 1.0], 'f') / 6.0
  21. if intype in ['F', 'D']:
  22. Iin = Iin.astype('F')
  23. ckr = cspline2d(Iin.real, lmbda)
  24. cki = cspline2d(Iin.imag, lmbda)
  25. outr = sepfir2d(ckr, hcol, hcol)
  26. outi = sepfir2d(cki, hcol, hcol)
  27. out = (outr + 1j * outi).astype(intype)
  28. elif intype in ['f', 'd']:
  29. ckr = cspline2d(Iin, lmbda)
  30. out = sepfir2d(ckr, hcol, hcol)
  31. out = out.astype(intype)
  32. else:
  33. raise TypeError("Invalid data type for Iin")
  34. return out
  35. _splinefunc_cache = {}
  36. def _bspline_piecefunctions(order):
  37. """Returns the function defined over the left-side pieces for a bspline of
  38. a given order.
  39. The 0th piece is the first one less than 0. The last piece is a function
  40. identical to 0 (returned as the constant 0). (There are order//2 + 2 total
  41. pieces).
  42. Also returns the condition functions that when evaluated return boolean
  43. arrays for use with `numpy.piecewise`.
  44. """
  45. try:
  46. return _splinefunc_cache[order]
  47. except KeyError:
  48. pass
  49. def condfuncgen(num, val1, val2):
  50. if num == 0:
  51. return lambda x: logical_and(less_equal(x, val1),
  52. greater_equal(x, val2))
  53. elif num == 2:
  54. return lambda x: less_equal(x, val2)
  55. else:
  56. return lambda x: logical_and(less(x, val1),
  57. greater_equal(x, val2))
  58. last = order // 2 + 2
  59. if order % 2:
  60. startbound = -1.0
  61. else:
  62. startbound = -0.5
  63. condfuncs = [condfuncgen(0, 0, startbound)]
  64. bound = startbound
  65. for num in xrange(1, last - 1):
  66. condfuncs.append(condfuncgen(1, bound, bound - 1))
  67. bound = bound - 1
  68. condfuncs.append(condfuncgen(2, 0, -(order + 1) / 2.0))
  69. # final value of bound is used in piecefuncgen below
  70. # the functions to evaluate are taken from the left-hand-side
  71. # in the general expression derived from the central difference
  72. # operator (because they involve fewer terms).
  73. fval = factorial(order)
  74. def piecefuncgen(num):
  75. Mk = order // 2 - num
  76. if (Mk < 0):
  77. return 0 # final function is 0
  78. coeffs = [(1 - 2 * (k % 2)) * float(comb(order + 1, k, exact=1)) / fval
  79. for k in xrange(Mk + 1)]
  80. shifts = [-bound - k for k in xrange(Mk + 1)]
  81. def thefunc(x):
  82. res = 0.0
  83. for k in range(Mk + 1):
  84. res += coeffs[k] * (x + shifts[k]) ** order
  85. return res
  86. return thefunc
  87. funclist = [piecefuncgen(k) for k in xrange(last)]
  88. _splinefunc_cache[order] = (funclist, condfuncs)
  89. return funclist, condfuncs
  90. def bspline(x, n):
  91. """B-spline basis function of order n.
  92. Notes
  93. -----
  94. Uses numpy.piecewise and automatic function-generator.
  95. """
  96. ax = -abs(asarray(x))
  97. # number of pieces on the left-side is (n+1)/2
  98. funclist, condfuncs = _bspline_piecefunctions(n)
  99. condlist = [func(ax) for func in condfuncs]
  100. return piecewise(ax, condlist, funclist)
  101. def gauss_spline(x, n):
  102. """Gaussian approximation to B-spline basis function of order n.
  103. Parameters
  104. ----------
  105. n : int
  106. The order of the spline. Must be nonnegative, i.e. n >= 0
  107. References
  108. ----------
  109. .. [1] Bouma H., Vilanova A., Bescos J.O., ter Haar Romeny B.M., Gerritsen
  110. F.A. (2007) Fast and Accurate Gaussian Derivatives Based on B-Splines. In:
  111. Sgallari F., Murli A., Paragios N. (eds) Scale Space and Variational
  112. Methods in Computer Vision. SSVM 2007. Lecture Notes in Computer
  113. Science, vol 4485. Springer, Berlin, Heidelberg
  114. """
  115. signsq = (n + 1) / 12.0
  116. return 1 / sqrt(2 * pi * signsq) * exp(-x ** 2 / 2 / signsq)
  117. def cubic(x):
  118. """A cubic B-spline.
  119. This is a special case of `bspline`, and equivalent to ``bspline(x, 3)``.
  120. """
  121. ax = abs(asarray(x))
  122. res = zeros_like(ax)
  123. cond1 = less(ax, 1)
  124. if cond1.any():
  125. ax1 = ax[cond1]
  126. res[cond1] = 2.0 / 3 - 1.0 / 2 * ax1 ** 2 * (2 - ax1)
  127. cond2 = ~cond1 & less(ax, 2)
  128. if cond2.any():
  129. ax2 = ax[cond2]
  130. res[cond2] = 1.0 / 6 * (2 - ax2) ** 3
  131. return res
  132. def quadratic(x):
  133. """A quadratic B-spline.
  134. This is a special case of `bspline`, and equivalent to ``bspline(x, 2)``.
  135. """
  136. ax = abs(asarray(x))
  137. res = zeros_like(ax)
  138. cond1 = less(ax, 0.5)
  139. if cond1.any():
  140. ax1 = ax[cond1]
  141. res[cond1] = 0.75 - ax1 ** 2
  142. cond2 = ~cond1 & less(ax, 1.5)
  143. if cond2.any():
  144. ax2 = ax[cond2]
  145. res[cond2] = (ax2 - 1.5) ** 2 / 2.0
  146. return res
  147. def _coeff_smooth(lam):
  148. xi = 1 - 96 * lam + 24 * lam * sqrt(3 + 144 * lam)
  149. omeg = arctan2(sqrt(144 * lam - 1), sqrt(xi))
  150. rho = (24 * lam - 1 - sqrt(xi)) / (24 * lam)
  151. rho = rho * sqrt((48 * lam + 24 * lam * sqrt(3 + 144 * lam)) / xi)
  152. return rho, omeg
  153. def _hc(k, cs, rho, omega):
  154. return (cs / sin(omega) * (rho ** k) * sin(omega * (k + 1)) *
  155. greater(k, -1))
  156. def _hs(k, cs, rho, omega):
  157. c0 = (cs * cs * (1 + rho * rho) / (1 - rho * rho) /
  158. (1 - 2 * rho * rho * cos(2 * omega) + rho ** 4))
  159. gamma = (1 - rho * rho) / (1 + rho * rho) / tan(omega)
  160. ak = abs(k)
  161. return c0 * rho ** ak * (cos(omega * ak) + gamma * sin(omega * ak))
  162. def _cubic_smooth_coeff(signal, lamb):
  163. rho, omega = _coeff_smooth(lamb)
  164. cs = 1 - 2 * rho * cos(omega) + rho * rho
  165. K = len(signal)
  166. yp = zeros((K,), signal.dtype.char)
  167. k = arange(K)
  168. yp[0] = (_hc(0, cs, rho, omega) * signal[0] +
  169. add.reduce(_hc(k + 1, cs, rho, omega) * signal))
  170. yp[1] = (_hc(0, cs, rho, omega) * signal[0] +
  171. _hc(1, cs, rho, omega) * signal[1] +
  172. add.reduce(_hc(k + 2, cs, rho, omega) * signal))
  173. for n in range(2, K):
  174. yp[n] = (cs * signal[n] + 2 * rho * cos(omega) * yp[n - 1] -
  175. rho * rho * yp[n - 2])
  176. y = zeros((K,), signal.dtype.char)
  177. y[K - 1] = add.reduce((_hs(k, cs, rho, omega) +
  178. _hs(k + 1, cs, rho, omega)) * signal[::-1])
  179. y[K - 2] = add.reduce((_hs(k - 1, cs, rho, omega) +
  180. _hs(k + 2, cs, rho, omega)) * signal[::-1])
  181. for n in range(K - 3, -1, -1):
  182. y[n] = (cs * yp[n] + 2 * rho * cos(omega) * y[n + 1] -
  183. rho * rho * y[n + 2])
  184. return y
  185. def _cubic_coeff(signal):
  186. zi = -2 + sqrt(3)
  187. K = len(signal)
  188. yplus = zeros((K,), signal.dtype.char)
  189. powers = zi ** arange(K)
  190. yplus[0] = signal[0] + zi * add.reduce(powers * signal)
  191. for k in range(1, K):
  192. yplus[k] = signal[k] + zi * yplus[k - 1]
  193. output = zeros((K,), signal.dtype)
  194. output[K - 1] = zi / (zi - 1) * yplus[K - 1]
  195. for k in range(K - 2, -1, -1):
  196. output[k] = zi * (output[k + 1] - yplus[k])
  197. return output * 6.0
  198. def _quadratic_coeff(signal):
  199. zi = -3 + 2 * sqrt(2.0)
  200. K = len(signal)
  201. yplus = zeros((K,), signal.dtype.char)
  202. powers = zi ** arange(K)
  203. yplus[0] = signal[0] + zi * add.reduce(powers * signal)
  204. for k in range(1, K):
  205. yplus[k] = signal[k] + zi * yplus[k - 1]
  206. output = zeros((K,), signal.dtype.char)
  207. output[K - 1] = zi / (zi - 1) * yplus[K - 1]
  208. for k in range(K - 2, -1, -1):
  209. output[k] = zi * (output[k + 1] - yplus[k])
  210. return output * 8.0
  211. def cspline1d(signal, lamb=0.0):
  212. """
  213. Compute cubic spline coefficients for rank-1 array.
  214. Find the cubic spline coefficients for a 1-D signal assuming
  215. mirror-symmetric boundary conditions. To obtain the signal back from the
  216. spline representation mirror-symmetric-convolve these coefficients with a
  217. length 3 FIR window [1.0, 4.0, 1.0]/ 6.0 .
  218. Parameters
  219. ----------
  220. signal : ndarray
  221. A rank-1 array representing samples of a signal.
  222. lamb : float, optional
  223. Smoothing coefficient, default is 0.0.
  224. Returns
  225. -------
  226. c : ndarray
  227. Cubic spline coefficients.
  228. """
  229. if lamb != 0.0:
  230. return _cubic_smooth_coeff(signal, lamb)
  231. else:
  232. return _cubic_coeff(signal)
  233. def qspline1d(signal, lamb=0.0):
  234. """Compute quadratic spline coefficients for rank-1 array.
  235. Find the quadratic spline coefficients for a 1-D signal assuming
  236. mirror-symmetric boundary conditions. To obtain the signal back from the
  237. spline representation mirror-symmetric-convolve these coefficients with a
  238. length 3 FIR window [1.0, 6.0, 1.0]/ 8.0 .
  239. Parameters
  240. ----------
  241. signal : ndarray
  242. A rank-1 array representing samples of a signal.
  243. lamb : float, optional
  244. Smoothing coefficient (must be zero for now).
  245. Returns
  246. -------
  247. c : ndarray
  248. Cubic spline coefficients.
  249. """
  250. if lamb != 0.0:
  251. raise ValueError("Smoothing quadratic splines not supported yet.")
  252. else:
  253. return _quadratic_coeff(signal)
  254. def cspline1d_eval(cj, newx, dx=1.0, x0=0):
  255. """Evaluate a spline at the new set of points.
  256. `dx` is the old sample-spacing while `x0` was the old origin. In
  257. other-words the old-sample points (knot-points) for which the `cj`
  258. represent spline coefficients were at equally-spaced points of:
  259. oldx = x0 + j*dx j=0...N-1, with N=len(cj)
  260. Edges are handled using mirror-symmetric boundary conditions.
  261. """
  262. newx = (asarray(newx) - x0) / float(dx)
  263. res = zeros_like(newx, dtype=cj.dtype)
  264. if res.size == 0:
  265. return res
  266. N = len(cj)
  267. cond1 = newx < 0
  268. cond2 = newx > (N - 1)
  269. cond3 = ~(cond1 | cond2)
  270. # handle general mirror-symmetry
  271. res[cond1] = cspline1d_eval(cj, -newx[cond1])
  272. res[cond2] = cspline1d_eval(cj, 2 * (N - 1) - newx[cond2])
  273. newx = newx[cond3]
  274. if newx.size == 0:
  275. return res
  276. result = zeros_like(newx, dtype=cj.dtype)
  277. jlower = floor(newx - 2).astype(int) + 1
  278. for i in range(4):
  279. thisj = jlower + i
  280. indj = thisj.clip(0, N - 1) # handle edge cases
  281. result += cj[indj] * cubic(newx - thisj)
  282. res[cond3] = result
  283. return res
  284. def qspline1d_eval(cj, newx, dx=1.0, x0=0):
  285. """Evaluate a quadratic spline at the new set of points.
  286. `dx` is the old sample-spacing while `x0` was the old origin. In
  287. other-words the old-sample points (knot-points) for which the `cj`
  288. represent spline coefficients were at equally-spaced points of::
  289. oldx = x0 + j*dx j=0...N-1, with N=len(cj)
  290. Edges are handled using mirror-symmetric boundary conditions.
  291. """
  292. newx = (asarray(newx) - x0) / dx
  293. res = zeros_like(newx)
  294. if res.size == 0:
  295. return res
  296. N = len(cj)
  297. cond1 = newx < 0
  298. cond2 = newx > (N - 1)
  299. cond3 = ~(cond1 | cond2)
  300. # handle general mirror-symmetry
  301. res[cond1] = qspline1d_eval(cj, -newx[cond1])
  302. res[cond2] = qspline1d_eval(cj, 2 * (N - 1) - newx[cond2])
  303. newx = newx[cond3]
  304. if newx.size == 0:
  305. return res
  306. result = zeros_like(newx)
  307. jlower = floor(newx - 1.5).astype(int) + 1
  308. for i in range(3):
  309. thisj = jlower + i
  310. indj = thisj.clip(0, N - 1) # handle edge cases
  311. result += cj[indj] * quadratic(newx - thisj)
  312. res[cond3] = result
  313. return res