#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (c) 2016 Jérémie DECOCK (http://www.jdhp.org)
# This script is provided under the terms and conditions of the MIT license:
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
__all__ = ['get_pixels_clusters',
'filter_pixels_clusters',
'filter_pixels_clusters_stats',
'number_of_pixels_clusters']
import numpy as np
import scipy.ndimage as ndimage
"""Pixel clusters filtering.
Notes
-----
Reference: https://docs.scipy.org/doc/scipy-0.16.0/reference/generated/scipy.ndimage.measurements.label.html
"""
[docs]def get_pixels_clusters(array, threshold=0):
"""Return pixels clusters in the given image ``array``.
Parameters
----------
array : array_like
The input image where pixels clusters are searched. ``array`` should
be a 2D Numpy array.
A pixel cluster is a group of consecutive pixels having a value
strictly greater than 0.
Consecutive pixels are vertically or horizontally connected pixels,
i.e. vertical or horizontal neighbors
pixels; diagonal neighbors pixels are ignored.
threshold : float
A filtering is applied to the ``array`` image before pixels clusters
are searched.
All pixels strictly lower than ``threshold`` in ``array`` are set to 0.
If ``threshold`` value is ``None`` or lower than 0 then ``threshold``
is automatically set to 0 before the filtering is applied.
Returns
-------
filtered_array : array_like
The ``array`` image after the pre-processing filtering i.e. with all
pixels below ``threshold`` put to 0 (may contain ``NaN`` values).
label_array : array_like
An integer Numpy array where each unique pixels cluster in ``input``
has a unique ID.
This array defines the pixels cluster ID each pixel belongs to
This array never contains ``NaN`` values.
num_clusters : int
The number of pixels clusters in the ``array`` image.
Examples
--------
Lets search pixels clusters in the following ``img`` image:
>>> import numpy as np
>>> img = np.array([[0, 0, 1, 3, 0, -1],
... [0, 0, 0, 5, 0, 0],
... [4, 3, 0, 0, 1, 0],
... [0, 0, 0, 8, 0, 0]])
>>> filtered_array, label_array, num_clusters = get_pixels_clusters(img)
The default filtering threshold is applied here;
the top right pixel is put to 0 before pixels clusters are searched:
>>> print(filtered_array)
... # doctest: +NORMALIZE_WHITESPACE
[[ 0. 0. 1. 3. 0. 0.]
[ 0. 0. 0. 5. 0. 0.]
[ 4. 3. 0. 0. 1. 0.]
[ 0. 0. 0. 8. 0. 0.]]
This image contains 4 pixels clusters:
>>> print(num_clusters)
4
Each of the 4 clusters are labeled with a different integer:
>>> print(label_array)
[[0 0 1 1 0 0]
[0 0 0 1 0 0]
[2 2 0 0 3 0]
[0 0 0 4 0 0]]
See Also
--------
scipy.ndimage.measurements.label
The underlying function used for pixels clusters detection
(https://docs.scipy.org/doc/scipy-0.16.0/reference/generated/scipy.ndimage.measurements.label.html)
"""
if threshold is None:
threshold = 0.
array = array.astype('float64', copy=True)
filtered_array = np.copy(array)
# Put NaN pixels to 0
# This is OK as long as it is made temporary and internally to avoid issues
# with scipy
filtered_array[np.isnan(filtered_array)] = 0.
# Put to 0 pixels that are below 'threshold'
filtered_array[filtered_array < threshold] = 0.
mask = filtered_array > 0
# Detect pixels clusters (named "label" in scipy)
label_array, num_clusters = ndimage.label(mask) #, structure=np.ones((5, 5)))
# Put back NaN in filtered_array (required to avoid bugs in others
# functions e.g. incoherent dimensions with pixels_positions).
filtered_array[np.isnan(array)] = np.nan
return filtered_array, label_array, num_clusters
[docs]def filter_pixels_clusters(array, threshold=0):
"""Keep only pixels belonging to the largest cluster of pixels and put all others pixels to 0.
Parameters
----------
array : array_like
The input image to filter. ``array`` should be a 2D Numpy array.
threshold : float
A filtering is applied to the ``array`` image before pixels clusters
are searched.
See ``get_pixels_clusters`` documentation for more details.
Returns
-------
Numpy array
The input image ``array`` where only pixels belonging to the largest
cluster of pixels are kept and where all others pixels are put to 0.
Examples
--------
Lets search pixels clusters in the following ``img`` image:
>>> import numpy as np
>>> img = np.array([[0, 0, 1, 3, 0, -1],
... [0, 0, 0, 5, 0, 0],
... [4, 3, 0, 0, 1, 0],
... [0, 0, 0, 8, 0, 0]])
>>> filtered_array = filter_pixels_clusters(img)
This image contains 4 pixels clusters.
Only the biggest one is kept:
>>> print(filtered_array)
... # doctest: +NORMALIZE_WHITESPACE
[[ 0. 0. 1. 3. 0. 0.]
[ 0. 0. 0. 5. 0. 0.]
[ 0. 0. 0. 0. 0. 0.]
[ 0. 0. 0. 0. 0. 0.]]
Notes
-----
See ``get_pixels_clusters`` documentation for more details.
See Also
--------
get_pixels_clusters
"""
array = array.astype('float64', copy=True)
filtered_array, label_array, num_clusters = get_pixels_clusters(array, threshold)
# Put NaN pixels to -inf
# This is OK as long as it is made temporary and internally to avoid issues
# with scipy
filtered_array[np.isnan(filtered_array)] = -float('inf')
# Count the number of pixels for each island
num_pixels_per_island = ndimage.sum(filtered_array, label_array, range(num_clusters + 1))
# Only keep the biggest island
mask_biggest_island = num_pixels_per_island < np.max(num_pixels_per_island)
remove_pixel = mask_biggest_island[label_array]
filtered_array[remove_pixel] = 0
# Put back NaN in filtered_array (required to avoid bugs in others
# functions (e.g. uncoherent dimensions with pixels_positions).
filtered_array[np.isnan(array)] = np.nan
return filtered_array
[docs]def filter_pixels_clusters_stats(array, threshold=0):
"""Return statistics about *pixels clusters* in the given image ``array``.
Parameters
----------
array : array_like
The image to analyse.
threshold : float
A filtering is applied to the ``array`` image before pixels clusters
are searched.
See ``get_pixels_clusters`` documentation for more details.
Returns
-------
delta_value : float
The sum of pixels value removed if ``filter_pixels_clusters`` is applied
on the image ``array``.
delta_abs_value : float
The sum of the absolute value of pixels removed if ``filter_pixels_clusters``
is applied on the image ``array``.
delta_num_pixels : int
The number of pixel put to 0 if ``filter_pixels_clusters`` is applied
on the image ``array``.
Notes
-----
See ``get_pixels_clusters`` documentation for more details.
Examples
--------
Lets check stats about pixels clusters in the following ``img`` image:
>>> import numpy as np
>>> img = np.array([[0, 0, 1, 3, 0, -1],
... [0, 0, 0, 5, 0, 0],
... [4, 3, 0, 0, 1, 0],
... [0, 0, 0, 8, 0, 0]])
>>> delta_value, delta_abs_value, delta_num_pixels = filter_pixels_clusters_stats(img)
After filtering, the sum of removed pixels is 15:
>>> print(delta_value)
15.0
After filtering, the sum of the absolute values of removed pixels is 17:
>>> print(delta_abs_value)
17.0
After filtering, 5 pixels have been put to 0:
>>> print(delta_num_pixels)
5
See Also
--------
get_pixels_clusters
"""
array = array.astype('float64', copy=True)
filtered_array = filter_pixels_clusters(array, threshold=threshold)
delta_value = np.nansum(array - filtered_array)
delta_abs_value = np.nansum(np.abs(array - filtered_array))
array[np.isfinite(array) & (array != 0)] = 1 # May genereate warnings on NaN values
filtered_array[np.isfinite(filtered_array) & (filtered_array != 0)] = 1 # May genereate warnings on NaN values
delta_num_pixels = np.nansum(array - filtered_array)
return float(delta_value), float(delta_abs_value), int(delta_num_pixels)
[docs]def number_of_pixels_clusters(array, threshold=0):
"""Return the number of *pixels clusters* in the given image ``array``.
Parameters
----------
array : array_like
The image to analyse.
threshold : float
A filtering is applied to the ``array`` image before pixels clusters
are searched.
See ``get_pixels_clusters`` documentation for more details.
Returns
-------
int
The number of pixel clusters in the image ``array``.
Examples
--------
Lets count pixels clusters in the following ``img`` image:
>>> import numpy as np
>>> img = np.array([[0, 0, 1, 3, 0, -1],
... [0, 0, 0, 5, 0, 0],
... [4, 3, 0, 0, 1, 0],
... [0, 0, 0, 8, 0, 0]])
>>> num_clusters = number_of_pixels_clusters(img)
This image contains 4 pixels clusters:
>>> print(num_clusters)
4
Notes
-----
See ``get_pixels_clusters`` documentation for more details.
See Also
--------
get_pixels_clusters
"""
filtered_array, label_array, num_labels = get_pixels_clusters(array, threshold)
return num_labels