_interpolation.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960
  1. # Copyright (C) 2003-2005 Peter J. Verveer
  2. #
  3. # Redistribution and use in source and binary forms, with or without
  4. # modification, are permitted provided that the following conditions
  5. # are met:
  6. #
  7. # 1. Redistributions of source code must retain the above copyright
  8. # notice, this list of conditions and the following disclaimer.
  9. #
  10. # 2. Redistributions in binary form must reproduce the above
  11. # copyright notice, this list of conditions and the following
  12. # disclaimer in the documentation and/or other materials provided
  13. # with the distribution.
  14. #
  15. # 3. The name of the author may not be used to endorse or promote
  16. # products derived from this software without specific prior
  17. # written permission.
  18. #
  19. # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
  20. # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  21. # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  22. # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
  23. # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  24. # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
  25. # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  26. # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  27. # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  28. # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  29. # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30. import itertools
  31. import warnings
  32. import numpy
  33. from numpy.core.multiarray import normalize_axis_index
  34. from scipy import special
  35. from . import _ni_support
  36. from . import _nd_image
  37. from ._ni_docstrings import docfiller
  38. __all__ = ['spline_filter1d', 'spline_filter', 'geometric_transform',
  39. 'map_coordinates', 'affine_transform', 'shift', 'zoom', 'rotate']
  40. @docfiller
  41. def spline_filter1d(input, order=3, axis=-1, output=numpy.float64,
  42. mode='mirror'):
  43. """
  44. Calculate a 1-D spline filter along the given axis.
  45. The lines of the array along the given axis are filtered by a
  46. spline filter. The order of the spline must be >= 2 and <= 5.
  47. Parameters
  48. ----------
  49. %(input)s
  50. order : int, optional
  51. The order of the spline, default is 3.
  52. axis : int, optional
  53. The axis along which the spline filter is applied. Default is the last
  54. axis.
  55. output : ndarray or dtype, optional
  56. The array in which to place the output, or the dtype of the returned
  57. array. Default is ``numpy.float64``.
  58. %(mode_interp_mirror)s
  59. Returns
  60. -------
  61. spline_filter1d : ndarray
  62. The filtered input.
  63. Notes
  64. -----
  65. All of the interpolation functions in `ndimage` do spline interpolation of
  66. the input image. If using B-splines of `order > 1`, the input image
  67. values have to be converted to B-spline coefficients first, which is
  68. done by applying this 1-D filter sequentially along all
  69. axes of the input. All functions that require B-spline coefficients
  70. will automatically filter their inputs, a behavior controllable with
  71. the `prefilter` keyword argument. For functions that accept a `mode`
  72. parameter, the result will only be correct if it matches the `mode`
  73. used when filtering.
  74. For complex-valued `input`, this function processes the real and imaginary
  75. components independently.
  76. .. versionadded:: 1.6.0
  77. Complex-valued support added.
  78. See Also
  79. --------
  80. spline_filter : Multidimensional spline filter.
  81. Examples
  82. --------
  83. We can filter an image using 1-D spline along the given axis:
  84. >>> from scipy.ndimage import spline_filter1d
  85. >>> import numpy as np
  86. >>> import matplotlib.pyplot as plt
  87. >>> orig_img = np.eye(20) # create an image
  88. >>> orig_img[10, :] = 1.0
  89. >>> sp_filter_axis_0 = spline_filter1d(orig_img, axis=0)
  90. >>> sp_filter_axis_1 = spline_filter1d(orig_img, axis=1)
  91. >>> f, ax = plt.subplots(1, 3, sharex=True)
  92. >>> for ind, data in enumerate([[orig_img, "original image"],
  93. ... [sp_filter_axis_0, "spline filter (axis=0)"],
  94. ... [sp_filter_axis_1, "spline filter (axis=1)"]]):
  95. ... ax[ind].imshow(data[0], cmap='gray_r')
  96. ... ax[ind].set_title(data[1])
  97. >>> plt.tight_layout()
  98. >>> plt.show()
  99. """
  100. if order < 0 or order > 5:
  101. raise RuntimeError('spline order not supported')
  102. input = numpy.asarray(input)
  103. complex_output = numpy.iscomplexobj(input)
  104. output = _ni_support._get_output(output, input,
  105. complex_output=complex_output)
  106. if complex_output:
  107. spline_filter1d(input.real, order, axis, output.real, mode)
  108. spline_filter1d(input.imag, order, axis, output.imag, mode)
  109. return output
  110. if order in [0, 1]:
  111. output[...] = numpy.array(input)
  112. else:
  113. mode = _ni_support._extend_mode_to_code(mode)
  114. axis = normalize_axis_index(axis, input.ndim)
  115. _nd_image.spline_filter1d(input, order, axis, output, mode)
  116. return output
  117. def spline_filter(input, order=3, output=numpy.float64, mode='mirror'):
  118. """
  119. Multidimensional spline filter.
  120. For more details, see `spline_filter1d`.
  121. See Also
  122. --------
  123. spline_filter1d : Calculate a 1-D spline filter along the given axis.
  124. Notes
  125. -----
  126. The multidimensional filter is implemented as a sequence of
  127. 1-D spline filters. The intermediate arrays are stored
  128. in the same data type as the output. Therefore, for output types
  129. with a limited precision, the results may be imprecise because
  130. intermediate results may be stored with insufficient precision.
  131. For complex-valued `input`, this function processes the real and imaginary
  132. components independently.
  133. .. versionadded:: 1.6.0
  134. Complex-valued support added.
  135. Examples
  136. --------
  137. We can filter an image using multidimentional splines:
  138. >>> from scipy.ndimage import spline_filter
  139. >>> import numpy as np
  140. >>> import matplotlib.pyplot as plt
  141. >>> orig_img = np.eye(20) # create an image
  142. >>> orig_img[10, :] = 1.0
  143. >>> sp_filter = spline_filter(orig_img, order=3)
  144. >>> f, ax = plt.subplots(1, 2, sharex=True)
  145. >>> for ind, data in enumerate([[orig_img, "original image"],
  146. ... [sp_filter, "spline filter"]]):
  147. ... ax[ind].imshow(data[0], cmap='gray_r')
  148. ... ax[ind].set_title(data[1])
  149. >>> plt.tight_layout()
  150. >>> plt.show()
  151. """
  152. if order < 2 or order > 5:
  153. raise RuntimeError('spline order not supported')
  154. input = numpy.asarray(input)
  155. complex_output = numpy.iscomplexobj(input)
  156. output = _ni_support._get_output(output, input,
  157. complex_output=complex_output)
  158. if complex_output:
  159. spline_filter(input.real, order, output.real, mode)
  160. spline_filter(input.imag, order, output.imag, mode)
  161. return output
  162. if order not in [0, 1] and input.ndim > 0:
  163. for axis in range(input.ndim):
  164. spline_filter1d(input, order, axis, output=output, mode=mode)
  165. input = output
  166. else:
  167. output[...] = input[...]
  168. return output
  169. def _prepad_for_spline_filter(input, mode, cval):
  170. if mode in ['nearest', 'grid-constant']:
  171. npad = 12
  172. if mode == 'grid-constant':
  173. padded = numpy.pad(input, npad, mode='constant',
  174. constant_values=cval)
  175. elif mode == 'nearest':
  176. padded = numpy.pad(input, npad, mode='edge')
  177. else:
  178. # other modes have exact boundary conditions implemented so
  179. # no prepadding is needed
  180. npad = 0
  181. padded = input
  182. return padded, npad
  183. @docfiller
  184. def geometric_transform(input, mapping, output_shape=None,
  185. output=None, order=3,
  186. mode='constant', cval=0.0, prefilter=True,
  187. extra_arguments=(), extra_keywords={}):
  188. """
  189. Apply an arbitrary geometric transform.
  190. The given mapping function is used to find, for each point in the
  191. output, the corresponding coordinates in the input. The value of the
  192. input at those coordinates is determined by spline interpolation of
  193. the requested order.
  194. Parameters
  195. ----------
  196. %(input)s
  197. mapping : {callable, scipy.LowLevelCallable}
  198. A callable object that accepts a tuple of length equal to the output
  199. array rank, and returns the corresponding input coordinates as a tuple
  200. of length equal to the input array rank.
  201. output_shape : tuple of ints, optional
  202. Shape tuple.
  203. %(output)s
  204. order : int, optional
  205. The order of the spline interpolation, default is 3.
  206. The order has to be in the range 0-5.
  207. %(mode_interp_constant)s
  208. %(cval)s
  209. %(prefilter)s
  210. extra_arguments : tuple, optional
  211. Extra arguments passed to `mapping`.
  212. extra_keywords : dict, optional
  213. Extra keywords passed to `mapping`.
  214. Returns
  215. -------
  216. output : ndarray
  217. The filtered input.
  218. See Also
  219. --------
  220. map_coordinates, affine_transform, spline_filter1d
  221. Notes
  222. -----
  223. This function also accepts low-level callback functions with one
  224. the following signatures and wrapped in `scipy.LowLevelCallable`:
  225. .. code:: c
  226. int mapping(npy_intp *output_coordinates, double *input_coordinates,
  227. int output_rank, int input_rank, void *user_data)
  228. int mapping(intptr_t *output_coordinates, double *input_coordinates,
  229. int output_rank, int input_rank, void *user_data)
  230. The calling function iterates over the elements of the output array,
  231. calling the callback function at each element. The coordinates of the
  232. current output element are passed through ``output_coordinates``. The
  233. callback function must return the coordinates at which the input must
  234. be interpolated in ``input_coordinates``. The rank of the input and
  235. output arrays are given by ``input_rank`` and ``output_rank``
  236. respectively. ``user_data`` is the data pointer provided
  237. to `scipy.LowLevelCallable` as-is.
  238. The callback function must return an integer error status that is zero
  239. if something went wrong and one otherwise. If an error occurs, you should
  240. normally set the Python error status with an informative message
  241. before returning, otherwise a default error message is set by the
  242. calling function.
  243. In addition, some other low-level function pointer specifications
  244. are accepted, but these are for backward compatibility only and should
  245. not be used in new code.
  246. For complex-valued `input`, this function transforms the real and imaginary
  247. components independently.
  248. .. versionadded:: 1.6.0
  249. Complex-valued support added.
  250. Examples
  251. --------
  252. >>> import numpy as np
  253. >>> from scipy.ndimage import geometric_transform
  254. >>> a = np.arange(12.).reshape((4, 3))
  255. >>> def shift_func(output_coords):
  256. ... return (output_coords[0] - 0.5, output_coords[1] - 0.5)
  257. ...
  258. >>> geometric_transform(a, shift_func)
  259. array([[ 0. , 0. , 0. ],
  260. [ 0. , 1.362, 2.738],
  261. [ 0. , 4.812, 6.187],
  262. [ 0. , 8.263, 9.637]])
  263. >>> b = [1, 2, 3, 4, 5]
  264. >>> def shift_func(output_coords):
  265. ... return (output_coords[0] - 3,)
  266. ...
  267. >>> geometric_transform(b, shift_func, mode='constant')
  268. array([0, 0, 0, 1, 2])
  269. >>> geometric_transform(b, shift_func, mode='nearest')
  270. array([1, 1, 1, 1, 2])
  271. >>> geometric_transform(b, shift_func, mode='reflect')
  272. array([3, 2, 1, 1, 2])
  273. >>> geometric_transform(b, shift_func, mode='wrap')
  274. array([2, 3, 4, 1, 2])
  275. """
  276. if order < 0 or order > 5:
  277. raise RuntimeError('spline order not supported')
  278. input = numpy.asarray(input)
  279. if output_shape is None:
  280. output_shape = input.shape
  281. if input.ndim < 1 or len(output_shape) < 1:
  282. raise RuntimeError('input and output rank must be > 0')
  283. complex_output = numpy.iscomplexobj(input)
  284. output = _ni_support._get_output(output, input, shape=output_shape,
  285. complex_output=complex_output)
  286. if complex_output:
  287. kwargs = dict(order=order, mode=mode, prefilter=prefilter,
  288. output_shape=output_shape,
  289. extra_arguments=extra_arguments,
  290. extra_keywords=extra_keywords)
  291. geometric_transform(input.real, mapping, output=output.real,
  292. cval=numpy.real(cval), **kwargs)
  293. geometric_transform(input.imag, mapping, output=output.imag,
  294. cval=numpy.imag(cval), **kwargs)
  295. return output
  296. if prefilter and order > 1:
  297. padded, npad = _prepad_for_spline_filter(input, mode, cval)
  298. filtered = spline_filter(padded, order, output=numpy.float64,
  299. mode=mode)
  300. else:
  301. npad = 0
  302. filtered = input
  303. mode = _ni_support._extend_mode_to_code(mode)
  304. _nd_image.geometric_transform(filtered, mapping, None, None, None, output,
  305. order, mode, cval, npad, extra_arguments,
  306. extra_keywords)
  307. return output
  308. @docfiller
  309. def map_coordinates(input, coordinates, output=None, order=3,
  310. mode='constant', cval=0.0, prefilter=True):
  311. """
  312. Map the input array to new coordinates by interpolation.
  313. The array of coordinates is used to find, for each point in the output,
  314. the corresponding coordinates in the input. The value of the input at
  315. those coordinates is determined by spline interpolation of the
  316. requested order.
  317. The shape of the output is derived from that of the coordinate
  318. array by dropping the first axis. The values of the array along
  319. the first axis are the coordinates in the input array at which the
  320. output value is found.
  321. Parameters
  322. ----------
  323. %(input)s
  324. coordinates : array_like
  325. The coordinates at which `input` is evaluated.
  326. %(output)s
  327. order : int, optional
  328. The order of the spline interpolation, default is 3.
  329. The order has to be in the range 0-5.
  330. %(mode_interp_constant)s
  331. %(cval)s
  332. %(prefilter)s
  333. Returns
  334. -------
  335. map_coordinates : ndarray
  336. The result of transforming the input. The shape of the output is
  337. derived from that of `coordinates` by dropping the first axis.
  338. See Also
  339. --------
  340. spline_filter, geometric_transform, scipy.interpolate
  341. Notes
  342. -----
  343. For complex-valued `input`, this function maps the real and imaginary
  344. components independently.
  345. .. versionadded:: 1.6.0
  346. Complex-valued support added.
  347. Examples
  348. --------
  349. >>> from scipy import ndimage
  350. >>> import numpy as np
  351. >>> a = np.arange(12.).reshape((4, 3))
  352. >>> a
  353. array([[ 0., 1., 2.],
  354. [ 3., 4., 5.],
  355. [ 6., 7., 8.],
  356. [ 9., 10., 11.]])
  357. >>> ndimage.map_coordinates(a, [[0.5, 2], [0.5, 1]], order=1)
  358. array([ 2., 7.])
  359. Above, the interpolated value of a[0.5, 0.5] gives output[0], while
  360. a[2, 1] is output[1].
  361. >>> inds = np.array([[0.5, 2], [0.5, 4]])
  362. >>> ndimage.map_coordinates(a, inds, order=1, cval=-33.3)
  363. array([ 2. , -33.3])
  364. >>> ndimage.map_coordinates(a, inds, order=1, mode='nearest')
  365. array([ 2., 8.])
  366. >>> ndimage.map_coordinates(a, inds, order=1, cval=0, output=bool)
  367. array([ True, False], dtype=bool)
  368. """
  369. if order < 0 or order > 5:
  370. raise RuntimeError('spline order not supported')
  371. input = numpy.asarray(input)
  372. coordinates = numpy.asarray(coordinates)
  373. if numpy.iscomplexobj(coordinates):
  374. raise TypeError('Complex type not supported')
  375. output_shape = coordinates.shape[1:]
  376. if input.ndim < 1 or len(output_shape) < 1:
  377. raise RuntimeError('input and output rank must be > 0')
  378. if coordinates.shape[0] != input.ndim:
  379. raise RuntimeError('invalid shape for coordinate array')
  380. complex_output = numpy.iscomplexobj(input)
  381. output = _ni_support._get_output(output, input, shape=output_shape,
  382. complex_output=complex_output)
  383. if complex_output:
  384. kwargs = dict(order=order, mode=mode, prefilter=prefilter)
  385. map_coordinates(input.real, coordinates, output=output.real,
  386. cval=numpy.real(cval), **kwargs)
  387. map_coordinates(input.imag, coordinates, output=output.imag,
  388. cval=numpy.imag(cval), **kwargs)
  389. return output
  390. if prefilter and order > 1:
  391. padded, npad = _prepad_for_spline_filter(input, mode, cval)
  392. filtered = spline_filter(padded, order, output=numpy.float64,
  393. mode=mode)
  394. else:
  395. npad = 0
  396. filtered = input
  397. mode = _ni_support._extend_mode_to_code(mode)
  398. _nd_image.geometric_transform(filtered, None, coordinates, None, None,
  399. output, order, mode, cval, npad, None, None)
  400. return output
  401. @docfiller
  402. def affine_transform(input, matrix, offset=0.0, output_shape=None,
  403. output=None, order=3,
  404. mode='constant', cval=0.0, prefilter=True):
  405. """
  406. Apply an affine transformation.
  407. Given an output image pixel index vector ``o``, the pixel value
  408. is determined from the input image at position
  409. ``np.dot(matrix, o) + offset``.
  410. This does 'pull' (or 'backward') resampling, transforming the output space
  411. to the input to locate data. Affine transformations are often described in
  412. the 'push' (or 'forward') direction, transforming input to output. If you
  413. have a matrix for the 'push' transformation, use its inverse
  414. (:func:`numpy.linalg.inv`) in this function.
  415. Parameters
  416. ----------
  417. %(input)s
  418. matrix : ndarray
  419. The inverse coordinate transformation matrix, mapping output
  420. coordinates to input coordinates. If ``ndim`` is the number of
  421. dimensions of ``input``, the given matrix must have one of the
  422. following shapes:
  423. - ``(ndim, ndim)``: the linear transformation matrix for each
  424. output coordinate.
  425. - ``(ndim,)``: assume that the 2-D transformation matrix is
  426. diagonal, with the diagonal specified by the given value. A more
  427. efficient algorithm is then used that exploits the separability
  428. of the problem.
  429. - ``(ndim + 1, ndim + 1)``: assume that the transformation is
  430. specified using homogeneous coordinates [1]_. In this case, any
  431. value passed to ``offset`` is ignored.
  432. - ``(ndim, ndim + 1)``: as above, but the bottom row of a
  433. homogeneous transformation matrix is always ``[0, 0, ..., 1]``,
  434. and may be omitted.
  435. offset : float or sequence, optional
  436. The offset into the array where the transform is applied. If a float,
  437. `offset` is the same for each axis. If a sequence, `offset` should
  438. contain one value for each axis.
  439. output_shape : tuple of ints, optional
  440. Shape tuple.
  441. %(output)s
  442. order : int, optional
  443. The order of the spline interpolation, default is 3.
  444. The order has to be in the range 0-5.
  445. %(mode_interp_constant)s
  446. %(cval)s
  447. %(prefilter)s
  448. Returns
  449. -------
  450. affine_transform : ndarray
  451. The transformed input.
  452. Notes
  453. -----
  454. The given matrix and offset are used to find for each point in the
  455. output the corresponding coordinates in the input by an affine
  456. transformation. The value of the input at those coordinates is
  457. determined by spline interpolation of the requested order. Points
  458. outside the boundaries of the input are filled according to the given
  459. mode.
  460. .. versionchanged:: 0.18.0
  461. Previously, the exact interpretation of the affine transformation
  462. depended on whether the matrix was supplied as a 1-D or a
  463. 2-D array. If a 1-D array was supplied
  464. to the matrix parameter, the output pixel value at index ``o``
  465. was determined from the input image at position
  466. ``matrix * (o + offset)``.
  467. For complex-valued `input`, this function transforms the real and imaginary
  468. components independently.
  469. .. versionadded:: 1.6.0
  470. Complex-valued support added.
  471. References
  472. ----------
  473. .. [1] https://en.wikipedia.org/wiki/Homogeneous_coordinates
  474. """
  475. if order < 0 or order > 5:
  476. raise RuntimeError('spline order not supported')
  477. input = numpy.asarray(input)
  478. if output_shape is None:
  479. if isinstance(output, numpy.ndarray):
  480. output_shape = output.shape
  481. else:
  482. output_shape = input.shape
  483. if input.ndim < 1 or len(output_shape) < 1:
  484. raise RuntimeError('input and output rank must be > 0')
  485. complex_output = numpy.iscomplexobj(input)
  486. output = _ni_support._get_output(output, input, shape=output_shape,
  487. complex_output=complex_output)
  488. if complex_output:
  489. kwargs = dict(offset=offset, output_shape=output_shape, order=order,
  490. mode=mode, prefilter=prefilter)
  491. affine_transform(input.real, matrix, output=output.real,
  492. cval=numpy.real(cval), **kwargs)
  493. affine_transform(input.imag, matrix, output=output.imag,
  494. cval=numpy.imag(cval), **kwargs)
  495. return output
  496. if prefilter and order > 1:
  497. padded, npad = _prepad_for_spline_filter(input, mode, cval)
  498. filtered = spline_filter(padded, order, output=numpy.float64,
  499. mode=mode)
  500. else:
  501. npad = 0
  502. filtered = input
  503. mode = _ni_support._extend_mode_to_code(mode)
  504. matrix = numpy.asarray(matrix, dtype=numpy.float64)
  505. if matrix.ndim not in [1, 2] or matrix.shape[0] < 1:
  506. raise RuntimeError('no proper affine matrix provided')
  507. if (matrix.ndim == 2 and matrix.shape[1] == input.ndim + 1 and
  508. (matrix.shape[0] in [input.ndim, input.ndim + 1])):
  509. if matrix.shape[0] == input.ndim + 1:
  510. exptd = [0] * input.ndim + [1]
  511. if not numpy.all(matrix[input.ndim] == exptd):
  512. msg = ('Expected homogeneous transformation matrix with '
  513. 'shape %s for image shape %s, but bottom row was '
  514. 'not equal to %s' % (matrix.shape, input.shape, exptd))
  515. raise ValueError(msg)
  516. # assume input is homogeneous coordinate transformation matrix
  517. offset = matrix[:input.ndim, input.ndim]
  518. matrix = matrix[:input.ndim, :input.ndim]
  519. if matrix.shape[0] != input.ndim:
  520. raise RuntimeError('affine matrix has wrong number of rows')
  521. if matrix.ndim == 2 and matrix.shape[1] != output.ndim:
  522. raise RuntimeError('affine matrix has wrong number of columns')
  523. if not matrix.flags.contiguous:
  524. matrix = matrix.copy()
  525. offset = _ni_support._normalize_sequence(offset, input.ndim)
  526. offset = numpy.asarray(offset, dtype=numpy.float64)
  527. if offset.ndim != 1 or offset.shape[0] < 1:
  528. raise RuntimeError('no proper offset provided')
  529. if not offset.flags.contiguous:
  530. offset = offset.copy()
  531. if matrix.ndim == 1:
  532. warnings.warn(
  533. "The behavior of affine_transform with a 1-D "
  534. "array supplied for the matrix parameter has changed in "
  535. "SciPy 0.18.0."
  536. )
  537. _nd_image.zoom_shift(filtered, matrix, offset/matrix, output, order,
  538. mode, cval, npad, False)
  539. else:
  540. _nd_image.geometric_transform(filtered, None, None, matrix, offset,
  541. output, order, mode, cval, npad, None,
  542. None)
  543. return output
  544. @docfiller
  545. def shift(input, shift, output=None, order=3, mode='constant', cval=0.0,
  546. prefilter=True):
  547. """
  548. Shift an array.
  549. The array is shifted using spline interpolation of the requested order.
  550. Points outside the boundaries of the input are filled according to the
  551. given mode.
  552. Parameters
  553. ----------
  554. %(input)s
  555. shift : float or sequence
  556. The shift along the axes. If a float, `shift` is the same for each
  557. axis. If a sequence, `shift` should contain one value for each axis.
  558. %(output)s
  559. order : int, optional
  560. The order of the spline interpolation, default is 3.
  561. The order has to be in the range 0-5.
  562. %(mode_interp_constant)s
  563. %(cval)s
  564. %(prefilter)s
  565. Returns
  566. -------
  567. shift : ndarray
  568. The shifted input.
  569. Notes
  570. -----
  571. For complex-valued `input`, this function shifts the real and imaginary
  572. components independently.
  573. .. versionadded:: 1.6.0
  574. Complex-valued support added.
  575. """
  576. if order < 0 or order > 5:
  577. raise RuntimeError('spline order not supported')
  578. input = numpy.asarray(input)
  579. if input.ndim < 1:
  580. raise RuntimeError('input and output rank must be > 0')
  581. complex_output = numpy.iscomplexobj(input)
  582. output = _ni_support._get_output(output, input,
  583. complex_output=complex_output)
  584. if complex_output:
  585. # import under different name to avoid confusion with shift parameter
  586. from scipy.ndimage._interpolation import shift as _shift
  587. kwargs = dict(order=order, mode=mode, prefilter=prefilter)
  588. _shift(input.real, shift, output=output.real, cval=numpy.real(cval),
  589. **kwargs)
  590. _shift(input.imag, shift, output=output.imag, cval=numpy.imag(cval),
  591. **kwargs)
  592. return output
  593. if prefilter and order > 1:
  594. padded, npad = _prepad_for_spline_filter(input, mode, cval)
  595. filtered = spline_filter(padded, order, output=numpy.float64,
  596. mode=mode)
  597. else:
  598. npad = 0
  599. filtered = input
  600. mode = _ni_support._extend_mode_to_code(mode)
  601. shift = _ni_support._normalize_sequence(shift, input.ndim)
  602. shift = [-ii for ii in shift]
  603. shift = numpy.asarray(shift, dtype=numpy.float64)
  604. if not shift.flags.contiguous:
  605. shift = shift.copy()
  606. _nd_image.zoom_shift(filtered, None, shift, output, order, mode, cval,
  607. npad, False)
  608. return output
  609. @docfiller
  610. def zoom(input, zoom, output=None, order=3, mode='constant', cval=0.0,
  611. prefilter=True, *, grid_mode=False):
  612. """
  613. Zoom an array.
  614. The array is zoomed using spline interpolation of the requested order.
  615. Parameters
  616. ----------
  617. %(input)s
  618. zoom : float or sequence
  619. The zoom factor along the axes. If a float, `zoom` is the same for each
  620. axis. If a sequence, `zoom` should contain one value for each axis.
  621. %(output)s
  622. order : int, optional
  623. The order of the spline interpolation, default is 3.
  624. The order has to be in the range 0-5.
  625. %(mode_interp_constant)s
  626. %(cval)s
  627. %(prefilter)s
  628. grid_mode : bool, optional
  629. If False, the distance from the pixel centers is zoomed. Otherwise, the
  630. distance including the full pixel extent is used. For example, a 1d
  631. signal of length 5 is considered to have length 4 when `grid_mode` is
  632. False, but length 5 when `grid_mode` is True. See the following
  633. visual illustration:
  634. .. code-block:: text
  635. | pixel 1 | pixel 2 | pixel 3 | pixel 4 | pixel 5 |
  636. |<-------------------------------------->|
  637. vs.
  638. |<----------------------------------------------->|
  639. The starting point of the arrow in the diagram above corresponds to
  640. coordinate location 0 in each mode.
  641. Returns
  642. -------
  643. zoom : ndarray
  644. The zoomed input.
  645. Notes
  646. -----
  647. For complex-valued `input`, this function zooms the real and imaginary
  648. components independently.
  649. .. versionadded:: 1.6.0
  650. Complex-valued support added.
  651. Examples
  652. --------
  653. >>> from scipy import ndimage, datasets
  654. >>> import matplotlib.pyplot as plt
  655. >>> fig = plt.figure()
  656. >>> ax1 = fig.add_subplot(121) # left side
  657. >>> ax2 = fig.add_subplot(122) # right side
  658. >>> ascent = datasets.ascent()
  659. >>> result = ndimage.zoom(ascent, 3.0)
  660. >>> ax1.imshow(ascent, vmin=0, vmax=255)
  661. >>> ax2.imshow(result, vmin=0, vmax=255)
  662. >>> plt.show()
  663. >>> print(ascent.shape)
  664. (512, 512)
  665. >>> print(result.shape)
  666. (1536, 1536)
  667. """
  668. if order < 0 or order > 5:
  669. raise RuntimeError('spline order not supported')
  670. input = numpy.asarray(input)
  671. if input.ndim < 1:
  672. raise RuntimeError('input and output rank must be > 0')
  673. zoom = _ni_support._normalize_sequence(zoom, input.ndim)
  674. output_shape = tuple(
  675. [int(round(ii * jj)) for ii, jj in zip(input.shape, zoom)])
  676. complex_output = numpy.iscomplexobj(input)
  677. output = _ni_support._get_output(output, input, shape=output_shape,
  678. complex_output=complex_output)
  679. if complex_output:
  680. # import under different name to avoid confusion with zoom parameter
  681. from scipy.ndimage._interpolation import zoom as _zoom
  682. kwargs = dict(order=order, mode=mode, prefilter=prefilter)
  683. _zoom(input.real, zoom, output=output.real, cval=numpy.real(cval),
  684. **kwargs)
  685. _zoom(input.imag, zoom, output=output.imag, cval=numpy.imag(cval),
  686. **kwargs)
  687. return output
  688. if prefilter and order > 1:
  689. padded, npad = _prepad_for_spline_filter(input, mode, cval)
  690. filtered = spline_filter(padded, order, output=numpy.float64,
  691. mode=mode)
  692. else:
  693. npad = 0
  694. filtered = input
  695. if grid_mode:
  696. # warn about modes that may have surprising behavior
  697. suggest_mode = None
  698. if mode == 'constant':
  699. suggest_mode = 'grid-constant'
  700. elif mode == 'wrap':
  701. suggest_mode = 'grid-wrap'
  702. if suggest_mode is not None:
  703. warnings.warn(
  704. ("It is recommended to use mode = {} instead of {} when "
  705. "grid_mode is True.").format(suggest_mode, mode)
  706. )
  707. mode = _ni_support._extend_mode_to_code(mode)
  708. zoom_div = numpy.array(output_shape)
  709. zoom_nominator = numpy.array(input.shape)
  710. if not grid_mode:
  711. zoom_div -= 1
  712. zoom_nominator -= 1
  713. # Zooming to infinite values is unpredictable, so just choose
  714. # zoom factor 1 instead
  715. zoom = numpy.divide(zoom_nominator, zoom_div,
  716. out=numpy.ones_like(input.shape, dtype=numpy.float64),
  717. where=zoom_div != 0)
  718. zoom = numpy.ascontiguousarray(zoom)
  719. _nd_image.zoom_shift(filtered, zoom, None, output, order, mode, cval, npad,
  720. grid_mode)
  721. return output
  722. @docfiller
  723. def rotate(input, angle, axes=(1, 0), reshape=True, output=None, order=3,
  724. mode='constant', cval=0.0, prefilter=True):
  725. """
  726. Rotate an array.
  727. The array is rotated in the plane defined by the two axes given by the
  728. `axes` parameter using spline interpolation of the requested order.
  729. Parameters
  730. ----------
  731. %(input)s
  732. angle : float
  733. The rotation angle in degrees.
  734. axes : tuple of 2 ints, optional
  735. The two axes that define the plane of rotation. Default is the first
  736. two axes.
  737. reshape : bool, optional
  738. If `reshape` is true, the output shape is adapted so that the input
  739. array is contained completely in the output. Default is True.
  740. %(output)s
  741. order : int, optional
  742. The order of the spline interpolation, default is 3.
  743. The order has to be in the range 0-5.
  744. %(mode_interp_constant)s
  745. %(cval)s
  746. %(prefilter)s
  747. Returns
  748. -------
  749. rotate : ndarray
  750. The rotated input.
  751. Notes
  752. -----
  753. For complex-valued `input`, this function rotates the real and imaginary
  754. components independently.
  755. .. versionadded:: 1.6.0
  756. Complex-valued support added.
  757. Examples
  758. --------
  759. >>> from scipy import ndimage, datasets
  760. >>> import matplotlib.pyplot as plt
  761. >>> fig = plt.figure(figsize=(10, 3))
  762. >>> ax1, ax2, ax3 = fig.subplots(1, 3)
  763. >>> img = datasets.ascent()
  764. >>> img_45 = ndimage.rotate(img, 45, reshape=False)
  765. >>> full_img_45 = ndimage.rotate(img, 45, reshape=True)
  766. >>> ax1.imshow(img, cmap='gray')
  767. >>> ax1.set_axis_off()
  768. >>> ax2.imshow(img_45, cmap='gray')
  769. >>> ax2.set_axis_off()
  770. >>> ax3.imshow(full_img_45, cmap='gray')
  771. >>> ax3.set_axis_off()
  772. >>> fig.set_layout_engine('tight')
  773. >>> plt.show()
  774. >>> print(img.shape)
  775. (512, 512)
  776. >>> print(img_45.shape)
  777. (512, 512)
  778. >>> print(full_img_45.shape)
  779. (724, 724)
  780. """
  781. input_arr = numpy.asarray(input)
  782. ndim = input_arr.ndim
  783. if ndim < 2:
  784. raise ValueError('input array should be at least 2D')
  785. axes = list(axes)
  786. if len(axes) != 2:
  787. raise ValueError('axes should contain exactly two values')
  788. if not all([float(ax).is_integer() for ax in axes]):
  789. raise ValueError('axes should contain only integer values')
  790. if axes[0] < 0:
  791. axes[0] += ndim
  792. if axes[1] < 0:
  793. axes[1] += ndim
  794. if axes[0] < 0 or axes[1] < 0 or axes[0] >= ndim or axes[1] >= ndim:
  795. raise ValueError('invalid rotation plane specified')
  796. axes.sort()
  797. c, s = special.cosdg(angle), special.sindg(angle)
  798. rot_matrix = numpy.array([[c, s],
  799. [-s, c]])
  800. img_shape = numpy.asarray(input_arr.shape)
  801. in_plane_shape = img_shape[axes]
  802. if reshape:
  803. # Compute transformed input bounds
  804. iy, ix = in_plane_shape
  805. out_bounds = rot_matrix @ [[0, 0, iy, iy],
  806. [0, ix, 0, ix]]
  807. # Compute the shape of the transformed input plane
  808. out_plane_shape = (out_bounds.ptp(axis=1) + 0.5).astype(int)
  809. else:
  810. out_plane_shape = img_shape[axes]
  811. out_center = rot_matrix @ ((out_plane_shape - 1) / 2)
  812. in_center = (in_plane_shape - 1) / 2
  813. offset = in_center - out_center
  814. output_shape = img_shape
  815. output_shape[axes] = out_plane_shape
  816. output_shape = tuple(output_shape)
  817. complex_output = numpy.iscomplexobj(input_arr)
  818. output = _ni_support._get_output(output, input_arr, shape=output_shape,
  819. complex_output=complex_output)
  820. if ndim <= 2:
  821. affine_transform(input_arr, rot_matrix, offset, output_shape, output,
  822. order, mode, cval, prefilter)
  823. else:
  824. # If ndim > 2, the rotation is applied over all the planes
  825. # parallel to axes
  826. planes_coord = itertools.product(
  827. *[[slice(None)] if ax in axes else range(img_shape[ax])
  828. for ax in range(ndim)])
  829. out_plane_shape = tuple(out_plane_shape)
  830. for coordinates in planes_coord:
  831. ia = input_arr[coordinates]
  832. oa = output[coordinates]
  833. affine_transform(ia, rot_matrix, offset, out_plane_shape,
  834. oa, order, mode, cval, prefilter)
  835. return output