rm CondaPkg environment
This commit is contained in:
@@ -1,328 +0,0 @@
|
||||
from math import sqrt
|
||||
import numpy as np
|
||||
from scipy import ndimage as ndi
|
||||
|
||||
|
||||
STREL_4 = np.array([[0, 1, 0],
|
||||
[1, 1, 1],
|
||||
[0, 1, 0]], dtype=np.uint8)
|
||||
STREL_8 = np.ones((3, 3), dtype=np.uint8)
|
||||
|
||||
|
||||
# Coefficients from
|
||||
# Ohser J., Nagel W., Schladitz K. (2002) The Euler Number of Discretized Sets
|
||||
# - On the Choice of Adjacency in Homogeneous Lattices.
|
||||
# In: Mecke K., Stoyan D. (eds) Morphology of Condensed Matter. Lecture Notes
|
||||
# in Physics, vol 600. Springer, Berlin, Heidelberg.
|
||||
# The value of coefficients correspond to the contributions to the Euler number
|
||||
# of specific voxel configurations, which are themselves encoded thanks to a
|
||||
# LUT. Computing the Euler number from the addition of the contributions of
|
||||
# local configurations is possible thanks to an integral geometry formula
|
||||
# (see the paper by Ohser et al. for more details).
|
||||
EULER_COEFS2D_4 = [0, 1, 0, 0, 0, 0, 0, -1, 0, 1, 0, 0, 0, 0, 0, 0]
|
||||
EULER_COEFS2D_8 = [0, 0, 0, 0, 0, 0, -1, 0, 1, 0, 0, 0, 0, 0, -1, 0]
|
||||
EULER_COEFS3D_26 = np.array([0, 1, 1, 0, 1, 0, -2, -1,
|
||||
1, -2, 0, -1, 0, -1, -1, 0,
|
||||
1, 0, -2, -1, -2, -1, -1, -2,
|
||||
-6, -3, -3, -2, -3, -2, 0, -1,
|
||||
1, -2, 0, -1, -6, -3, -3, -2,
|
||||
-2, -1, -1, -2, -3, 0, -2, -1,
|
||||
0, -1, -1, 0, -3, -2, 0, -1,
|
||||
-3, 0, -2, -1, 0, 1, 1, 0,
|
||||
1, -2, -6, -3, 0, -1, -3, -2,
|
||||
-2, -1, -3, 0, -1, -2, -2, -1,
|
||||
0, -1, -3, -2, -1, 0, 0, -1,
|
||||
-3, 0, 0, 1, -2, -1, 1, 0,
|
||||
-2, -1, -3, 0, -3, 0, 0, 1,
|
||||
-1, 4, 0, 3, 0, 3, 1, 2,
|
||||
-1, -2, -2, -1, -2, -1, 1,
|
||||
0, 0, 3, 1, 2, 1, 2, 2, 1,
|
||||
1, -6, -2, -3, -2, -3, -1, 0,
|
||||
0, -3, -1, -2, -1, -2, -2, -1,
|
||||
-2, -3, -1, 0, -1, 0, 4, 3,
|
||||
-3, 0, 0, 1, 0, 1, 3, 2,
|
||||
0, -3, -1, -2, -3, 0, 0, 1,
|
||||
-1, 0, 0, -1, -2, 1, -1, 0,
|
||||
-1, -2, -2, -1, 0, 1, 3, 2,
|
||||
-2, 1, -1, 0, 1, 2, 2, 1,
|
||||
0, -3, -3, 0, -1, -2, 0, 1,
|
||||
-1, 0, -2, 1, 0, -1, -1, 0,
|
||||
-1, -2, 0, 1, -2, -1, 3, 2,
|
||||
-2, 1, 1, 2, -1, 0, 2, 1,
|
||||
-1, 0, -2, 1, -2, 1, 1, 2,
|
||||
-2, 3, -1, 2, -1, 2, 0, 1,
|
||||
0, -1, -1, 0, -1, 0, 2, 1,
|
||||
-1, 2, 0, 1, 0, 1, 1, 0, ])
|
||||
|
||||
|
||||
def euler_number(image, connectivity=None):
|
||||
"""Calculate the Euler characteristic in binary image.
|
||||
|
||||
For 2D objects, the Euler number is the number of objects minus the number
|
||||
of holes. For 3D objects, the Euler number is obtained as the number of
|
||||
objects plus the number of holes, minus the number of tunnels, or loops.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image: (N, M) ndarray or (N, M, D) ndarray.
|
||||
2D or 3D images.
|
||||
If image is not binary, all values strictly greater than zero
|
||||
are considered as the object.
|
||||
connectivity : int, optional
|
||||
Maximum number of orthogonal hops to consider a pixel/voxel
|
||||
as a neighbor.
|
||||
Accepted values are ranging from 1 to input.ndim. If ``None``, a full
|
||||
connectivity of ``input.ndim`` is used.
|
||||
4 or 8 neighborhoods are defined for 2D images (connectivity 1 and 2,
|
||||
respectively).
|
||||
6 or 26 neighborhoods are defined for 3D images, (connectivity 1 and 3,
|
||||
respectively). Connectivity 2 is not defined.
|
||||
|
||||
Returns
|
||||
-------
|
||||
euler_number : int
|
||||
Euler characteristic of the set of all objects in the image.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The Euler characteristic is an integer number that describes the
|
||||
topology of the set of all objects in the input image. If object is
|
||||
4-connected, then background is 8-connected, and conversely.
|
||||
|
||||
The computation of the Euler characteristic is based on an integral
|
||||
geometry formula in discretized space. In practice, a neighborhood
|
||||
configuration is constructed, and a LUT is applied for each
|
||||
configuration. The coefficients used are the ones of Ohser et al.
|
||||
|
||||
It can be useful to compute the Euler characteristic for several
|
||||
connectivities. A large relative difference between results
|
||||
for different connectivities suggests that the image resolution
|
||||
(with respect to the size of objects and holes) is too low.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] S. Rivollier. Analyse d’image geometrique et morphometrique par
|
||||
diagrammes de forme et voisinages adaptatifs generaux. PhD thesis,
|
||||
2010. Ecole Nationale Superieure des Mines de Saint-Etienne.
|
||||
https://tel.archives-ouvertes.fr/tel-00560838
|
||||
.. [2] Ohser J., Nagel W., Schladitz K. (2002) The Euler Number of
|
||||
Discretized Sets - On the Choice of Adjacency in Homogeneous
|
||||
Lattices. In: Mecke K., Stoyan D. (eds) Morphology of Condensed
|
||||
Matter. Lecture Notes in Physics, vol 600. Springer, Berlin,
|
||||
Heidelberg.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> SAMPLE = np.zeros((100,100,100));
|
||||
>>> SAMPLE[40:60, 40:60, 40:60]=1
|
||||
>>> euler_number(SAMPLE) # doctest: +ELLIPSIS
|
||||
1...
|
||||
>>> SAMPLE[45:55,45:55,45:55] = 0;
|
||||
>>> euler_number(SAMPLE) # doctest: +ELLIPSIS
|
||||
2...
|
||||
>>> SAMPLE = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0],
|
||||
... [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
|
||||
... [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
|
||||
... [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
|
||||
... [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
|
||||
... [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
... [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
... [1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0],
|
||||
... [0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1],
|
||||
... [0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]])
|
||||
>>> euler_number(SAMPLE) # doctest:
|
||||
0
|
||||
>>> euler_number(SAMPLE, connectivity=1) # doctest:
|
||||
2
|
||||
"""
|
||||
|
||||
# as image can be a label image, transform it to binary
|
||||
image = (image > 0).astype(int)
|
||||
image = np.pad(image, pad_width=1, mode='constant')
|
||||
|
||||
# check connectivity
|
||||
if connectivity is None:
|
||||
connectivity = image.ndim
|
||||
|
||||
# config variable is an adjacency configuration. A coefficient given by
|
||||
# variable coefs is attributed to each configuration in order to get
|
||||
# the Euler characteristic.
|
||||
if image.ndim == 2:
|
||||
|
||||
config = np.array([[0, 0, 0], [0, 1, 4], [0, 2, 8]])
|
||||
if connectivity == 1:
|
||||
coefs = EULER_COEFS2D_4
|
||||
else:
|
||||
coefs = EULER_COEFS2D_8
|
||||
bins = 16
|
||||
else: # 3D images
|
||||
if connectivity == 2:
|
||||
raise NotImplementedError(
|
||||
'For 3D images, Euler number is implemented '
|
||||
'for connectivities 1 and 3 only')
|
||||
|
||||
config = np.array([[[0, 0, 0], [0, 0, 0], [0, 0, 0]],
|
||||
[[0, 0, 0], [0, 1, 4], [0, 2, 8]],
|
||||
[[0, 0, 0], [0, 16, 64], [0, 32, 128]]])
|
||||
if connectivity == 1:
|
||||
coefs = EULER_COEFS3D_26[::-1]
|
||||
else:
|
||||
coefs = EULER_COEFS3D_26
|
||||
bins = 256
|
||||
|
||||
# XF has values in the 0-255 range in 3D, and in the 0-15 range in 2D,
|
||||
# with one unique value for each binary configuration of the
|
||||
# 27-voxel cube in 3D / 8-pixel square in 2D, up to symmetries
|
||||
XF = ndi.convolve(image, config, mode='constant', cval=0)
|
||||
h = np.bincount(XF.ravel(), minlength=bins)
|
||||
|
||||
if image.ndim == 2:
|
||||
return coefs @ h
|
||||
else:
|
||||
return int(0.125 * coefs @ h)
|
||||
|
||||
|
||||
def perimeter(image, neighborhood=4):
|
||||
"""Calculate total perimeter of all objects in binary image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : (N, M) ndarray
|
||||
2D binary image.
|
||||
neighborhood : 4 or 8, optional
|
||||
Neighborhood connectivity for border pixel determination. It is used to
|
||||
compute the contour. A higher neighborhood widens the border on which
|
||||
the perimeter is computed.
|
||||
|
||||
Returns
|
||||
-------
|
||||
perimeter : float
|
||||
Total perimeter of all objects in binary image.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] K. Benkrid, D. Crookes. Design and FPGA Implementation of
|
||||
a Perimeter Estimator. The Queen's University of Belfast.
|
||||
http://www.cs.qub.ac.uk/~d.crookes/webpubs/papers/perimeter.doc
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage import data, util
|
||||
>>> from skimage.measure import label
|
||||
>>> # coins image (binary)
|
||||
>>> img_coins = data.coins() > 110
|
||||
>>> # total perimeter of all objects in the image
|
||||
>>> perimeter(img_coins, neighborhood=4) # doctest: +ELLIPSIS
|
||||
7796.867...
|
||||
>>> perimeter(img_coins, neighborhood=8) # doctest: +ELLIPSIS
|
||||
8806.268...
|
||||
|
||||
"""
|
||||
if image.ndim != 2:
|
||||
raise NotImplementedError('`perimeter` supports 2D images only')
|
||||
|
||||
if neighborhood == 4:
|
||||
strel = STREL_4
|
||||
else:
|
||||
strel = STREL_8
|
||||
image = image.astype(np.uint8)
|
||||
eroded_image = ndi.binary_erosion(image, strel, border_value=0)
|
||||
border_image = image - eroded_image
|
||||
|
||||
perimeter_weights = np.zeros(50, dtype=np.float64)
|
||||
perimeter_weights[[5, 7, 15, 17, 25, 27]] = 1
|
||||
perimeter_weights[[21, 33]] = sqrt(2)
|
||||
perimeter_weights[[13, 23]] = (1 + sqrt(2)) / 2
|
||||
|
||||
perimeter_image = ndi.convolve(border_image, np.array([[10, 2, 10],
|
||||
[2, 1, 2],
|
||||
[10, 2, 10]]),
|
||||
mode='constant', cval=0)
|
||||
|
||||
# You can also write
|
||||
# return perimeter_weights[perimeter_image].sum()
|
||||
# but that was measured as taking much longer than bincount + np.dot (5x
|
||||
# as much time)
|
||||
perimeter_histogram = np.bincount(perimeter_image.ravel(), minlength=50)
|
||||
total_perimeter = perimeter_histogram @ perimeter_weights
|
||||
return total_perimeter
|
||||
|
||||
|
||||
def perimeter_crofton(image, directions=4):
|
||||
"""Calculate total Crofton perimeter of all objects in binary image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : (N, M) ndarray
|
||||
2D image. If image is not binary, all values strictly greater than zero
|
||||
are considered as the object.
|
||||
directions : 2 or 4, optional
|
||||
Number of directions used to approximate the Crofton perimeter. By
|
||||
default, 4 is used: it should be more accurate than 2.
|
||||
Computation time is the same in both cases.
|
||||
|
||||
Returns
|
||||
-------
|
||||
perimeter : float
|
||||
Total perimeter of all objects in binary image.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This measure is based on Crofton formula [1], which is a measure from
|
||||
integral geometry. It is defined for general curve length evaluation via
|
||||
a double integral along all directions. In a discrete
|
||||
space, 2 or 4 directions give a quite good approximation, 4 being more
|
||||
accurate than 2 for more complex shapes.
|
||||
|
||||
Similar to :func:`~.measure.perimeter`, this function returns an
|
||||
approximation of the perimeter in continuous space.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] https://en.wikipedia.org/wiki/Crofton_formula
|
||||
.. [2] S. Rivollier. Analyse d’image geometrique et morphometrique par
|
||||
diagrammes de forme et voisinages adaptatifs generaux. PhD thesis,
|
||||
2010.
|
||||
Ecole Nationale Superieure des Mines de Saint-Etienne.
|
||||
https://tel.archives-ouvertes.fr/tel-00560838
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage import data, util
|
||||
>>> from skimage.measure import label
|
||||
>>> # coins image (binary)
|
||||
>>> img_coins = data.coins() > 110
|
||||
>>> # total perimeter of all objects in the image
|
||||
>>> perimeter_crofton(img_coins, directions=2) # doctest: +ELLIPSIS
|
||||
8144.578...
|
||||
>>> perimeter_crofton(img_coins, directions=4) # doctest: +ELLIPSIS
|
||||
7837.077...
|
||||
"""
|
||||
if image.ndim != 2:
|
||||
raise NotImplementedError(
|
||||
'`perimeter_crofton` supports 2D images only')
|
||||
|
||||
# as image could be a label image, transform it to binary image
|
||||
image = (image > 0).astype(np.uint8)
|
||||
image = np.pad(image, pad_width=1, mode='constant')
|
||||
XF = ndi.convolve(image, np.array([[0, 0, 0], [0, 1, 4], [0, 2, 8]]),
|
||||
mode='constant', cval=0)
|
||||
|
||||
h = np.bincount(XF.ravel(), minlength=16)
|
||||
|
||||
# definition of the LUT
|
||||
if directions == 2:
|
||||
coefs = [0, np.pi / 2, 0, 0, 0, np.pi / 2, 0, 0,
|
||||
np.pi / 2, np.pi, 0, 0, np.pi / 2, np.pi, 0, 0]
|
||||
else:
|
||||
coefs = [0, np.pi / 4 * (1 + 1 / (np.sqrt(2))),
|
||||
np.pi / (4 * np.sqrt(2)),
|
||||
np.pi / (2 * np.sqrt(2)), 0,
|
||||
np.pi / 4 * (1 + 1 / (np.sqrt(2))),
|
||||
0, np.pi / (4 * np.sqrt(2)), np.pi / 4, np.pi / 2,
|
||||
np.pi / (4 * np.sqrt(2)), np.pi / (4 * np.sqrt(2)),
|
||||
np.pi / 4, np.pi / 2, 0, 0]
|
||||
|
||||
total_perimeter = coefs @ h
|
||||
return total_perimeter
|
||||
Reference in New Issue
Block a user