1"""
2
3General Description
4-------------------
5
6These filters compute the local histogram at each pixel, using a sliding window
7similar to the method described in [1]_. A histogram is built using a moving
8window in order to limit redundant computation. The moving window follows a
9snake-like path:
10
11...------------------------↘
12↙--------------------------↙
13↘--------------------------...
14
15The local histogram is updated at each pixel as the footprint window
16moves by, i.e. only those pixels entering and leaving the footprint
17update the local histogram. The histogram size is 8-bit (256 bins) for 8-bit
18images and 2- to 16-bit for 16-bit images depending on the maximum value of the
19image.
20
21The filter is applied up to the image border, the neighborhood used is
22adjusted accordingly. The user may provide a mask image (same size as input
23image) where non zero values are the part of the image participating in the
24histogram computation. By default the entire image is filtered.
25
26This implementation outperforms :func:`skimage.morphology.dilation`
27for large footprints.
28
29Input images will be cast in unsigned 8-bit integer or unsigned 16-bit integer
30if necessary. The number of histogram bins is then determined from the maximum
31value present in the image. Eventually, the output image is cast in the input
32dtype, or the `output_dtype` if set.
33
34To do
35-----
36
37* add simple examples, adapt documentation on existing examples
38* add/check existing doc
39* adapting tests for each type of filter
40
41
42References
43----------
44
45.. [1] Huang, T. ,Yang, G. ;  Tang, G.. "A fast two-dimensional
46       median filtering algorithm", IEEE Transactions on Acoustics, Speech and
47       Signal Processing, Feb 1979. Volume: 27 , Issue: 1, Page(s): 13 - 18.
48
49"""
50
51import numpy as np
52from scipy import ndimage as ndi
53
54from ..._shared.utils import check_nD, deprecate_kwarg, warn
55from ...util import img_as_ubyte
56from . import generic_cy
57
58
59__all__ = ['autolevel', 'equalize', 'gradient', 'maximum', 'mean',
60           'geometric_mean', 'subtract_mean', 'median', 'minimum', 'modal',
61           'enhance_contrast', 'pop', 'threshold', 'noise_filter',
62           'entropy', 'otsu']
63
64
65def _preprocess_input(image, footprint=None, out=None, mask=None,
66                      out_dtype=None, pixel_size=1):
67    """Preprocess and verify input for filters.rank methods.
68
69    Parameters
70    ----------
71    image : 2-D array (integer or float)
72        Input image.
73    footprint : 2-D array (integer or float), optional
74        The neighborhood expressed as a 2-D array of 1's and 0's.
75    out : 2-D array (integer or float), optional
76        If None, a new array is allocated.
77    mask : ndarray (integer or float), optional
78        Mask array that defines (>0) area of the image included in the local
79        neighborhood. If None, the complete image is used (default).
80    out_dtype : data-type, optional
81        Desired output data-type. Default is None, which means we cast output
82        in input dtype.
83    pixel_size : int, optional
84        Dimension of each pixel. Default value is 1.
85
86    Returns
87    -------
88    image : 2-D array (np.uint8 or np.uint16)
89    footprint : 2-D array (np.uint8)
90        The neighborhood expressed as a binary 2-D array.
91    out : 3-D array (same dtype out_dtype or as input)
92        Output array. The two first dimensions are the spatial ones, the third
93        one is the pixel vector (length 1 by default).
94    mask : 2-D array (np.uint8)
95        Mask array that defines (>0) area of the image included in the local
96        neighborhood.
97    n_bins : int
98        Number of histogram bins.
99
100    """
101    check_nD(image, 2)
102    input_dtype = image.dtype
103    if (input_dtype in (bool, bool) or out_dtype in (bool, bool)):
104        raise ValueError('dtype cannot be bool.')
105    if input_dtype not in (np.uint8, np.uint16):
106        message = (f'Possible precision loss converting image of type '
107                   f'{input_dtype} to uint8 as required by rank filters. '
108                   f'Convert manually using skimage.util.img_as_ubyte to '
109                   f'silence this warning.')
110        warn(message, stacklevel=5)
111        image = img_as_ubyte(image)
112
113    footprint = np.ascontiguousarray(img_as_ubyte(footprint > 0))
114    if footprint.ndim != image.ndim:
115        raise ValueError('Image dimensions and neighborhood dimensions'
116                         'do not match')
117
118    image = np.ascontiguousarray(image)
119
120    if mask is not None:
121        mask = img_as_ubyte(mask)
122        mask = np.ascontiguousarray(mask)
123
124    if image is out:
125        raise NotImplementedError("Cannot perform rank operation in place.")
126
127    if out is None:
128        if out_dtype is None:
129            out_dtype = image.dtype
130        out = np.empty(image.shape + (pixel_size,), dtype=out_dtype)
131    else:
132        if len(out.shape) == 2:
133            out = out.reshape(out.shape + (pixel_size,))
134
135    if image.dtype in (np.uint8, np.int8):
136        n_bins = 256
137    else:
138        # Convert to a Python int to avoid the potential overflow when we add
139        # 1 to the maximum of the image.
140        n_bins = int(max(3, image.max())) + 1
141
142    if n_bins > 2 ** 10:
143        warn(f'Bad rank filter performance is expected due to a '
144             f'large number of bins ({n_bins}), equivalent to an approximate '
145             f'bitdepth of {np.log2(n_bins):.1f}.',
146             stacklevel=2)
147
148    return image, footprint, out, mask, n_bins
149
150
151def _handle_input_3D(image, footprint=None, out=None, mask=None,
152                     out_dtype=None, pixel_size=1):
153    """Preprocess and verify input for filters.rank methods.
154
155    Parameters
156    ----------
157    image : 3-D array (integer or float)
158        Input image.
159    footprint : 3-D array (integer or float), optional
160        The neighborhood expressed as a 3-D array of 1's and 0's.
161    out : 3-D array (integer or float), optional
162        If None, a new array is allocated.
163    mask : ndarray (integer or float), optional
164        Mask array that defines (>0) area of the image included in the local
165        neighborhood. If None, the complete image is used (default).
166    out_dtype : data-type, optional
167        Desired output data-type. Default is None, which means we cast output
168        in input dtype.
169    pixel_size : int, optional
170        Dimension of each pixel. Default value is 1.
171
172    Returns
173    -------
174    image : 3-D array (np.uint8 or np.uint16)
175    footprint : 3-D array (np.uint8)
176        The neighborhood expressed as a binary 3-D array.
177    out : 3-D array (same dtype out_dtype or as input)
178        Output array. The two first dimensions are the spatial ones, the third
179        one is the pixel vector (length 1 by default).
180    mask : 3-D array (np.uint8)
181        Mask array that defines (>0) area of the image included in the local
182        neighborhood.
183    n_bins : int
184        Number of histogram bins.
185
186    """
187    check_nD(image, 3)
188    if image.dtype not in (np.uint8, np.uint16):
189        message = (f'Possible precision loss converting image of type '
190                   f'{image.dtype} to uint8 as required by rank filters. '
191                   f'Convert manually using skimage.util.img_as_ubyte to '
192                   f'silence this warning.')
193        warn(message, stacklevel=2)
194        image = img_as_ubyte(image)
195
196    footprint = np.ascontiguousarray(img_as_ubyte(footprint > 0))
197    if footprint.ndim != image.ndim:
198        raise ValueError('Image dimensions and neighborhood dimensions'
199                         'do not match')
200    image = np.ascontiguousarray(image)
201
202    if mask is None:
203        mask = np.ones(image.shape, dtype=np.uint8)
204    else:
205        mask = img_as_ubyte(mask)
206        mask = np.ascontiguousarray(mask)
207
208    if image is out:
209        raise NotImplementedError("Cannot perform rank operation in place.")
210
211    if out is None:
212        if out_dtype is None:
213            out_dtype = image.dtype
214        out = np.empty(image.shape + (pixel_size,), dtype=out_dtype)
215    else:
216        out = out.reshape(out.shape + (pixel_size,))
217
218    is_8bit = image.dtype in (np.uint8, np.int8)
219
220    if is_8bit:
221        n_bins = 256
222    else:
223        # Convert to a Python int to avoid the potential overflow when we add
224        # 1 to the maximum of the image.
225        n_bins = int(max(3, image.max())) + 1
226
227    if n_bins > 2**10:
228        warn(f'Bad rank filter performance is expected due to a '
229             f'large number of bins ({n_bins}), equivalent to an approximate '
230             f'bitdepth of {np.log2(n_bins):.1f}.',
231             stacklevel=2)
232
233    return image, footprint, out, mask, n_bins
234
235
236def _apply_scalar_per_pixel(func, image, footprint, out, mask, shift_x,
237                            shift_y, out_dtype=None):
238    """Process the specific cython function to the image.
239
240    Parameters
241    ----------
242    func : function
243        Cython function to apply.
244    image : 2-D array (integer or float)
245        Input image.
246    footprint : 2-D array (integer or float)
247        The neighborhood expressed as a 2-D array of 1's and 0's.
248    out : 2-D array (integer or float)
249        If None, a new array is allocated.
250    mask : ndarray (integer or float)
251        Mask array that defines (>0) area of the image included in the local
252        neighborhood. If None, the complete image is used (default).
253    shift_x, shift_y : int
254        Offset added to the footprint center point. Shift is bounded to the
255        footprint sizes (center must be inside the given footprint).
256    out_dtype : data-type, optional
257        Desired output data-type. Default is None, which means we cast output
258        in input dtype.
259
260    """
261    # preprocess and verify the input
262    image, footprint, out, mask, n_bins = _preprocess_input(image, footprint,
263                                                            out, mask,
264                                                            out_dtype)
265
266    # apply cython function
267    func(image, footprint, shift_x=shift_x, shift_y=shift_y, mask=mask,
268         out=out, n_bins=n_bins)
269
270    return np.squeeze(out, axis=-1)
271
272
273def _apply_scalar_per_pixel_3D(func, image, footprint, out, mask, shift_x,
274                               shift_y, shift_z, out_dtype=None):
275
276    image, footprint, out, mask, n_bins = _handle_input_3D(
277        image, footprint, out, mask, out_dtype
278    )
279
280    func(image, footprint, shift_x=shift_x, shift_y=shift_y, shift_z=shift_z,
281         mask=mask, out=out, n_bins=n_bins)
282
283    return out.reshape(out.shape[:3])
284
285
286def _apply_vector_per_pixel(func, image, footprint, out, mask, shift_x,
287                            shift_y, out_dtype=None, pixel_size=1):
288    """
289
290    Parameters
291    ----------
292    func : function
293        Cython function to apply.
294    image : 2-D array (integer or float)
295        Input image.
296    footprint : 2-D array (integer or float)
297        The neighborhood expressed as a 2-D array of 1's and 0's.
298    out : 2-D array (integer or float)
299        If None, a new array is allocated.
300    mask : ndarray (integer or float)
301        Mask array that defines (>0) area of the image included in the local
302        neighborhood. If None, the complete image is used (default).
303    shift_x, shift_y : int
304        Offset added to the footprint center point. Shift is bounded to the
305        footprint sizes (center must be inside the given footprint).
306    out_dtype : data-type, optional
307        Desired output data-type. Default is None, which means we cast output
308        in input dtype.
309    pixel_size : int, optional
310        Dimension of each pixel.
311
312    Returns
313    -------
314    out : 3-D array with float dtype of dimensions (H,W,N), where (H,W) are
315        the dimensions of the input image and N is n_bins or
316        ``image.max() + 1`` if no value is provided as a parameter.
317        Effectively, each pixel is a N-D feature vector that is the histogram.
318        The sum of the elements in the feature vector will be 1, unless no
319        pixels in the window were covered by both footprint and mask, in which
320        case all elements will be 0.
321
322    """
323    # preprocess and verify the input
324    image, footprint, out, mask, n_bins = _preprocess_input(image, footprint,
325                                                            out, mask,
326                                                            out_dtype,
327                                                            pixel_size)
328
329    # apply cython function
330    func(image, footprint, shift_x=shift_x, shift_y=shift_y, mask=mask,
331         out=out, n_bins=n_bins)
332
333    return out
334
335
336@deprecate_kwarg(kwarg_mapping={'selem': 'footprint'}, removed_version="1.0",
337                 deprecated_version="0.19")
338def autolevel(image, footprint, out=None, mask=None,
339              shift_x=False, shift_y=False, shift_z=False):
340    """Auto-level image using local histogram.
341
342    This filter locally stretches the histogram of gray values to cover the
343    entire range of values from "white" to "black".
344
345    Parameters
346    ----------
347    image : ([P,] M, N) ndarray (uint8, uint16)
348        Input image.
349    footprint : ndarray
350        The neighborhood expressed as an ndarray of 1's and 0's.
351    out : ([P,] M, N) array (same dtype as input)
352        If None, a new array is allocated.
353    mask : ndarray (integer or float), optional
354        Mask array that defines (>0) area of the image included in the local
355        neighborhood. If None, the complete image is used (default).
356    shift_x, shift_y, shift_z : int
357        Offset added to the footprint center point. Shift is bounded to the
358        footprint sizes (center must be inside the given footprint).
359
360    Returns
361    -------
362    out : ([P,] M, N) ndarray (same dtype as input image)
363        Output image.
364
365    Examples
366    --------
367    >>> from skimage import data
368    >>> from skimage.morphology import disk, ball
369    >>> from skimage.filters.rank import autolevel
370    >>> import numpy as np
371    >>> img = data.camera()
372    >>> rng = np.random.default_rng()
373    >>> volume = rng.integers(0, 255, size=(10,10,10), dtype=np.uint8)
374    >>> auto = autolevel(img, disk(5))
375    >>> auto_vol = autolevel(volume, ball(5))
376
377    """
378
379    np_image = np.asanyarray(image)
380    if np_image.ndim == 2:
381        return _apply_scalar_per_pixel(generic_cy._autolevel, image, footprint,
382                                       out=out, mask=mask,
383                                       shift_x=shift_x, shift_y=shift_y)
384    else:
385        return _apply_scalar_per_pixel_3D(generic_cy._autolevel_3D, image,
386                                          footprint, out=out, mask=mask,
387                                          shift_x=shift_x, shift_y=shift_y,
388                                          shift_z=shift_z)
389
390
391@deprecate_kwarg(kwarg_mapping={'selem': 'footprint'}, removed_version="1.0",
392                 deprecated_version="0.19")
393def equalize(image, footprint, out=None, mask=None,
394             shift_x=False, shift_y=False, shift_z=False):
395    """Equalize image using local histogram.
396
397    Parameters
398    ----------
399    image : ([P,] M, N) ndarray (uint8, uint16)
400        Input image.
401    footprint : ndarray
402        The neighborhood expressed as an ndarray of 1's and 0's.
403    out : ([P,] M, N) array (same dtype as input)
404        If None, a new array is allocated.
405    mask : ndarray (integer or float), optional
406        Mask array that defines (>0) area of the image included in the local
407        neighborhood. If None, the complete image is used (default).
408    shift_x, shift_y, shift_z : int
409        Offset added to the footprint center point. Shift is bounded to the
410        footprint sizes (center must be inside the given footprint).
411
412    Returns
413    -------
414    out : ([P,] M, N) ndarray (same dtype as input image)
415        Output image.
416
417    Examples
418    --------
419    >>> from skimage import data
420    >>> from skimage.morphology import disk, ball
421    >>> from skimage.filters.rank import equalize
422    >>> import numpy as np
423    >>> img = data.camera()
424    >>> rng = np.random.default_rng()
425    >>> volume = rng.integers(0, 255, size=(10,10,10), dtype=np.uint8)
426    >>> equ = equalize(img, disk(5))
427    >>> equ_vol = equalize(volume, ball(5))
428
429    """
430
431    np_image = np.asanyarray(image)
432    if np_image.ndim == 2:
433        return _apply_scalar_per_pixel(generic_cy._equalize, image, footprint,
434                                       out=out, mask=mask,
435                                       shift_x=shift_x, shift_y=shift_y)
436    else:
437        return _apply_scalar_per_pixel_3D(generic_cy._equalize_3D, image,
438                                          footprint, out=out, mask=mask,
439                                          shift_x=shift_x, shift_y=shift_y,
440                                          shift_z=shift_z)
441
442
443@deprecate_kwarg(kwarg_mapping={'selem': 'footprint'}, removed_version="1.0",
444                 deprecated_version="0.19")
445def gradient(image, footprint, out=None, mask=None,
446             shift_x=False, shift_y=False, shift_z=False):
447    """Return local gradient of an image (i.e. local maximum - local minimum).
448
449    Parameters
450    ----------
451    image : ([P,] M, N) ndarray (uint8, uint16)
452        Input image.
453    footprint : ndarray
454        The neighborhood expressed as an ndarray of 1's and 0's.
455    out : ([P,] M, N) array (same dtype as input)
456        If None, a new array is allocated.
457    mask : ndarray (integer or float), optional
458        Mask array that defines (>0) area of the image included in the local
459        neighborhood. If None, the complete image is used (default).
460    shift_x, shift_y, shift_z : int
461        Offset added to the footprint center point. Shift is bounded to the
462        footprint sizes (center must be inside the given footprint).
463
464    Returns
465    -------
466    out : ([P,] M, N) ndarray (same dtype as input image)
467        Output image.
468
469    Examples
470    --------
471    >>> from skimage import data
472    >>> from skimage.morphology import disk, ball
473    >>> from skimage.filters.rank import gradient
474    >>> import numpy as np
475    >>> img = data.camera()
476    >>> rng = np.random.default_rng()
477    >>> volume = rng.integers(0, 255, size=(10,10,10), dtype=np.uint8)
478    >>> out = gradient(img, disk(5))
479    >>> out_vol = gradient(volume, ball(5))
480
481    """
482
483    np_image = np.asanyarray(image)
484    if np_image.ndim == 2:
485        return _apply_scalar_per_pixel(generic_cy._gradient, image, footprint,
486                                       out=out, mask=mask,
487                                       shift_x=shift_x, shift_y=shift_y)
488    else:
489        return _apply_scalar_per_pixel_3D(generic_cy._gradient_3D, image,
490                                          footprint, out=out, mask=mask,
491                                          shift_x=shift_x, shift_y=shift_y,
492                                          shift_z=shift_z)
493
494
495@deprecate_kwarg(kwarg_mapping={'selem': 'footprint'}, removed_version="1.0",
496                 deprecated_version="0.19")
497def maximum(image, footprint, out=None, mask=None,
498            shift_x=False, shift_y=False, shift_z=False):
499    """Return local maximum of an image.
500
501    Parameters
502    ----------
503    image : ([P,] M, N) ndarray (uint8, uint16)
504        Input image.
505    footprint : ndarray
506        The neighborhood expressed as an ndarray of 1's and 0's.
507    out : ([P,] M, N) array (same dtype as input)
508        If None, a new array is allocated.
509    mask : ndarray (integer or float), optional
510        Mask array that defines (>0) area of the image included in the local
511        neighborhood. If None, the complete image is used (default).
512    shift_x, shift_y, shift_z : int
513        Offset added to the footprint center point. Shift is bounded to the
514        footprint sizes (center must be inside the given footprint).
515
516    Returns
517    -------
518    out : ([P,] M, N) ndarray (same dtype as input image)
519        Output image.
520
521    See also
522    --------
523    skimage.morphology.dilation
524
525    Notes
526    -----
527    The lower algorithm complexity makes `skimage.filters.rank.maximum`
528    more efficient for larger images and footprints.
529
530    Examples
531    --------
532    >>> from skimage import data
533    >>> from skimage.morphology import disk, ball
534    >>> from skimage.filters.rank import maximum
535    >>> import numpy as np
536    >>> img = data.camera()
537    >>> rng = np.random.default_rng()
538    >>> volume = rng.integers(0, 255, size=(10,10,10), dtype=np.uint8)
539    >>> out = maximum(img, disk(5))
540    >>> out_vol = maximum(volume, ball(5))
541
542    """
543
544    np_image = np.asanyarray(image)
545    if np_image.ndim == 2:
546        return _apply_scalar_per_pixel(generic_cy._maximum, image, footprint,
547                                       out=out, mask=mask,
548                                       shift_x=shift_x, shift_y=shift_y)
549    else:
550        return _apply_scalar_per_pixel_3D(generic_cy._maximum_3D, image,
551                                          footprint, out=out, mask=mask,
552                                          shift_x=shift_x, shift_y=shift_y,
553                                          shift_z=shift_z)
554
555
556@deprecate_kwarg(kwarg_mapping={'selem': 'footprint'}, removed_version="1.0",
557                 deprecated_version="0.19")
558def mean(image, footprint, out=None, mask=None,
559         shift_x=False, shift_y=False, shift_z=False):
560    """Return local mean of an image.
561
562    Parameters
563    ----------
564    image : ([P,] M, N) ndarray (uint8, uint16)
565        Input image.
566    footprint : ndarray
567        The neighborhood expressed as an ndarray of 1's and 0's.
568    out : ([P,] M, N) array (same dtype as input)
569        If None, a new array is allocated.
570    mask : ndarray (integer or float), optional
571        Mask array that defines (>0) area of the image included in the local
572        neighborhood. If None, the complete image is used (default).
573    shift_x, shift_y, shift_z : int
574        Offset added to the footprint center point. Shift is bounded to the
575        footprint sizes (center must be inside the given footprint).
576
577    Returns
578    -------
579    out : ([P,] M, N) ndarray (same dtype as input image)
580        Output image.
581
582    Examples
583    --------
584    >>> from skimage import data
585    >>> from skimage.morphology import disk, ball
586    >>> from skimage.filters.rank import mean
587    >>> import numpy as np
588    >>> img = data.camera()
589    >>> rng = np.random.default_rng()
590    >>> volume = rng.integers(0, 255, size=(10,10,10), dtype=np.uint8)
591    >>> avg = mean(img, disk(5))
592    >>> avg_vol = mean(volume, ball(5))
593
594    """
595
596    np_image = np.asanyarray(image)
597    if np_image.ndim == 2:
598        return _apply_scalar_per_pixel(generic_cy._mean, image, footprint,
599                                       out=out, mask=mask,
600                                       shift_x=shift_x, shift_y=shift_y)
601    else:
602        return _apply_scalar_per_pixel_3D(generic_cy._mean_3D, image,
603                                          footprint, out=out, mask=mask,
604                                          shift_x=shift_x, shift_y=shift_y,
605                                          shift_z=shift_z)
606
607
608@deprecate_kwarg(kwarg_mapping={'selem': 'footprint'}, removed_version="1.0",
609                 deprecated_version="0.19")
610def geometric_mean(image, footprint, out=None, mask=None,
611                   shift_x=False, shift_y=False, shift_z=False):
612    """Return local geometric mean of an image.
613
614    Parameters
615    ----------
616    image : ([P,] M, N) ndarray (uint8, uint16)
617        Input image.
618    footprint : ndarray
619        The neighborhood expressed as an ndarray of 1's and 0's.
620    out : ([P,] M, N) array (same dtype as input)
621        If None, a new array is allocated.
622    mask : ndarray (integer or float), optional
623        Mask array that defines (>0) area of the image included in the local
624        neighborhood. If None, the complete image is used (default).
625    shift_x, shift_y, shift_z : int
626        Offset added to the footprint center point. Shift is bounded to the
627        footprint sizes (center must be inside the given footprint).
628
629    Returns
630    -------
631    out : ([P,] M, N) ndarray (same dtype as input image)
632        Output image.
633
634    Examples
635    --------
636    >>> from skimage import data
637    >>> from skimage.morphology import disk, ball
638    >>> from skimage.filters.rank import mean
639    >>> import numpy as np
640    >>> img = data.camera()
641    >>> rng = np.random.default_rng()
642    >>> volume = rng.integers(0, 255, size=(10,10,10), dtype=np.uint8)
643    >>> avg = geometric_mean(img, disk(5))
644    >>> avg_vol = geometric_mean(volume, ball(5))
645
646    References
647    ----------
648    .. [1] Gonzalez, R. C. and Wood, R. E. "Digital Image Processing (3rd Edition)."
649           Prentice-Hall Inc, 2006.
650
651    """
652
653    np_image = np.asanyarray(image)
654    if np_image.ndim == 2:
655        return _apply_scalar_per_pixel(generic_cy._geometric_mean, image,
656                                       footprint, out=out, mask=mask,
657                                       shift_x=shift_x, shift_y=shift_y)
658    else:
659        return _apply_scalar_per_pixel_3D(generic_cy._geometric_mean_3D, image,
660                                          footprint, out=out, mask=mask,
661                                          shift_x=shift_x, shift_y=shift_y,
662                                          shift_z=shift_z)
663
664
665@deprecate_kwarg(kwarg_mapping={'selem': 'footprint'}, removed_version="1.0",
666                 deprecated_version="0.19")
667def subtract_mean(image, footprint, out=None, mask=None,
668                  shift_x=False, shift_y=False, shift_z=False):
669    """Return image subtracted from its local mean.
670
671    Parameters
672    ----------
673    image : ([P,] M, N) ndarray (uint8, uint16)
674        Input image.
675    footprint : ndarray
676        The neighborhood expressed as an ndarray of 1's and 0's.
677    out : ([P,] M, N) array (same dtype as input)
678        If None, a new array is allocated.
679    mask : ndarray (integer or float), optional
680        Mask array that defines (>0) area of the image included in the local
681        neighborhood. If None, the complete image is used (default).
682    shift_x, shift_y, shift_z : int
683        Offset added to the footprint center point. Shift is bounded to the
684        footprint sizes (center must be inside the given footprint).
685
686    Returns
687    -------
688    out : ([P,] M, N) ndarray (same dtype as input image)
689        Output image.
690
691    Notes
692    -----
693    Subtracting the mean value may introduce underflow. To compensate
694    this potential underflow, the obtained difference is downscaled by
695    a factor of 2 and shifted by `n_bins / 2 - 1`, the median value of
696    the local histogram (`n_bins = max(3, image.max()) +1` for 16-bits
697    images and 256 otherwise).
698
699    Examples
700    --------
701    >>> from skimage import data
702    >>> from skimage.morphology import disk, ball
703    >>> from skimage.filters.rank import subtract_mean
704    >>> import numpy as np
705    >>> img = data.camera()
706    >>> rng = np.random.default_rng()
707    >>> volume = rng.integers(0, 255, size=(10,10,10), dtype=np.uint8)
708    >>> out = subtract_mean(img, disk(5))
709    >>> out_vol = subtract_mean(volume, ball(5))
710
711    """
712
713    np_image = np.asanyarray(image)
714    if np_image.ndim == 2:
715        return _apply_scalar_per_pixel(generic_cy._subtract_mean, image,
716                                       footprint, out=out, mask=mask,
717                                       shift_x=shift_x, shift_y=shift_y)
718    else:
719        return _apply_scalar_per_pixel_3D(generic_cy._subtract_mean_3D, image,
720                                          footprint, out=out, mask=mask,
721                                          shift_x=shift_x, shift_y=shift_y,
722                                          shift_z=shift_z)
723
724
725@deprecate_kwarg(kwarg_mapping={'selem': 'footprint'}, removed_version="1.0",
726                 deprecated_version="0.19")
727def median(image, footprint=None, out=None, mask=None,
728           shift_x=False, shift_y=False, shift_z=False):
729    """Return local median of an image.
730
731    Parameters
732    ----------
733    image : ([P,] M, N) ndarray (uint8, uint16)
734        Input image.
735    footprint : ndarray
736        The neighborhood expressed as an ndarray of 1's and 0's. If None, a
737        full square of size 3 is used.
738    out : ([P,] M, N) array (same dtype as input)
739        If None, a new array is allocated.
740    mask : ndarray (integer or float), optional
741        Mask array that defines (>0) area of the image included in the local
742        neighborhood. If None, the complete image is used (default).
743    shift_x, shift_y, shift_z : int
744        Offset added to the footprint center point. Shift is bounded to the
745        footprint sizes (center must be inside the given footprint).
746
747    Returns
748    -------
749    out : ([P,] M, N) ndarray (same dtype as input image)
750        Output image.
751
752    See also
753    --------
754    skimage.filters.median : Implementation of a median filtering which handles
755        images with floating precision.
756
757    Examples
758    --------
759    >>> from skimage import data
760    >>> from skimage.morphology import disk, ball
761    >>> from skimage.filters.rank import median
762    >>> import numpy as np
763    >>> img = data.camera()
764    >>> rng = np.random.default_rng()
765    >>> volume = rng.integers(0, 255, size=(10,10,10), dtype=np.uint8)
766    >>> med = median(img, disk(5))
767    >>> med_vol = median(volume, ball(5))
768
769    """
770
771    np_image = np.asanyarray(image)
772    if footprint is None:
773        footprint = ndi.generate_binary_structure(image.ndim, image.ndim)
774    if np_image.ndim == 2:
775        return _apply_scalar_per_pixel(generic_cy._median, image, footprint,
776                                       out=out, mask=mask,
777                                       shift_x=shift_x, shift_y=shift_y)
778    else:
779        return _apply_scalar_per_pixel_3D(generic_cy._median_3D, image,
780                                          footprint, out=out, mask=mask,
781                                          shift_x=shift_x, shift_y=shift_y,
782                                          shift_z=shift_z)
783
784
785@deprecate_kwarg(kwarg_mapping={'selem': 'footprint'}, removed_version="1.0",
786                 deprecated_version="0.19")
787def minimum(image, footprint, out=None, mask=None,
788            shift_x=False, shift_y=False, shift_z=False):
789    """Return local minimum of an image.
790
791    Parameters
792    ----------
793    image : ([P,] M, N) ndarray (uint8, uint16)
794        Input image.
795    footprint : ndarray
796        The neighborhood expressed as an ndarray of 1's and 0's.
797    out : ([P,] M, N) array (same dtype as input)
798        If None, a new array is allocated.
799    mask : ndarray (integer or float), optional
800        Mask array that defines (>0) area of the image included in the local
801        neighborhood. If None, the complete image is used (default).
802    shift_x, shift_y, shift_z : int
803        Offset added to the footprint center point. Shift is bounded to the
804        footprint sizes (center must be inside the given footprint).
805
806    Returns
807    -------
808    out : ([P,] M, N) ndarray (same dtype as input image)
809        Output image.
810
811    See also
812    --------
813    skimage.morphology.erosion
814
815    Notes
816    -----
817    The lower algorithm complexity makes `skimage.filters.rank.minimum` more
818    efficient for larger images and footprints.
819
820    Examples
821    --------
822    >>> from skimage import data
823    >>> from skimage.morphology import disk, ball
824    >>> from skimage.filters.rank import minimum
825    >>> import numpy as np
826    >>> img = data.camera()
827    >>> rng = np.random.default_rng()
828    >>> volume = rng.integers(0, 255, size=(10,10,10), dtype=np.uint8)
829    >>> out = minimum(img, disk(5))
830    >>> out_vol = minimum(volume, ball(5))
831
832    """
833
834    np_image = np.asanyarray(image)
835    if np_image.ndim == 2:
836        return _apply_scalar_per_pixel(generic_cy._minimum, image, footprint,
837                                       out=out, mask=mask,
838                                       shift_x=shift_x, shift_y=shift_y)
839    else:
840        return _apply_scalar_per_pixel_3D(generic_cy._minimum_3D, image,
841                                          footprint, out=out, mask=mask,
842                                          shift_x=shift_x, shift_y=shift_y,
843                                          shift_z=shift_z)
844
845
846@deprecate_kwarg(kwarg_mapping={'selem': 'footprint'}, removed_version="1.0",
847                 deprecated_version="0.19")
848def modal(image, footprint, out=None, mask=None,
849          shift_x=False, shift_y=False, shift_z=False):
850    """Return local mode of an image.
851
852    The mode is the value that appears most often in the local histogram.
853
854    Parameters
855    ----------
856    image : ([P,] M, N) ndarray (uint8, uint16)
857        Input image.
858    footprint : ndarray
859        The neighborhood expressed as an ndarray of 1's and 0's.
860    out : ([P,] M, N) array (same dtype as input)
861        If None, a new array is allocated.
862    mask : ndarray (integer or float), optional
863        Mask array that defines (>0) area of the image included in the local
864        neighborhood. If None, the complete image is used (default).
865    shift_x, shift_y, shift_z : int
866        Offset added to the footprint center point. Shift is bounded to the
867        footprint sizes (center must be inside the given footprint).
868
869    Returns
870    -------
871    out : ([P,] M, N) ndarray (same dtype as input image)
872        Output image.
873
874    Examples
875    --------
876    >>> from skimage import data
877    >>> from skimage.morphology import disk, ball
878    >>> from skimage.filters.rank import modal
879    >>> import numpy as np
880    >>> img = data.camera()
881    >>> rng = np.random.default_rng()
882    >>> volume = rng.integers(0, 255, size=(10,10,10), dtype=np.uint8)
883    >>> out = modal(img, disk(5))
884    >>> out_vol = modal(volume, ball(5))
885
886    """
887
888    np_image = np.asanyarray(image)
889    if np_image.ndim == 2:
890        return _apply_scalar_per_pixel(generic_cy._modal, image, footprint,
891                                       out=out, mask=mask,
892                                       shift_x=shift_x, shift_y=shift_y)
893    else:
894        return _apply_scalar_per_pixel_3D(generic_cy._modal_3D, image,
895                                          footprint, out=out, mask=mask,
896                                          shift_x=shift_x, shift_y=shift_y,
897                                          shift_z=shift_z)
898
899
900@deprecate_kwarg(kwarg_mapping={'selem': 'footprint'}, removed_version="1.0",
901                 deprecated_version="0.19")
902def enhance_contrast(image, footprint, out=None, mask=None,
903                     shift_x=False, shift_y=False, shift_z=False):
904    """Enhance contrast of an image.
905
906    This replaces each pixel by the local maximum if the pixel gray value is
907    closer to the local maximum than the local minimum. Otherwise it is
908    replaced by the local minimum.
909
910    Parameters
911    ----------
912    image : ([P,] M, N) ndarray (uint8, uint16)
913        Input image.
914    footprint : ndarray
915        The neighborhood expressed as an ndarray of 1's and 0's.
916    out : ([P,] M, N) array (same dtype as input)
917        If None, a new array is allocated.
918    mask : ndarray (integer or float), optional
919        Mask array that defines (>0) area of the image included in the local
920        neighborhood. If None, the complete image is used (default).
921    shift_x, shift_y, shift_z : int
922        Offset added to the footprint center point. Shift is bounded to the
923        footprint sizes (center must be inside the given footprint).
924
925    Returns
926    -------
927    out : ([P,] M, N) ndarray (same dtype as input image)
928        Output image
929
930    Examples
931    --------
932    >>> from skimage import data
933    >>> from skimage.morphology import disk, ball
934    >>> from skimage.filters.rank import enhance_contrast
935    >>> import numpy as np
936    >>> img = data.camera()
937    >>> rng = np.random.default_rng()
938    >>> volume = rng.integers(0, 255, size=(10,10,10), dtype=np.uint8)
939    >>> out = enhance_contrast(img, disk(5))
940    >>> out_vol = enhance_contrast(volume, ball(5))
941
942    """
943
944    np_image = np.asanyarray(image)
945    if np_image.ndim == 2:
946        return _apply_scalar_per_pixel(generic_cy._enhance_contrast, image,
947                                       footprint, out=out, mask=mask,
948                                       shift_x=shift_x, shift_y=shift_y)
949    else:
950        return _apply_scalar_per_pixel_3D(generic_cy._enhance_contrast_3D,
951                                          image, footprint, out=out, mask=mask,
952                                          shift_x=shift_x, shift_y=shift_y,
953                                          shift_z=shift_z)
954
955
956@deprecate_kwarg(kwarg_mapping={'selem': 'footprint'}, removed_version="1.0",
957                 deprecated_version="0.19")
958def pop(image, footprint, out=None, mask=None,
959        shift_x=False, shift_y=False, shift_z=False):
960    """Return the local number (population) of pixels.
961
962    The number of pixels is defined as the number of pixels which are included
963    in the footprint and the mask.
964
965    Parameters
966    ----------
967    image : ([P,] M, N) ndarray (uint8, uint16)
968        Input image.
969    footprint : ndarray
970        The neighborhood expressed as an ndarray of 1's and 0's.
971    out : ([P,] M, N) array (same dtype as input)
972        If None, a new array is allocated.
973    mask : ndarray (integer or float), optional
974        Mask array that defines (>0) area of the image included in the local
975        neighborhood. If None, the complete image is used (default).
976    shift_x, shift_y, shift_z : int
977        Offset added to the footprint center point. Shift is bounded to the
978        footprint sizes (center must be inside the given footprint).
979
980    Returns
981    -------
982    out : ([P,] M, N) ndarray (same dtype as input image)
983        Output image.
984
985    Examples
986    --------
987    >>> from skimage.morphology import square, cube # Need to add 3D example
988    >>> import skimage.filters.rank as rank
989    >>> img = 255 * np.array([[0, 0, 0, 0, 0],
990    ...                       [0, 1, 1, 1, 0],
991    ...                       [0, 1, 1, 1, 0],
992    ...                       [0, 1, 1, 1, 0],
993    ...                       [0, 0, 0, 0, 0]], dtype=np.uint8)
994    >>> rank.pop(img, square(3))
995    array([[4, 6, 6, 6, 4],
996           [6, 9, 9, 9, 6],
997           [6, 9, 9, 9, 6],
998           [6, 9, 9, 9, 6],
999           [4, 6, 6, 6, 4]], dtype=uint8)
1000
1001    """
1002
1003    np_image = np.asanyarray(image)
1004    if np_image.ndim == 2:
1005        return _apply_scalar_per_pixel(generic_cy._pop, image, footprint,
1006                                       out=out, mask=mask,
1007                                       shift_x=shift_x, shift_y=shift_y)
1008    else:
1009        return _apply_scalar_per_pixel_3D(generic_cy._pop_3D, image,
1010                                          footprint, out=out, mask=mask,
1011                                          shift_x=shift_x, shift_y=shift_y,
1012                                          shift_z=shift_z)
1013
1014
1015@deprecate_kwarg(kwarg_mapping={'selem': 'footprint'}, removed_version="1.0",
1016                 deprecated_version="0.19")
1017def sum(image, footprint, out=None, mask=None,
1018        shift_x=False, shift_y=False, shift_z=False):
1019    """Return the local sum of pixels.
1020
1021    Note that the sum may overflow depending on the data type of the input
1022    array.
1023
1024    Parameters
1025    ----------
1026    image : ([P,] M, N) ndarray (uint8, uint16)
1027        Input image.
1028    footprint : ndarray
1029        The neighborhood expressed as an ndarray of 1's and 0's.
1030    out : ([P,] M, N) array (same dtype as input)
1031        If None, a new array is allocated.
1032    mask : ndarray (integer or float), optional
1033        Mask array that defines (>0) area of the image included in the local
1034        neighborhood. If None, the complete image is used (default).
1035    shift_x, shift_y, shift_z : int
1036        Offset added to the footprint center point. Shift is bounded to the
1037        footprint sizes (center must be inside the given footprint).
1038
1039    Returns
1040    -------
1041    out : ([P,] M, N) ndarray (same dtype as input image)
1042        Output image.
1043
1044    Examples
1045    --------
1046    >>> from skimage.morphology import square, cube # Need to add 3D example
1047    >>> import skimage.filters.rank as rank         # Cube seems to fail but
1048    >>> img = np.array([[0, 0, 0, 0, 0],            # Ball can pass
1049    ...                 [0, 1, 1, 1, 0],
1050    ...                 [0, 1, 1, 1, 0],
1051    ...                 [0, 1, 1, 1, 0],
1052    ...                 [0, 0, 0, 0, 0]], dtype=np.uint8)
1053    >>> rank.sum(img, square(3))
1054    array([[1, 2, 3, 2, 1],
1055           [2, 4, 6, 4, 2],
1056           [3, 6, 9, 6, 3],
1057           [2, 4, 6, 4, 2],
1058           [1, 2, 3, 2, 1]], dtype=uint8)
1059
1060    """
1061
1062    np_image = np.asanyarray(image)
1063    if np_image.ndim == 2:
1064        return _apply_scalar_per_pixel(generic_cy._sum, image, footprint,
1065                                       out=out, mask=mask,
1066                                       shift_x=shift_x, shift_y=shift_y)
1067    else:
1068        return _apply_scalar_per_pixel_3D(generic_cy._sum_3D, image,
1069                                          footprint, out=out, mask=mask,
1070                                          shift_x=shift_x, shift_y=shift_y,
1071                                          shift_z=shift_z)
1072
1073
1074@deprecate_kwarg(kwarg_mapping={'selem': 'footprint'}, removed_version="1.0",
1075                 deprecated_version="0.19")
1076def threshold(image, footprint, out=None, mask=None,
1077              shift_x=False, shift_y=False, shift_z=False):
1078    """Local threshold of an image.
1079
1080    The resulting binary mask is True if the gray value of the center pixel is
1081    greater than the local mean.
1082
1083    Parameters
1084    ----------
1085    image : ([P,] M, N) ndarray (uint8, uint16)
1086        Input image.
1087    footprint : ndarray
1088        The neighborhood expressed as an ndarray of 1's and 0's.
1089    out : ([P,] M, N) array (same dtype as input)
1090        If None, a new array is allocated.
1091    mask : ndarray (integer or float), optional
1092        Mask array that defines (>0) area of the image included in the local
1093        neighborhood. If None, the complete image is used (default).
1094    shift_x, shift_y, shift_z : int
1095        Offset added to the footprint center point. Shift is bounded to the
1096        footprint sizes (center must be inside the given footprint).
1097
1098    Returns
1099    -------
1100    out : ([P,] M, N) ndarray (same dtype as input image)
1101        Output image.
1102
1103    Examples
1104    --------
1105    >>> from skimage.morphology import square, cube # Need to add 3D example
1106    >>> from skimage.filters.rank import threshold
1107    >>> img = 255 * np.array([[0, 0, 0, 0, 0],
1108    ...                       [0, 1, 1, 1, 0],
1109    ...                       [0, 1, 1, 1, 0],
1110    ...                       [0, 1, 1, 1, 0],
1111    ...                       [0, 0, 0, 0, 0]], dtype=np.uint8)
1112    >>> threshold(img, square(3))
1113    array([[0, 0, 0, 0, 0],
1114           [0, 1, 1, 1, 0],
1115           [0, 1, 0, 1, 0],
1116           [0, 1, 1, 1, 0],
1117           [0, 0, 0, 0, 0]], dtype=uint8)
1118
1119    """
1120
1121    np_image = np.asanyarray(image)
1122    if np_image.ndim == 2:
1123        return _apply_scalar_per_pixel(generic_cy._threshold, image, footprint,
1124                                       out=out, mask=mask,
1125                                       shift_x=shift_x, shift_y=shift_y)
1126    else:
1127        return _apply_scalar_per_pixel_3D(generic_cy._threshold_3D, image,
1128                                          footprint, out=out, mask=mask,
1129                                          shift_x=shift_x, shift_y=shift_y,
1130                                          shift_z=shift_z)
1131
1132
1133@deprecate_kwarg(kwarg_mapping={'selem': 'footprint'}, removed_version="1.0",
1134                 deprecated_version="0.19")
1135def noise_filter(image, footprint, out=None, mask=None,
1136                 shift_x=False, shift_y=False, shift_z=False):
1137    """Noise feature.
1138
1139    Parameters
1140    ----------
1141    image : ([P,] M, N) ndarray (uint8, uint16)
1142        Input image.
1143    footprint : ndarray
1144        The neighborhood expressed as an ndarray of 1's and 0's.
1145    out : ([P,] M, N) array (same dtype as input)
1146        If None, a new array is allocated.
1147    mask : ndarray (integer or float), optional
1148        Mask array that defines (>0) area of the image included in the local
1149        neighborhood. If None, the complete image is used (default).
1150    shift_x, shift_y, shift_z : int
1151        Offset added to the footprint center point. Shift is bounded to the
1152        footprint sizes (center must be inside the given footprint).
1153
1154    References
1155    ----------
1156    .. [1] N. Hashimoto et al. Referenceless image quality evaluation
1157                     for whole slide imaging. J Pathol Inform 2012;3:9.
1158
1159    Returns
1160    -------
1161    out : ([P,] M, N) ndarray (same dtype as input image)
1162        Output image.
1163
1164    Examples
1165    --------
1166    >>> from skimage import data
1167    >>> from skimage.morphology import disk, ball
1168    >>> from skimage.filters.rank import noise_filter
1169    >>> import numpy as np
1170    >>> img = data.camera()
1171    >>> rng = np.random.default_rng()
1172    >>> volume = rng.integers(0, 255, size=(10,10,10), dtype=np.uint8)
1173    >>> out = noise_filter(img, disk(5))
1174    >>> out_vol = noise_filter(volume, ball(5))
1175
1176    """
1177
1178    np_image = np.asanyarray(image)
1179    if np_image.ndim == 2:
1180        # ensure that the central pixel in the footprint is empty
1181        centre_r = int(footprint.shape[0] / 2) + shift_y
1182        centre_c = int(footprint.shape[1] / 2) + shift_x
1183        # make a local copy
1184        footprint_cpy = footprint.copy()
1185        footprint_cpy[centre_r, centre_c] = 0
1186
1187        return _apply_scalar_per_pixel(generic_cy._noise_filter, image,
1188                                       footprint_cpy, out=out, mask=mask,
1189                                       shift_x=shift_x, shift_y=shift_y)
1190    else:
1191        # ensure that the central pixel in the footprint is empty
1192        centre_r = int(footprint.shape[0] / 2) + shift_y
1193        centre_c = int(footprint.shape[1] / 2) + shift_x
1194        centre_z = int(footprint.shape[2] / 2) + shift_z
1195        # make a local copy
1196        footprint_cpy = footprint.copy()
1197        footprint_cpy[centre_r, centre_c, centre_z] = 0
1198
1199        return _apply_scalar_per_pixel_3D(generic_cy._noise_filter_3D,
1200                                          image, footprint_cpy, out=out,
1201                                          mask=mask, shift_x=shift_x,
1202                                          shift_y=shift_y, shift_z=shift_z)
1203
1204
1205@deprecate_kwarg(kwarg_mapping={'selem': 'footprint'}, removed_version="1.0",
1206                 deprecated_version="0.19")
1207def entropy(image, footprint, out=None, mask=None,
1208            shift_x=False, shift_y=False, shift_z=False):
1209    """Local entropy.
1210
1211    The entropy is computed using base 2 logarithm i.e. the filter returns the
1212    minimum number of bits needed to encode the local gray level
1213    distribution.
1214
1215    Parameters
1216    ----------
1217    image : ([P,] M, N) ndarray (uint8, uint16)
1218        Input image.
1219    footprint : ndarray
1220        The neighborhood expressed as an ndarray of 1's and 0's.
1221    out : ([P,] M, N) array (same dtype as input)
1222        If None, a new array is allocated.
1223    mask : ndarray (integer or float), optional
1224        Mask array that defines (>0) area of the image included in the local
1225        neighborhood. If None, the complete image is used (default).
1226    shift_x, shift_y, shift_z : int
1227        Offset added to the footprint center point. Shift is bounded to the
1228        footprint sizes (center must be inside the given footprint).
1229
1230    Returns
1231    -------
1232    out : ([P,] M, N) ndarray (float)
1233        Output image.
1234
1235    References
1236    ----------
1237    .. [1] `https://en.wikipedia.org/wiki/Entropy_(information_theory) <https://en.wikipedia.org/wiki/Entropy_(information_theory)>`_
1238
1239    Examples
1240    --------
1241    >>> from skimage import data
1242    >>> from skimage.filters.rank import entropy
1243    >>> from skimage.morphology import disk, ball
1244    >>> import numpy as np
1245    >>> img = data.camera()
1246    >>> rng = np.random.default_rng()
1247    >>> volume = rng.integers(0, 255, size=(10,10,10), dtype=np.uint8)
1248    >>> ent = entropy(img, disk(5))
1249    >>> ent_vol = entropy(volume, ball(5))
1250
1251    """
1252
1253    np_image = np.asanyarray(image)
1254    if np_image.ndim == 2:
1255        return _apply_scalar_per_pixel(generic_cy._entropy, image, footprint,
1256                                       out=out, mask=mask,
1257                                       shift_x=shift_x, shift_y=shift_y,
1258                                       out_dtype=np.double)
1259    else:
1260        return _apply_scalar_per_pixel_3D(generic_cy._entropy_3D, image,
1261                                          footprint, out=out, mask=mask,
1262                                          shift_x=shift_x, shift_y=shift_y,
1263                                          shift_z=shift_z, out_dtype=np.double)
1264
1265
1266@deprecate_kwarg(kwarg_mapping={'selem': 'footprint'}, removed_version="1.0",
1267                 deprecated_version="0.19")
1268def otsu(image, footprint, out=None, mask=None,
1269         shift_x=False, shift_y=False, shift_z=False):
1270    """Local Otsu's threshold value for each pixel.
1271
1272    Parameters
1273    ----------
1274    image : ([P,] M, N) ndarray (uint8, uint16)
1275        Input image.
1276    footprint : ndarray
1277        The neighborhood expressed as an ndarray of 1's and 0's.
1278    out : ([P,] M, N) array (same dtype as input)
1279        If None, a new array is allocated.
1280    mask : ndarray (integer or float), optional
1281        Mask array that defines (>0) area of the image included in the local
1282        neighborhood. If None, the complete image is used (default).
1283    shift_x, shift_y, shift_z : int
1284        Offset added to the footprint center point. Shift is bounded to the
1285        footprint sizes (center must be inside the given footprint).
1286
1287    Returns
1288    -------
1289    out : ([P,] M, N) ndarray (same dtype as input image)
1290        Output image.
1291
1292    References
1293    ----------
1294    .. [1] https://en.wikipedia.org/wiki/Otsu's_method
1295
1296    Examples
1297    --------
1298    >>> from skimage import data
1299    >>> from skimage.filters.rank import otsu
1300    >>> from skimage.morphology import disk, ball
1301    >>> import numpy as np
1302    >>> img = data.camera()
1303    >>> rng = np.random.default_rng()
1304    >>> volume = rng.integers(0, 255, size=(10,10,10), dtype=np.uint8)
1305    >>> local_otsu = otsu(img, disk(5))
1306    >>> thresh_image = img >= local_otsu
1307    >>> local_otsu_vol = otsu(volume, ball(5))
1308    >>> thresh_image_vol = volume >= local_otsu_vol
1309
1310    """
1311
1312    np_image = np.asanyarray(image)
1313    if np_image.ndim == 2:
1314        return _apply_scalar_per_pixel(generic_cy._otsu, image, footprint,
1315                                       out=out, mask=mask,
1316                                       shift_x=shift_x, shift_y=shift_y)
1317    else:
1318        return _apply_scalar_per_pixel_3D(generic_cy._otsu_3D, image,
1319                                          footprint, out=out, mask=mask,
1320                                          shift_x=shift_x, shift_y=shift_y,
1321                                          shift_z=shift_z)
1322
1323
1324@deprecate_kwarg(kwarg_mapping={'selem': 'footprint'}, removed_version="1.0",
1325                 deprecated_version="0.19")
1326def windowed_histogram(image, footprint, out=None, mask=None,
1327                       shift_x=False, shift_y=False, n_bins=None):
1328    """Normalized sliding window histogram
1329
1330    Parameters
1331    ----------
1332    image : 2-D array (integer or float)
1333        Input image.
1334    footprint : 2-D array (integer or float)
1335        The neighborhood expressed as a 2-D array of 1's and 0's.
1336    out : 2-D array (integer or float), optional
1337        If None, a new array is allocated.
1338    mask : ndarray (integer or float), optional
1339        Mask array that defines (>0) area of the image included in the local
1340        neighborhood. If None, the complete image is used (default).
1341    shift_x, shift_y : int, optional
1342        Offset added to the footprint center point. Shift is bounded to the
1343        footprint sizes (center must be inside the given footprint).
1344    n_bins : int or None
1345        The number of histogram bins. Will default to ``image.max() + 1``
1346        if None is passed.
1347
1348    Returns
1349    -------
1350    out : 3-D array (float)
1351        Array of dimensions (H,W,N), where (H,W) are the dimensions of the
1352        input image and N is n_bins or ``image.max() + 1`` if no value is
1353        provided as a parameter. Effectively, each pixel is a N-D feature
1354        vector that is the histogram. The sum of the elements in the feature
1355        vector will be 1, unless no pixels in the window were covered by both
1356        footprint and mask, in which case all elements will be 0.
1357
1358    Examples
1359    --------
1360    >>> from skimage import data
1361    >>> from skimage.filters.rank import windowed_histogram
1362    >>> from skimage.morphology import disk, ball
1363    >>> import numpy as np
1364    >>> img = data.camera()
1365    >>> rng = np.random.default_rng()
1366    >>> volume = rng.integers(0, 255, size=(10,10,10), dtype=np.uint8)
1367    >>> hist_img = windowed_histogram(img, disk(5))
1368
1369    """
1370
1371    if n_bins is None:
1372        n_bins = int(image.max()) + 1
1373
1374    return _apply_vector_per_pixel(generic_cy._windowed_hist, image, footprint,
1375                                   out=out, mask=mask,
1376                                   shift_x=shift_x, shift_y=shift_y,
1377                                   out_dtype=np.double,
1378                                   pixel_size=n_bins)
1379
1380
1381@deprecate_kwarg(kwarg_mapping={'selem': 'footprint'}, removed_version="1.0",
1382                 deprecated_version="0.19")
1383def majority(image, footprint, *, out=None, mask=None,
1384             shift_x=False, shift_y=False, shift_z=False):
1385    """Assign to each pixel the most common value within its neighborhood.
1386
1387    Parameters
1388    ----------
1389    image : ndarray
1390        Image array (uint8, uint16 array).
1391    footprint : 2-D array (integer or float)
1392        The neighborhood expressed as a 2-D array of 1's and 0's.
1393    out : ndarray (integer or float), optional
1394        If None, a new array will be allocated.
1395    mask : ndarray (integer or float), optional
1396        Mask array that defines (>0) area of the image included in the local
1397        neighborhood. If None, the complete image is used (default).
1398    shift_x, shift_y : int, optional
1399        Offset added to the footprint center point. Shift is bounded to the
1400        footprint sizes (center must be inside the given footprint).
1401
1402    Returns
1403    -------
1404    out : 2-D array (same dtype as input image)
1405        Output image.
1406
1407    Examples
1408    --------
1409    >>> from skimage import data
1410    >>> from skimage.filters.rank import majority
1411    >>> from skimage.morphology import disk, ball
1412    >>> import numpy as np
1413    >>> img = data.camera()
1414    >>> rng = np.random.default_rng()
1415    >>> volume = rng.integers(0, 255, size=(10,10,10), dtype=np.uint8)
1416    >>> maj_img = majority(img, disk(5))
1417    >>> maj_img_vol = majority(volume, ball(5))
1418
1419    """
1420
1421    np_image = np.asanyarray(image)
1422    if np_image.ndim == 2:
1423        return _apply_scalar_per_pixel(generic_cy._majority, image,
1424                                       footprint, out=out, mask=mask,
1425                                       shift_x=shift_x, shift_y=shift_y)
1426    else:
1427        return _apply_scalar_per_pixel_3D(generic_cy._majority_3D,
1428                                          image, footprint, out=out, mask=mask,
1429                                          shift_x=shift_x, shift_y=shift_y,
1430                                          shift_z=shift_z)
1431