_morphology.py 99 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633
  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 warnings
  31. import operator
  32. import numpy as np
  33. from . import _ni_support
  34. from . import _nd_image
  35. from . import _filters
  36. __all__ = ['iterate_structure', 'generate_binary_structure', 'binary_erosion',
  37. 'binary_dilation', 'binary_opening', 'binary_closing',
  38. 'binary_hit_or_miss', 'binary_propagation', 'binary_fill_holes',
  39. 'grey_erosion', 'grey_dilation', 'grey_opening', 'grey_closing',
  40. 'morphological_gradient', 'morphological_laplace', 'white_tophat',
  41. 'black_tophat', 'distance_transform_bf', 'distance_transform_cdt',
  42. 'distance_transform_edt']
  43. def _center_is_true(structure, origin):
  44. structure = np.asarray(structure)
  45. coor = tuple([oo + ss // 2 for ss, oo in zip(structure.shape,
  46. origin)])
  47. return bool(structure[coor])
  48. def iterate_structure(structure, iterations, origin=None):
  49. """
  50. Iterate a structure by dilating it with itself.
  51. Parameters
  52. ----------
  53. structure : array_like
  54. Structuring element (an array of bools, for example), to be dilated with
  55. itself.
  56. iterations : int
  57. number of dilations performed on the structure with itself
  58. origin : optional
  59. If origin is None, only the iterated structure is returned. If
  60. not, a tuple of the iterated structure and the modified origin is
  61. returned.
  62. Returns
  63. -------
  64. iterate_structure : ndarray of bools
  65. A new structuring element obtained by dilating `structure`
  66. (`iterations` - 1) times with itself.
  67. See Also
  68. --------
  69. generate_binary_structure
  70. Examples
  71. --------
  72. >>> from scipy import ndimage
  73. >>> struct = ndimage.generate_binary_structure(2, 1)
  74. >>> struct.astype(int)
  75. array([[0, 1, 0],
  76. [1, 1, 1],
  77. [0, 1, 0]])
  78. >>> ndimage.iterate_structure(struct, 2).astype(int)
  79. array([[0, 0, 1, 0, 0],
  80. [0, 1, 1, 1, 0],
  81. [1, 1, 1, 1, 1],
  82. [0, 1, 1, 1, 0],
  83. [0, 0, 1, 0, 0]])
  84. >>> ndimage.iterate_structure(struct, 3).astype(int)
  85. array([[0, 0, 0, 1, 0, 0, 0],
  86. [0, 0, 1, 1, 1, 0, 0],
  87. [0, 1, 1, 1, 1, 1, 0],
  88. [1, 1, 1, 1, 1, 1, 1],
  89. [0, 1, 1, 1, 1, 1, 0],
  90. [0, 0, 1, 1, 1, 0, 0],
  91. [0, 0, 0, 1, 0, 0, 0]])
  92. """
  93. structure = np.asarray(structure)
  94. if iterations < 2:
  95. return structure.copy()
  96. ni = iterations - 1
  97. shape = [ii + ni * (ii - 1) for ii in structure.shape]
  98. pos = [ni * (structure.shape[ii] // 2) for ii in range(len(shape))]
  99. slc = tuple(slice(pos[ii], pos[ii] + structure.shape[ii], None)
  100. for ii in range(len(shape)))
  101. out = np.zeros(shape, bool)
  102. out[slc] = structure != 0
  103. out = binary_dilation(out, structure, iterations=ni)
  104. if origin is None:
  105. return out
  106. else:
  107. origin = _ni_support._normalize_sequence(origin, structure.ndim)
  108. origin = [iterations * o for o in origin]
  109. return out, origin
  110. def generate_binary_structure(rank, connectivity):
  111. """
  112. Generate a binary structure for binary morphological operations.
  113. Parameters
  114. ----------
  115. rank : int
  116. Number of dimensions of the array to which the structuring element
  117. will be applied, as returned by `np.ndim`.
  118. connectivity : int
  119. `connectivity` determines which elements of the output array belong
  120. to the structure, i.e., are considered as neighbors of the central
  121. element. Elements up to a squared distance of `connectivity` from
  122. the center are considered neighbors. `connectivity` may range from 1
  123. (no diagonal elements are neighbors) to `rank` (all elements are
  124. neighbors).
  125. Returns
  126. -------
  127. output : ndarray of bools
  128. Structuring element which may be used for binary morphological
  129. operations, with `rank` dimensions and all dimensions equal to 3.
  130. See Also
  131. --------
  132. iterate_structure, binary_dilation, binary_erosion
  133. Notes
  134. -----
  135. `generate_binary_structure` can only create structuring elements with
  136. dimensions equal to 3, i.e., minimal dimensions. For larger structuring
  137. elements, that are useful e.g., for eroding large objects, one may either
  138. use `iterate_structure`, or create directly custom arrays with
  139. numpy functions such as `numpy.ones`.
  140. Examples
  141. --------
  142. >>> from scipy import ndimage
  143. >>> import numpy as np
  144. >>> struct = ndimage.generate_binary_structure(2, 1)
  145. >>> struct
  146. array([[False, True, False],
  147. [ True, True, True],
  148. [False, True, False]], dtype=bool)
  149. >>> a = np.zeros((5,5))
  150. >>> a[2, 2] = 1
  151. >>> a
  152. array([[ 0., 0., 0., 0., 0.],
  153. [ 0., 0., 0., 0., 0.],
  154. [ 0., 0., 1., 0., 0.],
  155. [ 0., 0., 0., 0., 0.],
  156. [ 0., 0., 0., 0., 0.]])
  157. >>> b = ndimage.binary_dilation(a, structure=struct).astype(a.dtype)
  158. >>> b
  159. array([[ 0., 0., 0., 0., 0.],
  160. [ 0., 0., 1., 0., 0.],
  161. [ 0., 1., 1., 1., 0.],
  162. [ 0., 0., 1., 0., 0.],
  163. [ 0., 0., 0., 0., 0.]])
  164. >>> ndimage.binary_dilation(b, structure=struct).astype(a.dtype)
  165. array([[ 0., 0., 1., 0., 0.],
  166. [ 0., 1., 1., 1., 0.],
  167. [ 1., 1., 1., 1., 1.],
  168. [ 0., 1., 1., 1., 0.],
  169. [ 0., 0., 1., 0., 0.]])
  170. >>> struct = ndimage.generate_binary_structure(2, 2)
  171. >>> struct
  172. array([[ True, True, True],
  173. [ True, True, True],
  174. [ True, True, True]], dtype=bool)
  175. >>> struct = ndimage.generate_binary_structure(3, 1)
  176. >>> struct # no diagonal elements
  177. array([[[False, False, False],
  178. [False, True, False],
  179. [False, False, False]],
  180. [[False, True, False],
  181. [ True, True, True],
  182. [False, True, False]],
  183. [[False, False, False],
  184. [False, True, False],
  185. [False, False, False]]], dtype=bool)
  186. """
  187. if connectivity < 1:
  188. connectivity = 1
  189. if rank < 1:
  190. return np.array(True, dtype=bool)
  191. output = np.fabs(np.indices([3] * rank) - 1)
  192. output = np.add.reduce(output, 0)
  193. return output <= connectivity
  194. def _binary_erosion(input, structure, iterations, mask, output,
  195. border_value, origin, invert, brute_force, axes):
  196. try:
  197. iterations = operator.index(iterations)
  198. except TypeError as e:
  199. raise TypeError('iterations parameter should be an integer') from e
  200. input = np.asarray(input)
  201. # The Cython code can't cope with broadcasted inputs
  202. if not input.flags.c_contiguous and not input.flags.f_contiguous:
  203. input = np.ascontiguousarray(input)
  204. ndim = input.ndim
  205. if np.iscomplexobj(input):
  206. raise TypeError('Complex type not supported')
  207. axes = _ni_support._check_axes(axes, input.ndim)
  208. num_axes = len(axes)
  209. if structure is None:
  210. structure = generate_binary_structure(num_axes, 1)
  211. else:
  212. structure = np.asarray(structure, dtype=bool)
  213. if ndim > num_axes:
  214. structure = _filters._expand_footprint(ndim, axes, structure,
  215. footprint_name="structure")
  216. if structure.ndim != input.ndim:
  217. raise RuntimeError('structure and input must have same dimensionality')
  218. if not structure.flags.contiguous:
  219. structure = structure.copy()
  220. if structure.size < 1:
  221. raise RuntimeError('structure must not be empty')
  222. if mask is not None:
  223. mask = np.asarray(mask)
  224. if mask.shape != input.shape:
  225. raise RuntimeError('mask and input must have equal sizes')
  226. origin = _ni_support._normalize_sequence(origin, num_axes)
  227. origin = _filters._expand_origin(ndim, axes, origin)
  228. cit = _center_is_true(structure, origin)
  229. if isinstance(output, np.ndarray):
  230. if np.iscomplexobj(output):
  231. raise TypeError('Complex output type not supported')
  232. else:
  233. output = bool
  234. output = _ni_support._get_output(output, input)
  235. temp_needed = np.may_share_memory(input, output)
  236. if temp_needed:
  237. # input and output arrays cannot share memory
  238. temp = output
  239. output = _ni_support._get_output(output.dtype, input)
  240. if iterations == 1:
  241. _nd_image.binary_erosion(input, structure, mask, output,
  242. border_value, origin, invert, cit, 0)
  243. elif cit and not brute_force:
  244. changed, coordinate_list = _nd_image.binary_erosion(
  245. input, structure, mask, output,
  246. border_value, origin, invert, cit, 1)
  247. structure = structure[tuple([slice(None, None, -1)] *
  248. structure.ndim)]
  249. for ii in range(len(origin)):
  250. origin[ii] = -origin[ii]
  251. if not structure.shape[ii] & 1:
  252. origin[ii] -= 1
  253. if mask is not None:
  254. mask = np.asarray(mask, dtype=np.int8)
  255. if not structure.flags.contiguous:
  256. structure = structure.copy()
  257. _nd_image.binary_erosion2(output, structure, mask, iterations - 1,
  258. origin, invert, coordinate_list)
  259. else:
  260. tmp_in = np.empty_like(input, dtype=bool)
  261. tmp_out = output
  262. if iterations >= 1 and not iterations & 1:
  263. tmp_in, tmp_out = tmp_out, tmp_in
  264. changed = _nd_image.binary_erosion(
  265. input, structure, mask, tmp_out,
  266. border_value, origin, invert, cit, 0)
  267. ii = 1
  268. while ii < iterations or (iterations < 1 and changed):
  269. tmp_in, tmp_out = tmp_out, tmp_in
  270. changed = _nd_image.binary_erosion(
  271. tmp_in, structure, mask, tmp_out,
  272. border_value, origin, invert, cit, 0)
  273. ii += 1
  274. if temp_needed:
  275. temp[...] = output
  276. output = temp
  277. return output
  278. def binary_erosion(input, structure=None, iterations=1, mask=None, output=None,
  279. border_value=0, origin=0, brute_force=False, *, axes=None):
  280. """
  281. Multidimensional binary erosion with a given structuring element.
  282. Binary erosion is a mathematical morphology operation used for image
  283. processing.
  284. Parameters
  285. ----------
  286. input : array_like
  287. Binary image to be eroded. Non-zero (True) elements form
  288. the subset to be eroded.
  289. structure : array_like, optional
  290. Structuring element used for the erosion. Non-zero elements are
  291. considered True. If no structuring element is provided, an element
  292. is generated with a square connectivity equal to one.
  293. iterations : int, optional
  294. The erosion is repeated `iterations` times (one, by default).
  295. If iterations is less than 1, the erosion is repeated until the
  296. result does not change anymore.
  297. mask : array_like, optional
  298. If a mask is given, only those elements with a True value at
  299. the corresponding mask element are modified at each iteration.
  300. output : ndarray, optional
  301. Array of the same shape as input, into which the output is placed.
  302. By default, a new array is created.
  303. border_value : int (cast to 0 or 1), optional
  304. Value at the border in the output array.
  305. origin : int or tuple of ints, optional
  306. Placement of the filter, by default 0.
  307. brute_force : boolean, optional
  308. Memory condition: if False, only the pixels whose value was changed in
  309. the last iteration are tracked as candidates to be updated (eroded) in
  310. the current iteration; if True all pixels are considered as candidates
  311. for erosion, regardless of what happened in the previous iteration.
  312. False by default.
  313. axes : tuple of int or None
  314. The axes over which to apply the filter. If None, `input` is filtered
  315. along all axes. If an `origin` tuple is provided, its length must match
  316. the number of axes.
  317. Returns
  318. -------
  319. binary_erosion : ndarray of bools
  320. Erosion of the input by the structuring element.
  321. See Also
  322. --------
  323. grey_erosion, binary_dilation, binary_closing, binary_opening,
  324. generate_binary_structure
  325. Notes
  326. -----
  327. Erosion [1]_ is a mathematical morphology operation [2]_ that uses a
  328. structuring element for shrinking the shapes in an image. The binary
  329. erosion of an image by a structuring element is the locus of the points
  330. where a superimposition of the structuring element centered on the point
  331. is entirely contained in the set of non-zero elements of the image.
  332. References
  333. ----------
  334. .. [1] https://en.wikipedia.org/wiki/Erosion_%28morphology%29
  335. .. [2] https://en.wikipedia.org/wiki/Mathematical_morphology
  336. Examples
  337. --------
  338. >>> from scipy import ndimage
  339. >>> import numpy as np
  340. >>> a = np.zeros((7,7), dtype=int)
  341. >>> a[1:6, 2:5] = 1
  342. >>> a
  343. array([[0, 0, 0, 0, 0, 0, 0],
  344. [0, 0, 1, 1, 1, 0, 0],
  345. [0, 0, 1, 1, 1, 0, 0],
  346. [0, 0, 1, 1, 1, 0, 0],
  347. [0, 0, 1, 1, 1, 0, 0],
  348. [0, 0, 1, 1, 1, 0, 0],
  349. [0, 0, 0, 0, 0, 0, 0]])
  350. >>> ndimage.binary_erosion(a).astype(a.dtype)
  351. array([[0, 0, 0, 0, 0, 0, 0],
  352. [0, 0, 0, 0, 0, 0, 0],
  353. [0, 0, 0, 1, 0, 0, 0],
  354. [0, 0, 0, 1, 0, 0, 0],
  355. [0, 0, 0, 1, 0, 0, 0],
  356. [0, 0, 0, 0, 0, 0, 0],
  357. [0, 0, 0, 0, 0, 0, 0]])
  358. >>> #Erosion removes objects smaller than the structure
  359. >>> ndimage.binary_erosion(a, structure=np.ones((5,5))).astype(a.dtype)
  360. array([[0, 0, 0, 0, 0, 0, 0],
  361. [0, 0, 0, 0, 0, 0, 0],
  362. [0, 0, 0, 0, 0, 0, 0],
  363. [0, 0, 0, 0, 0, 0, 0],
  364. [0, 0, 0, 0, 0, 0, 0],
  365. [0, 0, 0, 0, 0, 0, 0],
  366. [0, 0, 0, 0, 0, 0, 0]])
  367. """
  368. return _binary_erosion(input, structure, iterations, mask,
  369. output, border_value, origin, 0, brute_force, axes)
  370. def binary_dilation(input, structure=None, iterations=1, mask=None,
  371. output=None, border_value=0, origin=0,
  372. brute_force=False, *, axes=None):
  373. """
  374. Multidimensional binary dilation with the given structuring element.
  375. Parameters
  376. ----------
  377. input : array_like
  378. Binary array_like to be dilated. Non-zero (True) elements form
  379. the subset to be dilated.
  380. structure : array_like, optional
  381. Structuring element used for the dilation. Non-zero elements are
  382. considered True. If no structuring element is provided an element
  383. is generated with a square connectivity equal to one.
  384. iterations : int, optional
  385. The dilation is repeated `iterations` times (one, by default).
  386. If iterations is less than 1, the dilation is repeated until the
  387. result does not change anymore. Only an integer of iterations is
  388. accepted.
  389. mask : array_like, optional
  390. If a mask is given, only those elements with a True value at
  391. the corresponding mask element are modified at each iteration.
  392. output : ndarray, optional
  393. Array of the same shape as input, into which the output is placed.
  394. By default, a new array is created.
  395. border_value : int (cast to 0 or 1), optional
  396. Value at the border in the output array.
  397. origin : int or tuple of ints, optional
  398. Placement of the filter, by default 0.
  399. brute_force : boolean, optional
  400. Memory condition: if False, only the pixels whose value was changed in
  401. the last iteration are tracked as candidates to be updated (dilated)
  402. in the current iteration; if True all pixels are considered as
  403. candidates for dilation, regardless of what happened in the previous
  404. iteration. False by default.
  405. axes : tuple of int or None
  406. The axes over which to apply the filter. If None, `input` is filtered
  407. along all axes. If an `origin` tuple is provided, its length must match
  408. the number of axes.
  409. Returns
  410. -------
  411. binary_dilation : ndarray of bools
  412. Dilation of the input by the structuring element.
  413. See Also
  414. --------
  415. grey_dilation, binary_erosion, binary_closing, binary_opening,
  416. generate_binary_structure
  417. Notes
  418. -----
  419. Dilation [1]_ is a mathematical morphology operation [2]_ that uses a
  420. structuring element for expanding the shapes in an image. The binary
  421. dilation of an image by a structuring element is the locus of the points
  422. covered by the structuring element, when its center lies within the
  423. non-zero points of the image.
  424. References
  425. ----------
  426. .. [1] https://en.wikipedia.org/wiki/Dilation_%28morphology%29
  427. .. [2] https://en.wikipedia.org/wiki/Mathematical_morphology
  428. Examples
  429. --------
  430. >>> from scipy import ndimage
  431. >>> import numpy as np
  432. >>> a = np.zeros((5, 5))
  433. >>> a[2, 2] = 1
  434. >>> a
  435. array([[ 0., 0., 0., 0., 0.],
  436. [ 0., 0., 0., 0., 0.],
  437. [ 0., 0., 1., 0., 0.],
  438. [ 0., 0., 0., 0., 0.],
  439. [ 0., 0., 0., 0., 0.]])
  440. >>> ndimage.binary_dilation(a)
  441. array([[False, False, False, False, False],
  442. [False, False, True, False, False],
  443. [False, True, True, True, False],
  444. [False, False, True, False, False],
  445. [False, False, False, False, False]], dtype=bool)
  446. >>> ndimage.binary_dilation(a).astype(a.dtype)
  447. array([[ 0., 0., 0., 0., 0.],
  448. [ 0., 0., 1., 0., 0.],
  449. [ 0., 1., 1., 1., 0.],
  450. [ 0., 0., 1., 0., 0.],
  451. [ 0., 0., 0., 0., 0.]])
  452. >>> # 3x3 structuring element with connectivity 1, used by default
  453. >>> struct1 = ndimage.generate_binary_structure(2, 1)
  454. >>> struct1
  455. array([[False, True, False],
  456. [ True, True, True],
  457. [False, True, False]], dtype=bool)
  458. >>> # 3x3 structuring element with connectivity 2
  459. >>> struct2 = ndimage.generate_binary_structure(2, 2)
  460. >>> struct2
  461. array([[ True, True, True],
  462. [ True, True, True],
  463. [ True, True, True]], dtype=bool)
  464. >>> ndimage.binary_dilation(a, structure=struct1).astype(a.dtype)
  465. array([[ 0., 0., 0., 0., 0.],
  466. [ 0., 0., 1., 0., 0.],
  467. [ 0., 1., 1., 1., 0.],
  468. [ 0., 0., 1., 0., 0.],
  469. [ 0., 0., 0., 0., 0.]])
  470. >>> ndimage.binary_dilation(a, structure=struct2).astype(a.dtype)
  471. array([[ 0., 0., 0., 0., 0.],
  472. [ 0., 1., 1., 1., 0.],
  473. [ 0., 1., 1., 1., 0.],
  474. [ 0., 1., 1., 1., 0.],
  475. [ 0., 0., 0., 0., 0.]])
  476. >>> ndimage.binary_dilation(a, structure=struct1,\\
  477. ... iterations=2).astype(a.dtype)
  478. array([[ 0., 0., 1., 0., 0.],
  479. [ 0., 1., 1., 1., 0.],
  480. [ 1., 1., 1., 1., 1.],
  481. [ 0., 1., 1., 1., 0.],
  482. [ 0., 0., 1., 0., 0.]])
  483. """
  484. input = np.asarray(input)
  485. axes = _ni_support._check_axes(axes, input.ndim)
  486. num_axes = len(axes)
  487. if structure is None:
  488. structure = generate_binary_structure(num_axes, 1)
  489. origin = _ni_support._normalize_sequence(origin, num_axes)
  490. structure = np.asarray(structure)
  491. structure = structure[tuple([slice(None, None, -1)] *
  492. structure.ndim)]
  493. for ii in range(len(origin)):
  494. origin[ii] = -origin[ii]
  495. if not structure.shape[ii] & 1:
  496. origin[ii] -= 1
  497. return _binary_erosion(input, structure, iterations, mask,
  498. output, border_value, origin, 1, brute_force, axes)
  499. def binary_opening(input, structure=None, iterations=1, output=None,
  500. origin=0, mask=None, border_value=0, brute_force=False, *,
  501. axes=None):
  502. """
  503. Multidimensional binary opening with the given structuring element.
  504. The *opening* of an input image by a structuring element is the
  505. *dilation* of the *erosion* of the image by the structuring element.
  506. Parameters
  507. ----------
  508. input : array_like
  509. Binary array_like to be opened. Non-zero (True) elements form
  510. the subset to be opened.
  511. structure : array_like, optional
  512. Structuring element used for the opening. Non-zero elements are
  513. considered True. If no structuring element is provided an element
  514. is generated with a square connectivity equal to one (i.e., only
  515. nearest neighbors are connected to the center, diagonally-connected
  516. elements are not considered neighbors).
  517. iterations : int, optional
  518. The erosion step of the opening, then the dilation step are each
  519. repeated `iterations` times (one, by default). If `iterations` is
  520. less than 1, each operation is repeated until the result does
  521. not change anymore. Only an integer of iterations is accepted.
  522. output : ndarray, optional
  523. Array of the same shape as input, into which the output is placed.
  524. By default, a new array is created.
  525. origin : int or tuple of ints, optional
  526. Placement of the filter, by default 0.
  527. mask : array_like, optional
  528. If a mask is given, only those elements with a True value at
  529. the corresponding mask element are modified at each iteration.
  530. .. versionadded:: 1.1.0
  531. border_value : int (cast to 0 or 1), optional
  532. Value at the border in the output array.
  533. .. versionadded:: 1.1.0
  534. brute_force : boolean, optional
  535. Memory condition: if False, only the pixels whose value was changed in
  536. the last iteration are tracked as candidates to be updated in the
  537. current iteration; if true all pixels are considered as candidates for
  538. update, regardless of what happened in the previous iteration.
  539. False by default.
  540. .. versionadded:: 1.1.0
  541. axes : tuple of int or None
  542. The axes over which to apply the filter. If None, `input` is filtered
  543. along all axes. If an `origin` tuple is provided, its length must match
  544. the number of axes.
  545. Returns
  546. -------
  547. binary_opening : ndarray of bools
  548. Opening of the input by the structuring element.
  549. See Also
  550. --------
  551. grey_opening, binary_closing, binary_erosion, binary_dilation,
  552. generate_binary_structure
  553. Notes
  554. -----
  555. *Opening* [1]_ is a mathematical morphology operation [2]_ that
  556. consists in the succession of an erosion and a dilation of the
  557. input with the same structuring element. Opening, therefore, removes
  558. objects smaller than the structuring element.
  559. Together with *closing* (`binary_closing`), opening can be used for
  560. noise removal.
  561. References
  562. ----------
  563. .. [1] https://en.wikipedia.org/wiki/Opening_%28morphology%29
  564. .. [2] https://en.wikipedia.org/wiki/Mathematical_morphology
  565. Examples
  566. --------
  567. >>> from scipy import ndimage
  568. >>> import numpy as np
  569. >>> a = np.zeros((5,5), dtype=int)
  570. >>> a[1:4, 1:4] = 1; a[4, 4] = 1
  571. >>> a
  572. array([[0, 0, 0, 0, 0],
  573. [0, 1, 1, 1, 0],
  574. [0, 1, 1, 1, 0],
  575. [0, 1, 1, 1, 0],
  576. [0, 0, 0, 0, 1]])
  577. >>> # Opening removes small objects
  578. >>> ndimage.binary_opening(a, structure=np.ones((3,3))).astype(int)
  579. array([[0, 0, 0, 0, 0],
  580. [0, 1, 1, 1, 0],
  581. [0, 1, 1, 1, 0],
  582. [0, 1, 1, 1, 0],
  583. [0, 0, 0, 0, 0]])
  584. >>> # Opening can also smooth corners
  585. >>> ndimage.binary_opening(a).astype(int)
  586. array([[0, 0, 0, 0, 0],
  587. [0, 0, 1, 0, 0],
  588. [0, 1, 1, 1, 0],
  589. [0, 0, 1, 0, 0],
  590. [0, 0, 0, 0, 0]])
  591. >>> # Opening is the dilation of the erosion of the input
  592. >>> ndimage.binary_erosion(a).astype(int)
  593. array([[0, 0, 0, 0, 0],
  594. [0, 0, 0, 0, 0],
  595. [0, 0, 1, 0, 0],
  596. [0, 0, 0, 0, 0],
  597. [0, 0, 0, 0, 0]])
  598. >>> ndimage.binary_dilation(ndimage.binary_erosion(a)).astype(int)
  599. array([[0, 0, 0, 0, 0],
  600. [0, 0, 1, 0, 0],
  601. [0, 1, 1, 1, 0],
  602. [0, 0, 1, 0, 0],
  603. [0, 0, 0, 0, 0]])
  604. """
  605. input = np.asarray(input)
  606. axes = _ni_support._check_axes(axes, input.ndim)
  607. num_axes = len(axes)
  608. if structure is None:
  609. structure = generate_binary_structure(num_axes, 1)
  610. tmp = binary_erosion(input, structure, iterations, mask, None,
  611. border_value, origin, brute_force, axes=axes)
  612. return binary_dilation(tmp, structure, iterations, mask, output,
  613. border_value, origin, brute_force, axes=axes)
  614. def binary_closing(input, structure=None, iterations=1, output=None,
  615. origin=0, mask=None, border_value=0, brute_force=False, *,
  616. axes=None):
  617. """
  618. Multidimensional binary closing with the given structuring element.
  619. The *closing* of an input image by a structuring element is the
  620. *erosion* of the *dilation* of the image by the structuring element.
  621. Parameters
  622. ----------
  623. input : array_like
  624. Binary array_like to be closed. Non-zero (True) elements form
  625. the subset to be closed.
  626. structure : array_like, optional
  627. Structuring element used for the closing. Non-zero elements are
  628. considered True. If no structuring element is provided an element
  629. is generated with a square connectivity equal to one (i.e., only
  630. nearest neighbors are connected to the center, diagonally-connected
  631. elements are not considered neighbors).
  632. iterations : int, optional
  633. The dilation step of the closing, then the erosion step are each
  634. repeated `iterations` times (one, by default). If iterations is
  635. less than 1, each operations is repeated until the result does
  636. not change anymore. Only an integer of iterations is accepted.
  637. output : ndarray, optional
  638. Array of the same shape as input, into which the output is placed.
  639. By default, a new array is created.
  640. origin : int or tuple of ints, optional
  641. Placement of the filter, by default 0.
  642. mask : array_like, optional
  643. If a mask is given, only those elements with a True value at
  644. the corresponding mask element are modified at each iteration.
  645. .. versionadded:: 1.1.0
  646. border_value : int (cast to 0 or 1), optional
  647. Value at the border in the output array.
  648. .. versionadded:: 1.1.0
  649. brute_force : boolean, optional
  650. Memory condition: if False, only the pixels whose value was changed in
  651. the last iteration are tracked as candidates to be updated in the
  652. current iteration; if true al pixels are considered as candidates for
  653. update, regardless of what happened in the previous iteration.
  654. False by default.
  655. .. versionadded:: 1.1.0
  656. axes : tuple of int or None
  657. The axes over which to apply the filter. If None, `input` is filtered
  658. along all axes. If an `origin` tuple is provided, its length must match
  659. the number of axes.
  660. Returns
  661. -------
  662. binary_closing : ndarray of bools
  663. Closing of the input by the structuring element.
  664. See Also
  665. --------
  666. grey_closing, binary_opening, binary_dilation, binary_erosion,
  667. generate_binary_structure
  668. Notes
  669. -----
  670. *Closing* [1]_ is a mathematical morphology operation [2]_ that
  671. consists in the succession of a dilation and an erosion of the
  672. input with the same structuring element. Closing therefore fills
  673. holes smaller than the structuring element.
  674. Together with *opening* (`binary_opening`), closing can be used for
  675. noise removal.
  676. References
  677. ----------
  678. .. [1] https://en.wikipedia.org/wiki/Closing_%28morphology%29
  679. .. [2] https://en.wikipedia.org/wiki/Mathematical_morphology
  680. Examples
  681. --------
  682. >>> from scipy import ndimage
  683. >>> import numpy as np
  684. >>> a = np.zeros((5,5), dtype=int)
  685. >>> a[1:-1, 1:-1] = 1; a[2,2] = 0
  686. >>> a
  687. array([[0, 0, 0, 0, 0],
  688. [0, 1, 1, 1, 0],
  689. [0, 1, 0, 1, 0],
  690. [0, 1, 1, 1, 0],
  691. [0, 0, 0, 0, 0]])
  692. >>> # Closing removes small holes
  693. >>> ndimage.binary_closing(a).astype(int)
  694. array([[0, 0, 0, 0, 0],
  695. [0, 1, 1, 1, 0],
  696. [0, 1, 1, 1, 0],
  697. [0, 1, 1, 1, 0],
  698. [0, 0, 0, 0, 0]])
  699. >>> # Closing is the erosion of the dilation of the input
  700. >>> ndimage.binary_dilation(a).astype(int)
  701. array([[0, 1, 1, 1, 0],
  702. [1, 1, 1, 1, 1],
  703. [1, 1, 1, 1, 1],
  704. [1, 1, 1, 1, 1],
  705. [0, 1, 1, 1, 0]])
  706. >>> ndimage.binary_erosion(ndimage.binary_dilation(a)).astype(int)
  707. array([[0, 0, 0, 0, 0],
  708. [0, 1, 1, 1, 0],
  709. [0, 1, 1, 1, 0],
  710. [0, 1, 1, 1, 0],
  711. [0, 0, 0, 0, 0]])
  712. >>> a = np.zeros((7,7), dtype=int)
  713. >>> a[1:6, 2:5] = 1; a[1:3,3] = 0
  714. >>> a
  715. array([[0, 0, 0, 0, 0, 0, 0],
  716. [0, 0, 1, 0, 1, 0, 0],
  717. [0, 0, 1, 0, 1, 0, 0],
  718. [0, 0, 1, 1, 1, 0, 0],
  719. [0, 0, 1, 1, 1, 0, 0],
  720. [0, 0, 1, 1, 1, 0, 0],
  721. [0, 0, 0, 0, 0, 0, 0]])
  722. >>> # In addition to removing holes, closing can also
  723. >>> # coarsen boundaries with fine hollows.
  724. >>> ndimage.binary_closing(a).astype(int)
  725. array([[0, 0, 0, 0, 0, 0, 0],
  726. [0, 0, 1, 0, 1, 0, 0],
  727. [0, 0, 1, 1, 1, 0, 0],
  728. [0, 0, 1, 1, 1, 0, 0],
  729. [0, 0, 1, 1, 1, 0, 0],
  730. [0, 0, 1, 1, 1, 0, 0],
  731. [0, 0, 0, 0, 0, 0, 0]])
  732. >>> ndimage.binary_closing(a, structure=np.ones((2,2))).astype(int)
  733. array([[0, 0, 0, 0, 0, 0, 0],
  734. [0, 0, 1, 1, 1, 0, 0],
  735. [0, 0, 1, 1, 1, 0, 0],
  736. [0, 0, 1, 1, 1, 0, 0],
  737. [0, 0, 1, 1, 1, 0, 0],
  738. [0, 0, 1, 1, 1, 0, 0],
  739. [0, 0, 0, 0, 0, 0, 0]])
  740. """
  741. input = np.asarray(input)
  742. axes = _ni_support._check_axes(axes, input.ndim)
  743. num_axes = len(axes)
  744. if structure is None:
  745. structure = generate_binary_structure(num_axes, 1)
  746. tmp = binary_dilation(input, structure, iterations, mask, None,
  747. border_value, origin, brute_force, axes=axes)
  748. return binary_erosion(tmp, structure, iterations, mask, output,
  749. border_value, origin, brute_force, axes=axes)
  750. def binary_hit_or_miss(input, structure1=None, structure2=None,
  751. output=None, origin1=0, origin2=None, *, axes=None):
  752. """
  753. Multidimensional binary hit-or-miss transform.
  754. The hit-or-miss transform finds the locations of a given pattern
  755. inside the input image.
  756. Parameters
  757. ----------
  758. input : array_like (cast to booleans)
  759. Binary image where a pattern is to be detected.
  760. structure1 : array_like (cast to booleans), optional
  761. Part of the structuring element to be fitted to the foreground
  762. (non-zero elements) of `input`. If no value is provided, a
  763. structure of square connectivity 1 is chosen.
  764. structure2 : array_like (cast to booleans), optional
  765. Second part of the structuring element that has to miss completely
  766. the foreground. If no value is provided, the complementary of
  767. `structure1` is taken.
  768. output : ndarray, optional
  769. Array of the same shape as input, into which the output is placed.
  770. By default, a new array is created.
  771. origin1 : int or tuple of ints, optional
  772. Placement of the first part of the structuring element `structure1`,
  773. by default 0 for a centered structure.
  774. origin2 : int or tuple of ints, optional
  775. Placement of the second part of the structuring element `structure2`,
  776. by default 0 for a centered structure. If a value is provided for
  777. `origin1` and not for `origin2`, then `origin2` is set to `origin1`.
  778. axes : tuple of int or None
  779. The axes over which to apply the filter. If None, `input` is filtered
  780. along all axes. If `origin1` or `origin2` tuples are provided, their
  781. length must match the number of axes.
  782. Returns
  783. -------
  784. binary_hit_or_miss : ndarray
  785. Hit-or-miss transform of `input` with the given structuring
  786. element (`structure1`, `structure2`).
  787. See Also
  788. --------
  789. binary_erosion
  790. References
  791. ----------
  792. .. [1] https://en.wikipedia.org/wiki/Hit-or-miss_transform
  793. Examples
  794. --------
  795. >>> from scipy import ndimage
  796. >>> import numpy as np
  797. >>> a = np.zeros((7,7), dtype=int)
  798. >>> a[1, 1] = 1; a[2:4, 2:4] = 1; a[4:6, 4:6] = 1
  799. >>> a
  800. array([[0, 0, 0, 0, 0, 0, 0],
  801. [0, 1, 0, 0, 0, 0, 0],
  802. [0, 0, 1, 1, 0, 0, 0],
  803. [0, 0, 1, 1, 0, 0, 0],
  804. [0, 0, 0, 0, 1, 1, 0],
  805. [0, 0, 0, 0, 1, 1, 0],
  806. [0, 0, 0, 0, 0, 0, 0]])
  807. >>> structure1 = np.array([[1, 0, 0], [0, 1, 1], [0, 1, 1]])
  808. >>> structure1
  809. array([[1, 0, 0],
  810. [0, 1, 1],
  811. [0, 1, 1]])
  812. >>> # Find the matches of structure1 in the array a
  813. >>> ndimage.binary_hit_or_miss(a, structure1=structure1).astype(int)
  814. array([[0, 0, 0, 0, 0, 0, 0],
  815. [0, 0, 0, 0, 0, 0, 0],
  816. [0, 0, 1, 0, 0, 0, 0],
  817. [0, 0, 0, 0, 0, 0, 0],
  818. [0, 0, 0, 0, 1, 0, 0],
  819. [0, 0, 0, 0, 0, 0, 0],
  820. [0, 0, 0, 0, 0, 0, 0]])
  821. >>> # Change the origin of the filter
  822. >>> # origin1=1 is equivalent to origin1=(1,1) here
  823. >>> ndimage.binary_hit_or_miss(a, structure1=structure1,\\
  824. ... origin1=1).astype(int)
  825. array([[0, 0, 0, 0, 0, 0, 0],
  826. [0, 0, 0, 0, 0, 0, 0],
  827. [0, 0, 0, 0, 0, 0, 0],
  828. [0, 0, 0, 1, 0, 0, 0],
  829. [0, 0, 0, 0, 0, 0, 0],
  830. [0, 0, 0, 0, 0, 1, 0],
  831. [0, 0, 0, 0, 0, 0, 0]])
  832. """
  833. input = np.asarray(input)
  834. axes = _ni_support._check_axes(axes, input.ndim)
  835. num_axes = len(axes)
  836. if structure1 is None:
  837. structure1 = generate_binary_structure(num_axes, 1)
  838. else:
  839. structure1 = np.asarray(structure1)
  840. if structure2 is None:
  841. structure2 = np.logical_not(structure1)
  842. origin1 = _ni_support._normalize_sequence(origin1, num_axes)
  843. if origin2 is None:
  844. origin2 = origin1
  845. else:
  846. origin2 = _ni_support._normalize_sequence(origin2, num_axes)
  847. tmp1 = _binary_erosion(input, structure1, 1, None, None, 0, origin1,
  848. 0, False, axes)
  849. inplace = isinstance(output, np.ndarray)
  850. result = _binary_erosion(input, structure2, 1, None, output, 0,
  851. origin2, 1, False, axes)
  852. if inplace:
  853. np.logical_not(output, output)
  854. np.logical_and(tmp1, output, output)
  855. else:
  856. np.logical_not(result, result)
  857. return np.logical_and(tmp1, result)
  858. def binary_propagation(input, structure=None, mask=None,
  859. output=None, border_value=0, origin=0, *, axes=None):
  860. """
  861. Multidimensional binary propagation with the given structuring element.
  862. Parameters
  863. ----------
  864. input : array_like
  865. Binary image to be propagated inside `mask`.
  866. structure : array_like, optional
  867. Structuring element used in the successive dilations. The output
  868. may depend on the structuring element, especially if `mask` has
  869. several connex components. If no structuring element is
  870. provided, an element is generated with a squared connectivity equal
  871. to one.
  872. mask : array_like, optional
  873. Binary mask defining the region into which `input` is allowed to
  874. propagate.
  875. output : ndarray, optional
  876. Array of the same shape as input, into which the output is placed.
  877. By default, a new array is created.
  878. border_value : int (cast to 0 or 1), optional
  879. Value at the border in the output array.
  880. origin : int or tuple of ints, optional
  881. Placement of the filter, by default 0.
  882. axes : tuple of int or None
  883. The axes over which to apply the filter. If None, `input` is filtered
  884. along all axes. If an `origin` tuple is provided, its length must match
  885. the number of axes.
  886. Returns
  887. -------
  888. binary_propagation : ndarray
  889. Binary propagation of `input` inside `mask`.
  890. Notes
  891. -----
  892. This function is functionally equivalent to calling binary_dilation
  893. with the number of iterations less than one: iterative dilation until
  894. the result does not change anymore.
  895. The succession of an erosion and propagation inside the original image
  896. can be used instead of an *opening* for deleting small objects while
  897. keeping the contours of larger objects untouched.
  898. References
  899. ----------
  900. .. [1] http://cmm.ensmp.fr/~serra/cours/pdf/en/ch6en.pdf, slide 15.
  901. .. [2] I.T. Young, J.J. Gerbrands, and L.J. van Vliet, "Fundamentals of
  902. image processing", 1998
  903. ftp://qiftp.tudelft.nl/DIPimage/docs/FIP2.3.pdf
  904. Examples
  905. --------
  906. >>> from scipy import ndimage
  907. >>> import numpy as np
  908. >>> input = np.zeros((8, 8), dtype=int)
  909. >>> input[2, 2] = 1
  910. >>> mask = np.zeros((8, 8), dtype=int)
  911. >>> mask[1:4, 1:4] = mask[4, 4] = mask[6:8, 6:8] = 1
  912. >>> input
  913. array([[0, 0, 0, 0, 0, 0, 0, 0],
  914. [0, 0, 0, 0, 0, 0, 0, 0],
  915. [0, 0, 1, 0, 0, 0, 0, 0],
  916. [0, 0, 0, 0, 0, 0, 0, 0],
  917. [0, 0, 0, 0, 0, 0, 0, 0],
  918. [0, 0, 0, 0, 0, 0, 0, 0],
  919. [0, 0, 0, 0, 0, 0, 0, 0],
  920. [0, 0, 0, 0, 0, 0, 0, 0]])
  921. >>> mask
  922. array([[0, 0, 0, 0, 0, 0, 0, 0],
  923. [0, 1, 1, 1, 0, 0, 0, 0],
  924. [0, 1, 1, 1, 0, 0, 0, 0],
  925. [0, 1, 1, 1, 0, 0, 0, 0],
  926. [0, 0, 0, 0, 1, 0, 0, 0],
  927. [0, 0, 0, 0, 0, 0, 0, 0],
  928. [0, 0, 0, 0, 0, 0, 1, 1],
  929. [0, 0, 0, 0, 0, 0, 1, 1]])
  930. >>> ndimage.binary_propagation(input, mask=mask).astype(int)
  931. array([[0, 0, 0, 0, 0, 0, 0, 0],
  932. [0, 1, 1, 1, 0, 0, 0, 0],
  933. [0, 1, 1, 1, 0, 0, 0, 0],
  934. [0, 1, 1, 1, 0, 0, 0, 0],
  935. [0, 0, 0, 0, 0, 0, 0, 0],
  936. [0, 0, 0, 0, 0, 0, 0, 0],
  937. [0, 0, 0, 0, 0, 0, 0, 0],
  938. [0, 0, 0, 0, 0, 0, 0, 0]])
  939. >>> ndimage.binary_propagation(input, mask=mask,\\
  940. ... structure=np.ones((3,3))).astype(int)
  941. array([[0, 0, 0, 0, 0, 0, 0, 0],
  942. [0, 1, 1, 1, 0, 0, 0, 0],
  943. [0, 1, 1, 1, 0, 0, 0, 0],
  944. [0, 1, 1, 1, 0, 0, 0, 0],
  945. [0, 0, 0, 0, 1, 0, 0, 0],
  946. [0, 0, 0, 0, 0, 0, 0, 0],
  947. [0, 0, 0, 0, 0, 0, 0, 0],
  948. [0, 0, 0, 0, 0, 0, 0, 0]])
  949. >>> # Comparison between opening and erosion+propagation
  950. >>> a = np.zeros((6,6), dtype=int)
  951. >>> a[2:5, 2:5] = 1; a[0, 0] = 1; a[5, 5] = 1
  952. >>> a
  953. array([[1, 0, 0, 0, 0, 0],
  954. [0, 0, 0, 0, 0, 0],
  955. [0, 0, 1, 1, 1, 0],
  956. [0, 0, 1, 1, 1, 0],
  957. [0, 0, 1, 1, 1, 0],
  958. [0, 0, 0, 0, 0, 1]])
  959. >>> ndimage.binary_opening(a).astype(int)
  960. array([[0, 0, 0, 0, 0, 0],
  961. [0, 0, 0, 0, 0, 0],
  962. [0, 0, 0, 1, 0, 0],
  963. [0, 0, 1, 1, 1, 0],
  964. [0, 0, 0, 1, 0, 0],
  965. [0, 0, 0, 0, 0, 0]])
  966. >>> b = ndimage.binary_erosion(a)
  967. >>> b.astype(int)
  968. array([[0, 0, 0, 0, 0, 0],
  969. [0, 0, 0, 0, 0, 0],
  970. [0, 0, 0, 0, 0, 0],
  971. [0, 0, 0, 1, 0, 0],
  972. [0, 0, 0, 0, 0, 0],
  973. [0, 0, 0, 0, 0, 0]])
  974. >>> ndimage.binary_propagation(b, mask=a).astype(int)
  975. array([[0, 0, 0, 0, 0, 0],
  976. [0, 0, 0, 0, 0, 0],
  977. [0, 0, 1, 1, 1, 0],
  978. [0, 0, 1, 1, 1, 0],
  979. [0, 0, 1, 1, 1, 0],
  980. [0, 0, 0, 0, 0, 0]])
  981. """
  982. return binary_dilation(input, structure, -1, mask, output,
  983. border_value, origin, axes=axes)
  984. def binary_fill_holes(input, structure=None, output=None, origin=0, *,
  985. axes=None):
  986. """
  987. Fill the holes in binary objects.
  988. Parameters
  989. ----------
  990. input : array_like
  991. N-D binary array with holes to be filled
  992. structure : array_like, optional
  993. Structuring element used in the computation; large-size elements
  994. make computations faster but may miss holes separated from the
  995. background by thin regions. The default element (with a square
  996. connectivity equal to one) yields the intuitive result where all
  997. holes in the input have been filled.
  998. output : ndarray, optional
  999. Array of the same shape as input, into which the output is placed.
  1000. By default, a new array is created.
  1001. origin : int, tuple of ints, optional
  1002. Position of the structuring element.
  1003. axes : tuple of int or None
  1004. The axes over which to apply the filter. If None, `input` is filtered
  1005. along all axes. If an `origin` tuple is provided, its length must match
  1006. the number of axes.
  1007. Returns
  1008. -------
  1009. out : ndarray
  1010. Transformation of the initial image `input` where holes have been
  1011. filled.
  1012. See Also
  1013. --------
  1014. binary_dilation, binary_propagation, label
  1015. Notes
  1016. -----
  1017. The algorithm used in this function consists in invading the complementary
  1018. of the shapes in `input` from the outer boundary of the image,
  1019. using binary dilations. Holes are not connected to the boundary and are
  1020. therefore not invaded. The result is the complementary subset of the
  1021. invaded region.
  1022. References
  1023. ----------
  1024. .. [1] https://en.wikipedia.org/wiki/Mathematical_morphology
  1025. Examples
  1026. --------
  1027. >>> from scipy import ndimage
  1028. >>> import numpy as np
  1029. >>> a = np.zeros((5, 5), dtype=int)
  1030. >>> a[1:4, 1:4] = 1
  1031. >>> a[2,2] = 0
  1032. >>> a
  1033. array([[0, 0, 0, 0, 0],
  1034. [0, 1, 1, 1, 0],
  1035. [0, 1, 0, 1, 0],
  1036. [0, 1, 1, 1, 0],
  1037. [0, 0, 0, 0, 0]])
  1038. >>> ndimage.binary_fill_holes(a).astype(int)
  1039. array([[0, 0, 0, 0, 0],
  1040. [0, 1, 1, 1, 0],
  1041. [0, 1, 1, 1, 0],
  1042. [0, 1, 1, 1, 0],
  1043. [0, 0, 0, 0, 0]])
  1044. >>> # Too big structuring element
  1045. >>> ndimage.binary_fill_holes(a, structure=np.ones((5,5))).astype(int)
  1046. array([[0, 0, 0, 0, 0],
  1047. [0, 1, 1, 1, 0],
  1048. [0, 1, 0, 1, 0],
  1049. [0, 1, 1, 1, 0],
  1050. [0, 0, 0, 0, 0]])
  1051. """
  1052. input = np.asarray(input)
  1053. mask = np.logical_not(input)
  1054. tmp = np.zeros(mask.shape, bool)
  1055. inplace = isinstance(output, np.ndarray)
  1056. if inplace:
  1057. binary_dilation(tmp, structure, -1, mask, output, 1, origin, axes=axes)
  1058. np.logical_not(output, output)
  1059. else:
  1060. output = binary_dilation(tmp, structure, -1, mask, None, 1,
  1061. origin, axes=axes)
  1062. np.logical_not(output, output)
  1063. return output
  1064. def grey_erosion(input, size=None, footprint=None, structure=None,
  1065. output=None, mode="reflect", cval=0.0, origin=0, *,
  1066. axes=None):
  1067. """
  1068. Calculate a greyscale erosion, using either a structuring element,
  1069. or a footprint corresponding to a flat structuring element.
  1070. Grayscale erosion is a mathematical morphology operation. For the
  1071. simple case of a full and flat structuring element, it can be viewed
  1072. as a minimum filter over a sliding window.
  1073. Parameters
  1074. ----------
  1075. input : array_like
  1076. Array over which the grayscale erosion is to be computed.
  1077. size : tuple of ints
  1078. Shape of a flat and full structuring element used for the grayscale
  1079. erosion. Optional if `footprint` or `structure` is provided.
  1080. footprint : array of ints, optional
  1081. Positions of non-infinite elements of a flat structuring element
  1082. used for the grayscale erosion. Non-zero values give the set of
  1083. neighbors of the center over which the minimum is chosen.
  1084. structure : array of ints, optional
  1085. Structuring element used for the grayscale erosion. `structure`
  1086. may be a non-flat structuring element. The `structure` array applies a
  1087. subtractive offset for each pixel in the neighborhood.
  1088. output : array, optional
  1089. An array used for storing the output of the erosion may be provided.
  1090. mode : {'reflect','constant','nearest','mirror', 'wrap'}, optional
  1091. The `mode` parameter determines how the array borders are
  1092. handled, where `cval` is the value when mode is equal to
  1093. 'constant'. Default is 'reflect'
  1094. cval : scalar, optional
  1095. Value to fill past edges of input if `mode` is 'constant'. Default
  1096. is 0.0.
  1097. origin : scalar, optional
  1098. The `origin` parameter controls the placement of the filter.
  1099. Default 0
  1100. axes : tuple of int or None
  1101. The axes over which to apply the filter. If None, `input` is filtered
  1102. along all axes. If an `origin` tuple is provided, its length must match
  1103. the number of axes.
  1104. Returns
  1105. -------
  1106. output : ndarray
  1107. Grayscale erosion of `input`.
  1108. See Also
  1109. --------
  1110. binary_erosion, grey_dilation, grey_opening, grey_closing
  1111. generate_binary_structure, minimum_filter
  1112. Notes
  1113. -----
  1114. The grayscale erosion of an image input by a structuring element s defined
  1115. over a domain E is given by:
  1116. (input+s)(x) = min {input(y) - s(x-y), for y in E}
  1117. In particular, for structuring elements defined as
  1118. s(y) = 0 for y in E, the grayscale erosion computes the minimum of the
  1119. input image inside a sliding window defined by E.
  1120. Grayscale erosion [1]_ is a *mathematical morphology* operation [2]_.
  1121. References
  1122. ----------
  1123. .. [1] https://en.wikipedia.org/wiki/Erosion_%28morphology%29
  1124. .. [2] https://en.wikipedia.org/wiki/Mathematical_morphology
  1125. Examples
  1126. --------
  1127. >>> from scipy import ndimage
  1128. >>> import numpy as np
  1129. >>> a = np.zeros((7,7), dtype=int)
  1130. >>> a[1:6, 1:6] = 3
  1131. >>> a[4,4] = 2; a[2,3] = 1
  1132. >>> a
  1133. array([[0, 0, 0, 0, 0, 0, 0],
  1134. [0, 3, 3, 3, 3, 3, 0],
  1135. [0, 3, 3, 1, 3, 3, 0],
  1136. [0, 3, 3, 3, 3, 3, 0],
  1137. [0, 3, 3, 3, 2, 3, 0],
  1138. [0, 3, 3, 3, 3, 3, 0],
  1139. [0, 0, 0, 0, 0, 0, 0]])
  1140. >>> ndimage.grey_erosion(a, size=(3,3))
  1141. array([[0, 0, 0, 0, 0, 0, 0],
  1142. [0, 0, 0, 0, 0, 0, 0],
  1143. [0, 0, 1, 1, 1, 0, 0],
  1144. [0, 0, 1, 1, 1, 0, 0],
  1145. [0, 0, 3, 2, 2, 0, 0],
  1146. [0, 0, 0, 0, 0, 0, 0],
  1147. [0, 0, 0, 0, 0, 0, 0]])
  1148. >>> footprint = ndimage.generate_binary_structure(2, 1)
  1149. >>> footprint
  1150. array([[False, True, False],
  1151. [ True, True, True],
  1152. [False, True, False]], dtype=bool)
  1153. >>> # Diagonally-connected elements are not considered neighbors
  1154. >>> ndimage.grey_erosion(a, footprint=footprint)
  1155. array([[0, 0, 0, 0, 0, 0, 0],
  1156. [0, 0, 0, 0, 0, 0, 0],
  1157. [0, 0, 1, 1, 1, 0, 0],
  1158. [0, 0, 3, 1, 2, 0, 0],
  1159. [0, 0, 3, 2, 2, 0, 0],
  1160. [0, 0, 0, 0, 0, 0, 0],
  1161. [0, 0, 0, 0, 0, 0, 0]])
  1162. """
  1163. if size is None and footprint is None and structure is None:
  1164. raise ValueError("size, footprint, or structure must be specified")
  1165. return _filters._min_or_max_filter(input, size, footprint, structure,
  1166. output, mode, cval, origin, 1,
  1167. axes=axes)
  1168. def grey_dilation(input, size=None, footprint=None, structure=None,
  1169. output=None, mode="reflect", cval=0.0, origin=0, *,
  1170. axes=None):
  1171. """
  1172. Calculate a greyscale dilation, using either a structuring element,
  1173. or a footprint corresponding to a flat structuring element.
  1174. Grayscale dilation is a mathematical morphology operation. For the
  1175. simple case of a full and flat structuring element, it can be viewed
  1176. as a maximum filter over a sliding window.
  1177. Parameters
  1178. ----------
  1179. input : array_like
  1180. Array over which the grayscale dilation is to be computed.
  1181. size : tuple of ints
  1182. Shape of a flat and full structuring element used for the grayscale
  1183. dilation. Optional if `footprint` or `structure` is provided.
  1184. footprint : array of ints, optional
  1185. Positions of non-infinite elements of a flat structuring element
  1186. used for the grayscale dilation. Non-zero values give the set of
  1187. neighbors of the center over which the maximum is chosen.
  1188. structure : array of ints, optional
  1189. Structuring element used for the grayscale dilation. `structure`
  1190. may be a non-flat structuring element. The `structure` array applies an
  1191. additive offset for each pixel in the neighborhood.
  1192. output : array, optional
  1193. An array used for storing the output of the dilation may be provided.
  1194. mode : {'reflect','constant','nearest','mirror', 'wrap'}, optional
  1195. The `mode` parameter determines how the array borders are
  1196. handled, where `cval` is the value when mode is equal to
  1197. 'constant'. Default is 'reflect'
  1198. cval : scalar, optional
  1199. Value to fill past edges of input if `mode` is 'constant'. Default
  1200. is 0.0.
  1201. origin : scalar, optional
  1202. The `origin` parameter controls the placement of the filter.
  1203. Default 0
  1204. axes : tuple of int or None
  1205. The axes over which to apply the filter. If None, `input` is filtered
  1206. along all axes. If an `origin` tuple is provided, its length must match
  1207. the number of axes.
  1208. Returns
  1209. -------
  1210. grey_dilation : ndarray
  1211. Grayscale dilation of `input`.
  1212. See Also
  1213. --------
  1214. binary_dilation, grey_erosion, grey_closing, grey_opening
  1215. generate_binary_structure, maximum_filter
  1216. Notes
  1217. -----
  1218. The grayscale dilation of an image input by a structuring element s defined
  1219. over a domain E is given by:
  1220. (input+s)(x) = max {input(y) + s(x-y), for y in E}
  1221. In particular, for structuring elements defined as
  1222. s(y) = 0 for y in E, the grayscale dilation computes the maximum of the
  1223. input image inside a sliding window defined by E.
  1224. Grayscale dilation [1]_ is a *mathematical morphology* operation [2]_.
  1225. References
  1226. ----------
  1227. .. [1] https://en.wikipedia.org/wiki/Dilation_%28morphology%29
  1228. .. [2] https://en.wikipedia.org/wiki/Mathematical_morphology
  1229. Examples
  1230. --------
  1231. >>> from scipy import ndimage
  1232. >>> import numpy as np
  1233. >>> a = np.zeros((7,7), dtype=int)
  1234. >>> a[2:5, 2:5] = 1
  1235. >>> a[4,4] = 2; a[2,3] = 3
  1236. >>> a
  1237. array([[0, 0, 0, 0, 0, 0, 0],
  1238. [0, 0, 0, 0, 0, 0, 0],
  1239. [0, 0, 1, 3, 1, 0, 0],
  1240. [0, 0, 1, 1, 1, 0, 0],
  1241. [0, 0, 1, 1, 2, 0, 0],
  1242. [0, 0, 0, 0, 0, 0, 0],
  1243. [0, 0, 0, 0, 0, 0, 0]])
  1244. >>> ndimage.grey_dilation(a, size=(3,3))
  1245. array([[0, 0, 0, 0, 0, 0, 0],
  1246. [0, 1, 3, 3, 3, 1, 0],
  1247. [0, 1, 3, 3, 3, 1, 0],
  1248. [0, 1, 3, 3, 3, 2, 0],
  1249. [0, 1, 1, 2, 2, 2, 0],
  1250. [0, 1, 1, 2, 2, 2, 0],
  1251. [0, 0, 0, 0, 0, 0, 0]])
  1252. >>> ndimage.grey_dilation(a, footprint=np.ones((3,3)))
  1253. array([[0, 0, 0, 0, 0, 0, 0],
  1254. [0, 1, 3, 3, 3, 1, 0],
  1255. [0, 1, 3, 3, 3, 1, 0],
  1256. [0, 1, 3, 3, 3, 2, 0],
  1257. [0, 1, 1, 2, 2, 2, 0],
  1258. [0, 1, 1, 2, 2, 2, 0],
  1259. [0, 0, 0, 0, 0, 0, 0]])
  1260. >>> s = ndimage.generate_binary_structure(2,1)
  1261. >>> s
  1262. array([[False, True, False],
  1263. [ True, True, True],
  1264. [False, True, False]], dtype=bool)
  1265. >>> ndimage.grey_dilation(a, footprint=s)
  1266. array([[0, 0, 0, 0, 0, 0, 0],
  1267. [0, 0, 1, 3, 1, 0, 0],
  1268. [0, 1, 3, 3, 3, 1, 0],
  1269. [0, 1, 1, 3, 2, 1, 0],
  1270. [0, 1, 1, 2, 2, 2, 0],
  1271. [0, 0, 1, 1, 2, 0, 0],
  1272. [0, 0, 0, 0, 0, 0, 0]])
  1273. >>> ndimage.grey_dilation(a, size=(3,3), structure=np.ones((3,3)))
  1274. array([[1, 1, 1, 1, 1, 1, 1],
  1275. [1, 2, 4, 4, 4, 2, 1],
  1276. [1, 2, 4, 4, 4, 2, 1],
  1277. [1, 2, 4, 4, 4, 3, 1],
  1278. [1, 2, 2, 3, 3, 3, 1],
  1279. [1, 2, 2, 3, 3, 3, 1],
  1280. [1, 1, 1, 1, 1, 1, 1]])
  1281. """
  1282. if size is None and footprint is None and structure is None:
  1283. raise ValueError("size, footprint, or structure must be specified")
  1284. if structure is not None:
  1285. structure = np.asarray(structure)
  1286. structure = structure[tuple([slice(None, None, -1)] *
  1287. structure.ndim)]
  1288. if footprint is not None:
  1289. footprint = np.asarray(footprint)
  1290. footprint = footprint[tuple([slice(None, None, -1)] *
  1291. footprint.ndim)]
  1292. input = np.asarray(input)
  1293. axes = _ni_support._check_axes(axes, input.ndim)
  1294. origin = _ni_support._normalize_sequence(origin, len(axes))
  1295. for ii in range(len(origin)):
  1296. origin[ii] = -origin[ii]
  1297. if footprint is not None:
  1298. sz = footprint.shape[ii]
  1299. elif structure is not None:
  1300. sz = structure.shape[ii]
  1301. elif np.isscalar(size):
  1302. sz = size
  1303. else:
  1304. sz = size[ii]
  1305. if not sz & 1:
  1306. origin[ii] -= 1
  1307. return _filters._min_or_max_filter(input, size, footprint, structure,
  1308. output, mode, cval, origin, 0,
  1309. axes=axes)
  1310. def grey_opening(input, size=None, footprint=None, structure=None,
  1311. output=None, mode="reflect", cval=0.0, origin=0, *,
  1312. axes=None):
  1313. """
  1314. Multidimensional grayscale opening.
  1315. A grayscale opening consists in the succession of a grayscale erosion,
  1316. and a grayscale dilation.
  1317. Parameters
  1318. ----------
  1319. input : array_like
  1320. Array over which the grayscale opening is to be computed.
  1321. size : tuple of ints
  1322. Shape of a flat and full structuring element used for the grayscale
  1323. opening. Optional if `footprint` or `structure` is provided.
  1324. footprint : array of ints, optional
  1325. Positions of non-infinite elements of a flat structuring element
  1326. used for the grayscale opening.
  1327. structure : array of ints, optional
  1328. Structuring element used for the grayscale opening. `structure`
  1329. may be a non-flat structuring element. The `structure` array applies
  1330. offsets to the pixels in a neighborhood (the offset is additive during
  1331. dilation and subtractive during erosion).
  1332. output : array, optional
  1333. An array used for storing the output of the opening may be provided.
  1334. mode : {'reflect', 'constant', 'nearest', 'mirror', 'wrap'}, optional
  1335. The `mode` parameter determines how the array borders are
  1336. handled, where `cval` is the value when mode is equal to
  1337. 'constant'. Default is 'reflect'
  1338. cval : scalar, optional
  1339. Value to fill past edges of input if `mode` is 'constant'. Default
  1340. is 0.0.
  1341. origin : scalar, optional
  1342. The `origin` parameter controls the placement of the filter.
  1343. Default 0
  1344. axes : tuple of int or None
  1345. The axes over which to apply the filter. If None, `input` is filtered
  1346. along all axes. If an `origin` tuple is provided, its length must match
  1347. the number of axes.
  1348. Returns
  1349. -------
  1350. grey_opening : ndarray
  1351. Result of the grayscale opening of `input` with `structure`.
  1352. See Also
  1353. --------
  1354. binary_opening, grey_dilation, grey_erosion, grey_closing
  1355. generate_binary_structure
  1356. Notes
  1357. -----
  1358. The action of a grayscale opening with a flat structuring element amounts
  1359. to smoothen high local maxima, whereas binary opening erases small objects.
  1360. References
  1361. ----------
  1362. .. [1] https://en.wikipedia.org/wiki/Mathematical_morphology
  1363. Examples
  1364. --------
  1365. >>> from scipy import ndimage
  1366. >>> import numpy as np
  1367. >>> a = np.arange(36).reshape((6,6))
  1368. >>> a[3, 3] = 50
  1369. >>> a
  1370. array([[ 0, 1, 2, 3, 4, 5],
  1371. [ 6, 7, 8, 9, 10, 11],
  1372. [12, 13, 14, 15, 16, 17],
  1373. [18, 19, 20, 50, 22, 23],
  1374. [24, 25, 26, 27, 28, 29],
  1375. [30, 31, 32, 33, 34, 35]])
  1376. >>> ndimage.grey_opening(a, size=(3,3))
  1377. array([[ 0, 1, 2, 3, 4, 4],
  1378. [ 6, 7, 8, 9, 10, 10],
  1379. [12, 13, 14, 15, 16, 16],
  1380. [18, 19, 20, 22, 22, 22],
  1381. [24, 25, 26, 27, 28, 28],
  1382. [24, 25, 26, 27, 28, 28]])
  1383. >>> # Note that the local maximum a[3,3] has disappeared
  1384. """
  1385. if (size is not None) and (footprint is not None):
  1386. warnings.warn("ignoring size because footprint is set",
  1387. UserWarning, stacklevel=2)
  1388. tmp = grey_erosion(input, size, footprint, structure, None, mode,
  1389. cval, origin, axes=axes)
  1390. return grey_dilation(tmp, size, footprint, structure, output, mode,
  1391. cval, origin, axes=axes)
  1392. def grey_closing(input, size=None, footprint=None, structure=None,
  1393. output=None, mode="reflect", cval=0.0, origin=0, *,
  1394. axes=None):
  1395. """
  1396. Multidimensional grayscale closing.
  1397. A grayscale closing consists in the succession of a grayscale dilation,
  1398. and a grayscale erosion.
  1399. Parameters
  1400. ----------
  1401. input : array_like
  1402. Array over which the grayscale closing is to be computed.
  1403. size : tuple of ints
  1404. Shape of a flat and full structuring element used for the grayscale
  1405. closing. Optional if `footprint` or `structure` is provided.
  1406. footprint : array of ints, optional
  1407. Positions of non-infinite elements of a flat structuring element
  1408. used for the grayscale closing.
  1409. structure : array of ints, optional
  1410. Structuring element used for the grayscale closing. `structure`
  1411. may be a non-flat structuring element. The `structure` array applies
  1412. offsets to the pixels in a neighborhood (the offset is additive during
  1413. dilation and subtractive during erosion)
  1414. output : array, optional
  1415. An array used for storing the output of the closing may be provided.
  1416. mode : {'reflect', 'constant', 'nearest', 'mirror', 'wrap'}, optional
  1417. The `mode` parameter determines how the array borders are
  1418. handled, where `cval` is the value when mode is equal to
  1419. 'constant'. Default is 'reflect'
  1420. cval : scalar, optional
  1421. Value to fill past edges of input if `mode` is 'constant'. Default
  1422. is 0.0.
  1423. origin : scalar, optional
  1424. The `origin` parameter controls the placement of the filter.
  1425. Default 0
  1426. axes : tuple of int or None
  1427. The axes over which to apply the filter. If None, `input` is filtered
  1428. along all axes. If an `origin` tuple is provided, its length must match
  1429. the number of axes.
  1430. Returns
  1431. -------
  1432. grey_closing : ndarray
  1433. Result of the grayscale closing of `input` with `structure`.
  1434. See Also
  1435. --------
  1436. binary_closing, grey_dilation, grey_erosion, grey_opening,
  1437. generate_binary_structure
  1438. Notes
  1439. -----
  1440. The action of a grayscale closing with a flat structuring element amounts
  1441. to smoothen deep local minima, whereas binary closing fills small holes.
  1442. References
  1443. ----------
  1444. .. [1] https://en.wikipedia.org/wiki/Mathematical_morphology
  1445. Examples
  1446. --------
  1447. >>> from scipy import ndimage
  1448. >>> import numpy as np
  1449. >>> a = np.arange(36).reshape((6,6))
  1450. >>> a[3,3] = 0
  1451. >>> a
  1452. array([[ 0, 1, 2, 3, 4, 5],
  1453. [ 6, 7, 8, 9, 10, 11],
  1454. [12, 13, 14, 15, 16, 17],
  1455. [18, 19, 20, 0, 22, 23],
  1456. [24, 25, 26, 27, 28, 29],
  1457. [30, 31, 32, 33, 34, 35]])
  1458. >>> ndimage.grey_closing(a, size=(3,3))
  1459. array([[ 7, 7, 8, 9, 10, 11],
  1460. [ 7, 7, 8, 9, 10, 11],
  1461. [13, 13, 14, 15, 16, 17],
  1462. [19, 19, 20, 20, 22, 23],
  1463. [25, 25, 26, 27, 28, 29],
  1464. [31, 31, 32, 33, 34, 35]])
  1465. >>> # Note that the local minimum a[3,3] has disappeared
  1466. """
  1467. if (size is not None) and (footprint is not None):
  1468. warnings.warn("ignoring size because footprint is set",
  1469. UserWarning, stacklevel=2)
  1470. tmp = grey_dilation(input, size, footprint, structure, None, mode,
  1471. cval, origin, axes=axes)
  1472. return grey_erosion(tmp, size, footprint, structure, output, mode,
  1473. cval, origin, axes=axes)
  1474. def morphological_gradient(input, size=None, footprint=None, structure=None,
  1475. output=None, mode="reflect", cval=0.0, origin=0, *,
  1476. axes=None):
  1477. """
  1478. Multidimensional morphological gradient.
  1479. The morphological gradient is calculated as the difference between a
  1480. dilation and an erosion of the input with a given structuring element.
  1481. Parameters
  1482. ----------
  1483. input : array_like
  1484. Array over which to compute the morphlogical gradient.
  1485. size : tuple of ints
  1486. Shape of a flat and full structuring element used for the mathematical
  1487. morphology operations. Optional if `footprint` or `structure` is
  1488. provided. A larger `size` yields a more blurred gradient.
  1489. footprint : array of ints, optional
  1490. Positions of non-infinite elements of a flat structuring element
  1491. used for the morphology operations. Larger footprints
  1492. give a more blurred morphological gradient.
  1493. structure : array of ints, optional
  1494. Structuring element used for the morphology operations. `structure` may
  1495. be a non-flat structuring element. The `structure` array applies
  1496. offsets to the pixels in a neighborhood (the offset is additive during
  1497. dilation and subtractive during erosion)
  1498. output : array, optional
  1499. An array used for storing the output of the morphological gradient
  1500. may be provided.
  1501. mode : {'reflect', 'constant', 'nearest', 'mirror', 'wrap'}, optional
  1502. The `mode` parameter determines how the array borders are
  1503. handled, where `cval` is the value when mode is equal to
  1504. 'constant'. Default is 'reflect'
  1505. cval : scalar, optional
  1506. Value to fill past edges of input if `mode` is 'constant'. Default
  1507. is 0.0.
  1508. origin : scalar, optional
  1509. The `origin` parameter controls the placement of the filter.
  1510. Default 0
  1511. axes : tuple of int or None
  1512. The axes over which to apply the filter. If None, `input` is filtered
  1513. along all axes. If an `origin` tuple is provided, its length must match
  1514. the number of axes.
  1515. Returns
  1516. -------
  1517. morphological_gradient : ndarray
  1518. Morphological gradient of `input`.
  1519. See Also
  1520. --------
  1521. grey_dilation, grey_erosion, gaussian_gradient_magnitude
  1522. Notes
  1523. -----
  1524. For a flat structuring element, the morphological gradient
  1525. computed at a given point corresponds to the maximal difference
  1526. between elements of the input among the elements covered by the
  1527. structuring element centered on the point.
  1528. References
  1529. ----------
  1530. .. [1] https://en.wikipedia.org/wiki/Mathematical_morphology
  1531. Examples
  1532. --------
  1533. >>> from scipy import ndimage
  1534. >>> import numpy as np
  1535. >>> a = np.zeros((7,7), dtype=int)
  1536. >>> a[2:5, 2:5] = 1
  1537. >>> ndimage.morphological_gradient(a, size=(3,3))
  1538. array([[0, 0, 0, 0, 0, 0, 0],
  1539. [0, 1, 1, 1, 1, 1, 0],
  1540. [0, 1, 1, 1, 1, 1, 0],
  1541. [0, 1, 1, 0, 1, 1, 0],
  1542. [0, 1, 1, 1, 1, 1, 0],
  1543. [0, 1, 1, 1, 1, 1, 0],
  1544. [0, 0, 0, 0, 0, 0, 0]])
  1545. >>> # The morphological gradient is computed as the difference
  1546. >>> # between a dilation and an erosion
  1547. >>> ndimage.grey_dilation(a, size=(3,3)) -\\
  1548. ... ndimage.grey_erosion(a, size=(3,3))
  1549. array([[0, 0, 0, 0, 0, 0, 0],
  1550. [0, 1, 1, 1, 1, 1, 0],
  1551. [0, 1, 1, 1, 1, 1, 0],
  1552. [0, 1, 1, 0, 1, 1, 0],
  1553. [0, 1, 1, 1, 1, 1, 0],
  1554. [0, 1, 1, 1, 1, 1, 0],
  1555. [0, 0, 0, 0, 0, 0, 0]])
  1556. >>> a = np.zeros((7,7), dtype=int)
  1557. >>> a[2:5, 2:5] = 1
  1558. >>> a[4,4] = 2; a[2,3] = 3
  1559. >>> a
  1560. array([[0, 0, 0, 0, 0, 0, 0],
  1561. [0, 0, 0, 0, 0, 0, 0],
  1562. [0, 0, 1, 3, 1, 0, 0],
  1563. [0, 0, 1, 1, 1, 0, 0],
  1564. [0, 0, 1, 1, 2, 0, 0],
  1565. [0, 0, 0, 0, 0, 0, 0],
  1566. [0, 0, 0, 0, 0, 0, 0]])
  1567. >>> ndimage.morphological_gradient(a, size=(3,3))
  1568. array([[0, 0, 0, 0, 0, 0, 0],
  1569. [0, 1, 3, 3, 3, 1, 0],
  1570. [0, 1, 3, 3, 3, 1, 0],
  1571. [0, 1, 3, 2, 3, 2, 0],
  1572. [0, 1, 1, 2, 2, 2, 0],
  1573. [0, 1, 1, 2, 2, 2, 0],
  1574. [0, 0, 0, 0, 0, 0, 0]])
  1575. """
  1576. tmp = grey_dilation(input, size, footprint, structure, None, mode,
  1577. cval, origin, axes=axes)
  1578. if isinstance(output, np.ndarray):
  1579. grey_erosion(input, size, footprint, structure, output, mode,
  1580. cval, origin, axes=axes)
  1581. return np.subtract(tmp, output, output)
  1582. else:
  1583. return (tmp - grey_erosion(input, size, footprint, structure,
  1584. None, mode, cval, origin, axes=axes))
  1585. def morphological_laplace(input, size=None, footprint=None, structure=None,
  1586. output=None, mode="reflect", cval=0.0, origin=0, *,
  1587. axes=None):
  1588. """
  1589. Multidimensional morphological laplace.
  1590. Parameters
  1591. ----------
  1592. input : array_like
  1593. Input.
  1594. size : tuple of ints
  1595. Shape of a flat and full structuring element used for the mathematical
  1596. morphology operations. Optional if `footprint` or `structure` is
  1597. provided.
  1598. footprint : array of ints, optional
  1599. Positions of non-infinite elements of a flat structuring element
  1600. used for the morphology operations.
  1601. structure : array of ints, optional
  1602. Structuring element used for the morphology operations. `structure` may
  1603. be a non-flat structuring element. The `structure` array applies
  1604. offsets to the pixels in a neighborhood (the offset is additive during
  1605. dilation and subtractive during erosion)
  1606. output : ndarray, optional
  1607. An output array can optionally be provided.
  1608. mode : {'reflect','constant','nearest','mirror', 'wrap'}, optional
  1609. The mode parameter determines how the array borders are handled.
  1610. For 'constant' mode, values beyond borders are set to be `cval`.
  1611. Default is 'reflect'.
  1612. cval : scalar, optional
  1613. Value to fill past edges of input if mode is 'constant'.
  1614. Default is 0.0
  1615. origin : origin, optional
  1616. The origin parameter controls the placement of the filter.
  1617. axes : tuple of int or None
  1618. The axes over which to apply the filter. If None, `input` is filtered
  1619. along all axes. If an `origin` tuple is provided, its length must match
  1620. the number of axes.
  1621. Returns
  1622. -------
  1623. morphological_laplace : ndarray
  1624. Output
  1625. """
  1626. input = np.asarray(input)
  1627. tmp1 = grey_dilation(input, size, footprint, structure, None, mode,
  1628. cval, origin, axes=axes)
  1629. if isinstance(output, np.ndarray):
  1630. grey_erosion(input, size, footprint, structure, output, mode,
  1631. cval, origin, axes=axes)
  1632. np.add(tmp1, output, output)
  1633. np.subtract(output, input, output)
  1634. return np.subtract(output, input, output)
  1635. else:
  1636. tmp2 = grey_erosion(input, size, footprint, structure, None, mode,
  1637. cval, origin, axes=axes)
  1638. np.add(tmp1, tmp2, tmp2)
  1639. np.subtract(tmp2, input, tmp2)
  1640. np.subtract(tmp2, input, tmp2)
  1641. return tmp2
  1642. def white_tophat(input, size=None, footprint=None, structure=None,
  1643. output=None, mode="reflect", cval=0.0, origin=0, *,
  1644. axes=None):
  1645. """
  1646. Multidimensional white tophat filter.
  1647. Parameters
  1648. ----------
  1649. input : array_like
  1650. Input.
  1651. size : tuple of ints
  1652. Shape of a flat and full structuring element used for the filter.
  1653. Optional if `footprint` or `structure` is provided.
  1654. footprint : array of ints, optional
  1655. Positions of elements of a flat structuring element
  1656. used for the white tophat filter.
  1657. structure : array of ints, optional
  1658. Structuring element used for the filter. `structure` may be a non-flat
  1659. structuring element. The `structure` array applies offsets to the
  1660. pixels in a neighborhood (the offset is additive during dilation and
  1661. subtractive during erosion)
  1662. output : array, optional
  1663. An array used for storing the output of the filter may be provided.
  1664. mode : {'reflect', 'constant', 'nearest', 'mirror', 'wrap'}, optional
  1665. The `mode` parameter determines how the array borders are
  1666. handled, where `cval` is the value when mode is equal to
  1667. 'constant'. Default is 'reflect'
  1668. cval : scalar, optional
  1669. Value to fill past edges of input if `mode` is 'constant'.
  1670. Default is 0.0.
  1671. origin : scalar, optional
  1672. The `origin` parameter controls the placement of the filter.
  1673. Default is 0.
  1674. axes : tuple of int or None
  1675. The axes over which to apply the filter. If None, `input` is filtered
  1676. along all axes. If an `origin` tuple is provided, its length must match
  1677. the number of axes.
  1678. Returns
  1679. -------
  1680. output : ndarray
  1681. Result of the filter of `input` with `structure`.
  1682. See Also
  1683. --------
  1684. black_tophat
  1685. Examples
  1686. --------
  1687. Subtract gray background from a bright peak.
  1688. >>> from scipy.ndimage import generate_binary_structure, white_tophat
  1689. >>> import numpy as np
  1690. >>> square = generate_binary_structure(rank=2, connectivity=3)
  1691. >>> bright_on_gray = np.array([[2, 3, 3, 3, 2],
  1692. ... [3, 4, 5, 4, 3],
  1693. ... [3, 5, 9, 5, 3],
  1694. ... [3, 4, 5, 4, 3],
  1695. ... [2, 3, 3, 3, 2]])
  1696. >>> white_tophat(input=bright_on_gray, structure=square)
  1697. array([[0, 0, 0, 0, 0],
  1698. [0, 0, 1, 0, 0],
  1699. [0, 1, 5, 1, 0],
  1700. [0, 0, 1, 0, 0],
  1701. [0, 0, 0, 0, 0]])
  1702. """
  1703. input = np.asarray(input)
  1704. if (size is not None) and (footprint is not None):
  1705. warnings.warn("ignoring size because footprint is set",
  1706. UserWarning, stacklevel=2)
  1707. tmp = grey_erosion(input, size, footprint, structure, None, mode,
  1708. cval, origin, axes=axes)
  1709. tmp = grey_dilation(tmp, size, footprint, structure, output, mode,
  1710. cval, origin, axes=axes)
  1711. if tmp is None:
  1712. tmp = output
  1713. if input.dtype == np.bool_ and tmp.dtype == np.bool_:
  1714. np.bitwise_xor(input, tmp, out=tmp)
  1715. else:
  1716. np.subtract(input, tmp, out=tmp)
  1717. return tmp
  1718. def black_tophat(input, size=None, footprint=None, structure=None, output=None,
  1719. mode="reflect", cval=0.0, origin=0, *, axes=None):
  1720. """
  1721. Multidimensional black tophat filter.
  1722. Parameters
  1723. ----------
  1724. input : array_like
  1725. Input.
  1726. size : tuple of ints, optional
  1727. Shape of a flat and full structuring element used for the filter.
  1728. Optional if `footprint` or `structure` is provided.
  1729. footprint : array of ints, optional
  1730. Positions of non-infinite elements of a flat structuring element
  1731. used for the black tophat filter.
  1732. structure : array of ints, optional
  1733. Structuring element used for the filter. `structure` may be a non-flat
  1734. structuring element. The `structure` array applies offsets to the
  1735. pixels in a neighborhood (the offset is additive during dilation and
  1736. subtractive during erosion)
  1737. output : array, optional
  1738. An array used for storing the output of the filter may be provided.
  1739. mode : {'reflect', 'constant', 'nearest', 'mirror', 'wrap'}, optional
  1740. The `mode` parameter determines how the array borders are
  1741. handled, where `cval` is the value when mode is equal to
  1742. 'constant'. Default is 'reflect'
  1743. cval : scalar, optional
  1744. Value to fill past edges of input if `mode` is 'constant'. Default
  1745. is 0.0.
  1746. origin : scalar, optional
  1747. The `origin` parameter controls the placement of the filter.
  1748. Default 0
  1749. axes : tuple of int or None
  1750. The axes over which to apply the filter. If None, `input` is filtered
  1751. along all axes. If an `origin` tuple is provided, its length must match
  1752. the number of axes.
  1753. Returns
  1754. -------
  1755. black_tophat : ndarray
  1756. Result of the filter of `input` with `structure`.
  1757. See Also
  1758. --------
  1759. white_tophat, grey_opening, grey_closing
  1760. Examples
  1761. --------
  1762. Change dark peak to bright peak and subtract background.
  1763. >>> from scipy.ndimage import generate_binary_structure, black_tophat
  1764. >>> import numpy as np
  1765. >>> square = generate_binary_structure(rank=2, connectivity=3)
  1766. >>> dark_on_gray = np.array([[7, 6, 6, 6, 7],
  1767. ... [6, 5, 4, 5, 6],
  1768. ... [6, 4, 0, 4, 6],
  1769. ... [6, 5, 4, 5, 6],
  1770. ... [7, 6, 6, 6, 7]])
  1771. >>> black_tophat(input=dark_on_gray, structure=square)
  1772. array([[0, 0, 0, 0, 0],
  1773. [0, 0, 1, 0, 0],
  1774. [0, 1, 5, 1, 0],
  1775. [0, 0, 1, 0, 0],
  1776. [0, 0, 0, 0, 0]])
  1777. """
  1778. input = np.asarray(input)
  1779. if (size is not None) and (footprint is not None):
  1780. warnings.warn("ignoring size because footprint is set",
  1781. UserWarning, stacklevel=2)
  1782. tmp = grey_dilation(input, size, footprint, structure, None, mode,
  1783. cval, origin, axes=axes)
  1784. tmp = grey_erosion(tmp, size, footprint, structure, output, mode,
  1785. cval, origin, axes=axes)
  1786. if tmp is None:
  1787. tmp = output
  1788. if input.dtype == np.bool_ and tmp.dtype == np.bool_:
  1789. np.bitwise_xor(tmp, input, out=tmp)
  1790. else:
  1791. np.subtract(tmp, input, out=tmp)
  1792. return tmp
  1793. def distance_transform_bf(input, metric="euclidean", sampling=None,
  1794. return_distances=True, return_indices=False,
  1795. distances=None, indices=None):
  1796. """
  1797. Distance transform function by a brute force algorithm.
  1798. This function calculates the distance transform of the `input`, by
  1799. replacing each foreground (non-zero) element, with its
  1800. shortest distance to the background (any zero-valued element).
  1801. In addition to the distance transform, the feature transform can
  1802. be calculated. In this case the index of the closest background
  1803. element to each foreground element is returned in a separate array.
  1804. Parameters
  1805. ----------
  1806. input : array_like
  1807. Input
  1808. metric : {'euclidean', 'taxicab', 'chessboard'}, optional
  1809. 'cityblock' and 'manhattan' are also valid, and map to 'taxicab'.
  1810. The default is 'euclidean'.
  1811. sampling : float, or sequence of float, optional
  1812. This parameter is only used when `metric` is 'euclidean'.
  1813. Spacing of elements along each dimension. If a sequence, must be of
  1814. length equal to the input rank; if a single number, this is used for
  1815. all axes. If not specified, a grid spacing of unity is implied.
  1816. return_distances : bool, optional
  1817. Whether to calculate the distance transform.
  1818. Default is True.
  1819. return_indices : bool, optional
  1820. Whether to calculate the feature transform.
  1821. Default is False.
  1822. distances : ndarray, optional
  1823. An output array to store the calculated distance transform, instead of
  1824. returning it.
  1825. `return_distances` must be True.
  1826. It must be the same shape as `input`, and of type float64 if `metric`
  1827. is 'euclidean', uint32 otherwise.
  1828. indices : int32 ndarray, optional
  1829. An output array to store the calculated feature transform, instead of
  1830. returning it.
  1831. `return_indicies` must be True.
  1832. Its shape must be ``(input.ndim,) + input.shape``.
  1833. Returns
  1834. -------
  1835. distances : ndarray, optional
  1836. The calculated distance transform. Returned only when
  1837. `return_distances` is True and `distances` is not supplied.
  1838. It will have the same shape as the input array.
  1839. indices : int32 ndarray, optional
  1840. The calculated feature transform. It has an input-shaped array for each
  1841. dimension of the input. See distance_transform_edt documentation for an
  1842. example.
  1843. Returned only when `return_indices` is True and `indices` is not
  1844. supplied.
  1845. See Also
  1846. --------
  1847. distance_transform_cdt : Faster distance transform for taxicab and
  1848. chessboard metrics
  1849. distance_transform_edt : Faster distance transform for euclidean metric
  1850. Notes
  1851. -----
  1852. This function employs a slow brute force algorithm. See also the
  1853. function `distance_transform_cdt` for more efficient taxicab [1]_ and
  1854. chessboard algorithms [2]_.
  1855. References
  1856. ----------
  1857. .. [1] Taxicab distance. Wikipedia, 2023.
  1858. https://en.wikipedia.org/wiki/Taxicab_geometry
  1859. .. [2] Chessboard distance. Wikipedia, 2023.
  1860. https://en.wikipedia.org/wiki/Chebyshev_distance
  1861. Examples
  1862. --------
  1863. Import the necessary modules.
  1864. >>> import numpy as np
  1865. >>> from scipy.ndimage import distance_transform_bf
  1866. >>> import matplotlib.pyplot as plt
  1867. >>> from mpl_toolkits.axes_grid1 import ImageGrid
  1868. First, we create a toy binary image.
  1869. >>> def add_circle(center_x, center_y, radius, image, fillvalue=1):
  1870. ... # fill circular area with 1
  1871. ... xx, yy = np.mgrid[:image.shape[0], :image.shape[1]]
  1872. ... circle = (xx - center_x) ** 2 + (yy - center_y) ** 2
  1873. ... circle_shape = np.sqrt(circle) < radius
  1874. ... image[circle_shape] = fillvalue
  1875. ... return image
  1876. >>> image = np.zeros((100, 100), dtype=np.uint8)
  1877. >>> image[35:65, 20:80] = 1
  1878. >>> image = add_circle(28, 65, 10, image)
  1879. >>> image = add_circle(37, 30, 10, image)
  1880. >>> image = add_circle(70, 45, 20, image)
  1881. >>> image = add_circle(45, 80, 10, image)
  1882. Next, we set up the figure.
  1883. >>> fig = plt.figure(figsize=(8, 8)) # set up the figure structure
  1884. >>> grid = ImageGrid(fig, 111, nrows_ncols=(2, 2), axes_pad=(0.4, 0.3),
  1885. ... label_mode="1", share_all=True,
  1886. ... cbar_location="right", cbar_mode="each",
  1887. ... cbar_size="7%", cbar_pad="2%")
  1888. >>> for ax in grid:
  1889. ... ax.axis('off') # remove axes from images
  1890. The top left image is the original binary image.
  1891. >>> binary_image = grid[0].imshow(image, cmap='gray')
  1892. >>> cbar_binary_image = grid.cbar_axes[0].colorbar(binary_image)
  1893. >>> cbar_binary_image.set_ticks([0, 1])
  1894. >>> grid[0].set_title("Binary image: foreground in white")
  1895. The distance transform calculates the distance between foreground pixels
  1896. and the image background according to a distance metric. Available metrics
  1897. in `distance_transform_bf` are: ``euclidean`` (default), ``taxicab``
  1898. and ``chessboard``. The top right image contains the distance transform
  1899. based on the ``euclidean`` metric.
  1900. >>> distance_transform_euclidean = distance_transform_bf(image)
  1901. >>> euclidean_transform = grid[1].imshow(distance_transform_euclidean,
  1902. ... cmap='gray')
  1903. >>> cbar_euclidean = grid.cbar_axes[1].colorbar(euclidean_transform)
  1904. >>> colorbar_ticks = [0, 10, 20]
  1905. >>> cbar_euclidean.set_ticks(colorbar_ticks)
  1906. >>> grid[1].set_title("Euclidean distance")
  1907. The lower left image contains the distance transform using the ``taxicab``
  1908. metric.
  1909. >>> distance_transform_taxicab = distance_transform_bf(image,
  1910. ... metric='taxicab')
  1911. >>> taxicab_transformation = grid[2].imshow(distance_transform_taxicab,
  1912. ... cmap='gray')
  1913. >>> cbar_taxicab = grid.cbar_axes[2].colorbar(taxicab_transformation)
  1914. >>> cbar_taxicab.set_ticks(colorbar_ticks)
  1915. >>> grid[2].set_title("Taxicab distance")
  1916. Finally, the lower right image contains the distance transform using the
  1917. ``chessboard`` metric.
  1918. >>> distance_transform_cb = distance_transform_bf(image,
  1919. ... metric='chessboard')
  1920. >>> chessboard_transformation = grid[3].imshow(distance_transform_cb,
  1921. ... cmap='gray')
  1922. >>> cbar_taxicab = grid.cbar_axes[3].colorbar(chessboard_transformation)
  1923. >>> cbar_taxicab.set_ticks(colorbar_ticks)
  1924. >>> grid[3].set_title("Chessboard distance")
  1925. >>> plt.show()
  1926. """
  1927. ft_inplace = isinstance(indices, np.ndarray)
  1928. dt_inplace = isinstance(distances, np.ndarray)
  1929. _distance_tranform_arg_check(
  1930. dt_inplace, ft_inplace, return_distances, return_indices
  1931. )
  1932. tmp1 = np.asarray(input) != 0
  1933. struct = generate_binary_structure(tmp1.ndim, tmp1.ndim)
  1934. tmp2 = binary_dilation(tmp1, struct)
  1935. tmp2 = np.logical_xor(tmp1, tmp2)
  1936. tmp1 = tmp1.astype(np.int8) - tmp2.astype(np.int8)
  1937. metric = metric.lower()
  1938. if metric == 'euclidean':
  1939. metric = 1
  1940. elif metric in ['taxicab', 'cityblock', 'manhattan']:
  1941. metric = 2
  1942. elif metric == 'chessboard':
  1943. metric = 3
  1944. else:
  1945. raise RuntimeError('distance metric not supported')
  1946. if sampling is not None:
  1947. sampling = _ni_support._normalize_sequence(sampling, tmp1.ndim)
  1948. sampling = np.asarray(sampling, dtype=np.float64)
  1949. if not sampling.flags.contiguous:
  1950. sampling = sampling.copy()
  1951. if return_indices:
  1952. ft = np.zeros(tmp1.shape, dtype=np.int32)
  1953. else:
  1954. ft = None
  1955. if return_distances:
  1956. if distances is None:
  1957. if metric == 1:
  1958. dt = np.zeros(tmp1.shape, dtype=np.float64)
  1959. else:
  1960. dt = np.zeros(tmp1.shape, dtype=np.uint32)
  1961. else:
  1962. if distances.shape != tmp1.shape:
  1963. raise RuntimeError('distances array has wrong shape')
  1964. if metric == 1:
  1965. if distances.dtype.type != np.float64:
  1966. raise RuntimeError('distances array must be float64')
  1967. else:
  1968. if distances.dtype.type != np.uint32:
  1969. raise RuntimeError('distances array must be uint32')
  1970. dt = distances
  1971. else:
  1972. dt = None
  1973. _nd_image.distance_transform_bf(tmp1, metric, sampling, dt, ft)
  1974. if return_indices:
  1975. if isinstance(indices, np.ndarray):
  1976. if indices.dtype.type != np.int32:
  1977. raise RuntimeError('indices array must be int32')
  1978. if indices.shape != (tmp1.ndim,) + tmp1.shape:
  1979. raise RuntimeError('indices array has wrong shape')
  1980. tmp2 = indices
  1981. else:
  1982. tmp2 = np.indices(tmp1.shape, dtype=np.int32)
  1983. ft = np.ravel(ft)
  1984. for ii in range(tmp2.shape[0]):
  1985. rtmp = np.ravel(tmp2[ii, ...])[ft]
  1986. rtmp = rtmp.reshape(tmp1.shape)
  1987. tmp2[ii, ...] = rtmp
  1988. ft = tmp2
  1989. # construct and return the result
  1990. result = []
  1991. if return_distances and not dt_inplace:
  1992. result.append(dt)
  1993. if return_indices and not ft_inplace:
  1994. result.append(ft)
  1995. if len(result) == 2:
  1996. return tuple(result)
  1997. elif len(result) == 1:
  1998. return result[0]
  1999. else:
  2000. return None
  2001. def distance_transform_cdt(input, metric='chessboard', return_distances=True,
  2002. return_indices=False, distances=None, indices=None):
  2003. """
  2004. Distance transform for chamfer type of transforms.
  2005. This function calculates the distance transform of the `input`, by
  2006. replacing each foreground (non-zero) element, with its
  2007. shortest distance to the background (any zero-valued element).
  2008. In addition to the distance transform, the feature transform can
  2009. be calculated. In this case the index of the closest background
  2010. element to each foreground element is returned in a separate array.
  2011. Parameters
  2012. ----------
  2013. input : array_like
  2014. Input. Values of 0 are treated as background.
  2015. metric : {'chessboard', 'taxicab'} or array_like, optional
  2016. The `metric` determines the type of chamfering that is done. If the
  2017. `metric` is equal to 'taxicab' a structure is generated using
  2018. `generate_binary_structure` with a squared distance equal to 1. If
  2019. the `metric` is equal to 'chessboard', a `metric` is generated
  2020. using `generate_binary_structure` with a squared distance equal to
  2021. the dimensionality of the array. These choices correspond to the
  2022. common interpretations of the 'taxicab' and the 'chessboard'
  2023. distance metrics in two dimensions.
  2024. A custom metric may be provided, in the form of a matrix where
  2025. each dimension has a length of three.
  2026. 'cityblock' and 'manhattan' are also valid, and map to 'taxicab'.
  2027. The default is 'chessboard'.
  2028. return_distances : bool, optional
  2029. Whether to calculate the distance transform.
  2030. Default is True.
  2031. return_indices : bool, optional
  2032. Whether to calculate the feature transform.
  2033. Default is False.
  2034. distances : int32 ndarray, optional
  2035. An output array to store the calculated distance transform, instead of
  2036. returning it.
  2037. `return_distances` must be True.
  2038. It must be the same shape as `input`.
  2039. indices : int32 ndarray, optional
  2040. An output array to store the calculated feature transform, instead of
  2041. returning it.
  2042. `return_indicies` must be True.
  2043. Its shape must be ``(input.ndim,) + input.shape``.
  2044. Returns
  2045. -------
  2046. distances : int32 ndarray, optional
  2047. The calculated distance transform. Returned only when
  2048. `return_distances` is True, and `distances` is not supplied.
  2049. It will have the same shape as the input array.
  2050. indices : int32 ndarray, optional
  2051. The calculated feature transform. It has an input-shaped array for each
  2052. dimension of the input. See distance_transform_edt documentation for an
  2053. example.
  2054. Returned only when `return_indices` is True, and `indices` is not
  2055. supplied.
  2056. See Also
  2057. --------
  2058. distance_transform_edt : Fast distance transform for euclidean metric
  2059. distance_transform_bf : Distance transform for different metrics using
  2060. a slower brute force algorithm
  2061. Examples
  2062. --------
  2063. Import the necessary modules.
  2064. >>> import numpy as np
  2065. >>> from scipy.ndimage import distance_transform_cdt
  2066. >>> import matplotlib.pyplot as plt
  2067. >>> from mpl_toolkits.axes_grid1 import ImageGrid
  2068. First, we create a toy binary image.
  2069. >>> def add_circle(center_x, center_y, radius, image, fillvalue=1):
  2070. ... # fill circular area with 1
  2071. ... xx, yy = np.mgrid[:image.shape[0], :image.shape[1]]
  2072. ... circle = (xx - center_x) ** 2 + (yy - center_y) ** 2
  2073. ... circle_shape = np.sqrt(circle) < radius
  2074. ... image[circle_shape] = fillvalue
  2075. ... return image
  2076. >>> image = np.zeros((100, 100), dtype=np.uint8)
  2077. >>> image[35:65, 20:80] = 1
  2078. >>> image = add_circle(28, 65, 10, image)
  2079. >>> image = add_circle(37, 30, 10, image)
  2080. >>> image = add_circle(70, 45, 20, image)
  2081. >>> image = add_circle(45, 80, 10, image)
  2082. Next, we set up the figure.
  2083. >>> fig = plt.figure(figsize=(5, 15))
  2084. >>> grid = ImageGrid(fig, 111, nrows_ncols=(3, 1), axes_pad=(0.5, 0.3),
  2085. ... label_mode="1", share_all=True,
  2086. ... cbar_location="right", cbar_mode="each",
  2087. ... cbar_size="7%", cbar_pad="2%")
  2088. >>> for ax in grid:
  2089. ... ax.axis('off')
  2090. >>> top, middle, bottom = grid
  2091. >>> colorbar_ticks = [0, 10, 20]
  2092. The top image contains the original binary image.
  2093. >>> binary_image = top.imshow(image, cmap='gray')
  2094. >>> cbar_binary_image = top.cax.colorbar(binary_image)
  2095. >>> cbar_binary_image.set_ticks([0, 1])
  2096. >>> top.set_title("Binary image: foreground in white")
  2097. The middle image contains the distance transform using the ``taxicab``
  2098. metric.
  2099. >>> distance_taxicab = distance_transform_cdt(image, metric="taxicab")
  2100. >>> taxicab_transform = middle.imshow(distance_taxicab, cmap='gray')
  2101. >>> cbar_taxicab = middle.cax.colorbar(taxicab_transform)
  2102. >>> cbar_taxicab.set_ticks(colorbar_ticks)
  2103. >>> middle.set_title("Taxicab metric")
  2104. The bottom image contains the distance transform using the ``chessboard``
  2105. metric.
  2106. >>> distance_chessboard = distance_transform_cdt(image,
  2107. ... metric="chessboard")
  2108. >>> chessboard_transform = bottom.imshow(distance_chessboard, cmap='gray')
  2109. >>> cbar_chessboard = bottom.cax.colorbar(chessboard_transform)
  2110. >>> cbar_chessboard.set_ticks(colorbar_ticks)
  2111. >>> bottom.set_title("Chessboard metric")
  2112. >>> plt.tight_layout()
  2113. >>> plt.show()
  2114. """
  2115. ft_inplace = isinstance(indices, np.ndarray)
  2116. dt_inplace = isinstance(distances, np.ndarray)
  2117. _distance_tranform_arg_check(
  2118. dt_inplace, ft_inplace, return_distances, return_indices
  2119. )
  2120. input = np.asarray(input)
  2121. if isinstance(metric, str):
  2122. if metric in ['taxicab', 'cityblock', 'manhattan']:
  2123. rank = input.ndim
  2124. metric = generate_binary_structure(rank, 1)
  2125. elif metric == 'chessboard':
  2126. rank = input.ndim
  2127. metric = generate_binary_structure(rank, rank)
  2128. else:
  2129. raise ValueError('invalid metric provided')
  2130. else:
  2131. try:
  2132. metric = np.asarray(metric)
  2133. except Exception as e:
  2134. raise ValueError('invalid metric provided') from e
  2135. for s in metric.shape:
  2136. if s != 3:
  2137. raise ValueError('metric sizes must be equal to 3')
  2138. if not metric.flags.contiguous:
  2139. metric = metric.copy()
  2140. if dt_inplace:
  2141. if distances.dtype.type != np.int32:
  2142. raise ValueError('distances must be of int32 type')
  2143. if distances.shape != input.shape:
  2144. raise ValueError('distances has wrong shape')
  2145. dt = distances
  2146. dt[...] = np.where(input, -1, 0).astype(np.int32)
  2147. else:
  2148. dt = np.where(input, -1, 0).astype(np.int32)
  2149. rank = dt.ndim
  2150. if return_indices:
  2151. ft = np.arange(dt.size, dtype=np.int32).reshape(dt.shape)
  2152. else:
  2153. ft = None
  2154. _nd_image.distance_transform_op(metric, dt, ft)
  2155. dt = dt[tuple([slice(None, None, -1)] * rank)]
  2156. if return_indices:
  2157. ft = ft[tuple([slice(None, None, -1)] * rank)]
  2158. _nd_image.distance_transform_op(metric, dt, ft)
  2159. dt = dt[tuple([slice(None, None, -1)] * rank)]
  2160. if return_indices:
  2161. ft = ft[tuple([slice(None, None, -1)] * rank)]
  2162. ft = np.ravel(ft)
  2163. if ft_inplace:
  2164. if indices.dtype.type != np.int32:
  2165. raise ValueError('indices array must be int32')
  2166. if indices.shape != (dt.ndim,) + dt.shape:
  2167. raise ValueError('indices array has wrong shape')
  2168. tmp = indices
  2169. else:
  2170. tmp = np.indices(dt.shape, dtype=np.int32)
  2171. for ii in range(tmp.shape[0]):
  2172. rtmp = np.ravel(tmp[ii, ...])[ft]
  2173. rtmp = rtmp.reshape(dt.shape)
  2174. tmp[ii, ...] = rtmp
  2175. ft = tmp
  2176. # construct and return the result
  2177. result = []
  2178. if return_distances and not dt_inplace:
  2179. result.append(dt)
  2180. if return_indices and not ft_inplace:
  2181. result.append(ft)
  2182. if len(result) == 2:
  2183. return tuple(result)
  2184. elif len(result) == 1:
  2185. return result[0]
  2186. else:
  2187. return None
  2188. def distance_transform_edt(input, sampling=None, return_distances=True,
  2189. return_indices=False, distances=None, indices=None):
  2190. """
  2191. Exact Euclidean distance transform.
  2192. This function calculates the distance transform of the `input`, by
  2193. replacing each foreground (non-zero) element, with its
  2194. shortest distance to the background (any zero-valued element).
  2195. In addition to the distance transform, the feature transform can
  2196. be calculated. In this case the index of the closest background
  2197. element to each foreground element is returned in a separate array.
  2198. Parameters
  2199. ----------
  2200. input : array_like
  2201. Input data to transform. Can be any type but will be converted
  2202. into binary: 1 wherever input equates to True, 0 elsewhere.
  2203. sampling : float, or sequence of float, optional
  2204. Spacing of elements along each dimension. If a sequence, must be of
  2205. length equal to the input rank; if a single number, this is used for
  2206. all axes. If not specified, a grid spacing of unity is implied.
  2207. return_distances : bool, optional
  2208. Whether to calculate the distance transform.
  2209. Default is True.
  2210. return_indices : bool, optional
  2211. Whether to calculate the feature transform.
  2212. Default is False.
  2213. distances : float64 ndarray, optional
  2214. An output array to store the calculated distance transform, instead of
  2215. returning it.
  2216. `return_distances` must be True.
  2217. It must be the same shape as `input`.
  2218. indices : int32 ndarray, optional
  2219. An output array to store the calculated feature transform, instead of
  2220. returning it.
  2221. `return_indicies` must be True.
  2222. Its shape must be ``(input.ndim,) + input.shape``.
  2223. Returns
  2224. -------
  2225. distances : float64 ndarray, optional
  2226. The calculated distance transform. Returned only when
  2227. `return_distances` is True and `distances` is not supplied.
  2228. It will have the same shape as the input array.
  2229. indices : int32 ndarray, optional
  2230. The calculated feature transform. It has an input-shaped array for each
  2231. dimension of the input. See example below.
  2232. Returned only when `return_indices` is True and `indices` is not
  2233. supplied.
  2234. Notes
  2235. -----
  2236. The Euclidean distance transform gives values of the Euclidean
  2237. distance::
  2238. n
  2239. y_i = sqrt(sum (x[i]-b[i])**2)
  2240. i
  2241. where b[i] is the background point (value 0) with the smallest
  2242. Euclidean distance to input points x[i], and n is the
  2243. number of dimensions.
  2244. Examples
  2245. --------
  2246. >>> from scipy import ndimage
  2247. >>> import numpy as np
  2248. >>> a = np.array(([0,1,1,1,1],
  2249. ... [0,0,1,1,1],
  2250. ... [0,1,1,1,1],
  2251. ... [0,1,1,1,0],
  2252. ... [0,1,1,0,0]))
  2253. >>> ndimage.distance_transform_edt(a)
  2254. array([[ 0. , 1. , 1.4142, 2.2361, 3. ],
  2255. [ 0. , 0. , 1. , 2. , 2. ],
  2256. [ 0. , 1. , 1.4142, 1.4142, 1. ],
  2257. [ 0. , 1. , 1.4142, 1. , 0. ],
  2258. [ 0. , 1. , 1. , 0. , 0. ]])
  2259. With a sampling of 2 units along x, 1 along y:
  2260. >>> ndimage.distance_transform_edt(a, sampling=[2,1])
  2261. array([[ 0. , 1. , 2. , 2.8284, 3.6056],
  2262. [ 0. , 0. , 1. , 2. , 3. ],
  2263. [ 0. , 1. , 2. , 2.2361, 2. ],
  2264. [ 0. , 1. , 2. , 1. , 0. ],
  2265. [ 0. , 1. , 1. , 0. , 0. ]])
  2266. Asking for indices as well:
  2267. >>> edt, inds = ndimage.distance_transform_edt(a, return_indices=True)
  2268. >>> inds
  2269. array([[[0, 0, 1, 1, 3],
  2270. [1, 1, 1, 1, 3],
  2271. [2, 2, 1, 3, 3],
  2272. [3, 3, 4, 4, 3],
  2273. [4, 4, 4, 4, 4]],
  2274. [[0, 0, 1, 1, 4],
  2275. [0, 1, 1, 1, 4],
  2276. [0, 0, 1, 4, 4],
  2277. [0, 0, 3, 3, 4],
  2278. [0, 0, 3, 3, 4]]], dtype=int32)
  2279. With arrays provided for inplace outputs:
  2280. >>> indices = np.zeros(((np.ndim(a),) + a.shape), dtype=np.int32)
  2281. >>> ndimage.distance_transform_edt(a, return_indices=True, indices=indices)
  2282. array([[ 0. , 1. , 1.4142, 2.2361, 3. ],
  2283. [ 0. , 0. , 1. , 2. , 2. ],
  2284. [ 0. , 1. , 1.4142, 1.4142, 1. ],
  2285. [ 0. , 1. , 1.4142, 1. , 0. ],
  2286. [ 0. , 1. , 1. , 0. , 0. ]])
  2287. >>> indices
  2288. array([[[0, 0, 1, 1, 3],
  2289. [1, 1, 1, 1, 3],
  2290. [2, 2, 1, 3, 3],
  2291. [3, 3, 4, 4, 3],
  2292. [4, 4, 4, 4, 4]],
  2293. [[0, 0, 1, 1, 4],
  2294. [0, 1, 1, 1, 4],
  2295. [0, 0, 1, 4, 4],
  2296. [0, 0, 3, 3, 4],
  2297. [0, 0, 3, 3, 4]]], dtype=int32)
  2298. """
  2299. ft_inplace = isinstance(indices, np.ndarray)
  2300. dt_inplace = isinstance(distances, np.ndarray)
  2301. _distance_tranform_arg_check(
  2302. dt_inplace, ft_inplace, return_distances, return_indices
  2303. )
  2304. # calculate the feature transform
  2305. input = np.atleast_1d(np.where(input, 1, 0).astype(np.int8))
  2306. if sampling is not None:
  2307. sampling = _ni_support._normalize_sequence(sampling, input.ndim)
  2308. sampling = np.asarray(sampling, dtype=np.float64)
  2309. if not sampling.flags.contiguous:
  2310. sampling = sampling.copy()
  2311. if ft_inplace:
  2312. ft = indices
  2313. if ft.shape != (input.ndim,) + input.shape:
  2314. raise RuntimeError('indices array has wrong shape')
  2315. if ft.dtype.type != np.int32:
  2316. raise RuntimeError('indices array must be int32')
  2317. else:
  2318. ft = np.zeros((input.ndim,) + input.shape, dtype=np.int32)
  2319. _nd_image.euclidean_feature_transform(input, sampling, ft)
  2320. # if requested, calculate the distance transform
  2321. if return_distances:
  2322. dt = ft - np.indices(input.shape, dtype=ft.dtype)
  2323. dt = dt.astype(np.float64)
  2324. if sampling is not None:
  2325. for ii in range(len(sampling)):
  2326. dt[ii, ...] *= sampling[ii]
  2327. np.multiply(dt, dt, dt)
  2328. if dt_inplace:
  2329. dt = np.add.reduce(dt, axis=0)
  2330. if distances.shape != dt.shape:
  2331. raise RuntimeError('distances array has wrong shape')
  2332. if distances.dtype.type != np.float64:
  2333. raise RuntimeError('distances array must be float64')
  2334. np.sqrt(dt, distances)
  2335. else:
  2336. dt = np.add.reduce(dt, axis=0)
  2337. dt = np.sqrt(dt)
  2338. # construct and return the result
  2339. result = []
  2340. if return_distances and not dt_inplace:
  2341. result.append(dt)
  2342. if return_indices and not ft_inplace:
  2343. result.append(ft)
  2344. if len(result) == 2:
  2345. return tuple(result)
  2346. elif len(result) == 1:
  2347. return result[0]
  2348. else:
  2349. return None
  2350. def _distance_tranform_arg_check(distances_out, indices_out,
  2351. return_distances, return_indices):
  2352. """Raise a RuntimeError if the arguments are invalid"""
  2353. error_msgs = []
  2354. if (not return_distances) and (not return_indices):
  2355. error_msgs.append(
  2356. 'at least one of return_distances/return_indices must be True')
  2357. if distances_out and not return_distances:
  2358. error_msgs.append(
  2359. 'return_distances must be True if distances is supplied'
  2360. )
  2361. if indices_out and not return_indices:
  2362. error_msgs.append('return_indices must be True if indices is supplied')
  2363. if error_msgs:
  2364. raise RuntimeError(', '.join(error_msgs))