_interpolation.py 37 KB

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