Fixed database typo and removed unnecessary class identifier.
This commit is contained in:
parent
00ad49a143
commit
45fb349a7d
5098 changed files with 952558 additions and 85 deletions
52
venv/Lib/site-packages/skimage/measure/__init__.py
Normal file
52
venv/Lib/site-packages/skimage/measure/__init__.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
from ._find_contours import find_contours
|
||||
from ._marching_cubes_lewiner import marching_cubes_lewiner, marching_cubes
|
||||
from ._marching_cubes_classic import (marching_cubes_classic,
|
||||
mesh_surface_area)
|
||||
from ._regionprops import regionprops, perimeter, regionprops_table
|
||||
from .simple_metrics import compare_mse, compare_nrmse, compare_psnr
|
||||
from ._structural_similarity import compare_ssim
|
||||
from ._polygon import approximate_polygon, subdivide_polygon
|
||||
from .pnpoly import points_in_poly, grid_points_in_poly
|
||||
from ._moments import (moments, moments_central, moments_coords,
|
||||
moments_coords_central, moments_normalized, centroid,
|
||||
moments_hu, inertia_tensor, inertia_tensor_eigvals)
|
||||
from .profile import profile_line
|
||||
from .fit import LineModelND, CircleModel, EllipseModel, ransac
|
||||
from .block import block_reduce
|
||||
from ._label import label
|
||||
from .entropy import shannon_entropy
|
||||
|
||||
|
||||
__all__ = ['find_contours',
|
||||
'regionprops',
|
||||
'regionprops_table',
|
||||
'perimeter',
|
||||
'approximate_polygon',
|
||||
'subdivide_polygon',
|
||||
'LineModelND',
|
||||
'CircleModel',
|
||||
'EllipseModel',
|
||||
'ransac',
|
||||
'block_reduce',
|
||||
'moments',
|
||||
'moments_central',
|
||||
'moments_coords',
|
||||
'moments_coords_central',
|
||||
'moments_normalized',
|
||||
'moments_hu',
|
||||
'inertia_tensor',
|
||||
'inertia_tensor_eigvals',
|
||||
'marching_cubes',
|
||||
'marching_cubes_lewiner',
|
||||
'marching_cubes_classic',
|
||||
'mesh_surface_area',
|
||||
'profile_line',
|
||||
'label',
|
||||
'points_in_poly',
|
||||
'grid_points_in_poly',
|
||||
'compare_ssim',
|
||||
'compare_mse',
|
||||
'compare_nrmse',
|
||||
'compare_psnr',
|
||||
'shannon_entropy',
|
||||
]
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
venv/Lib/site-packages/skimage/measure/_ccomp.cp36-win32.pyd
Normal file
BIN
venv/Lib/site-packages/skimage/measure/_ccomp.cp36-win32.pyd
Normal file
Binary file not shown.
206
venv/Lib/site-packages/skimage/measure/_find_contours.py
Normal file
206
venv/Lib/site-packages/skimage/measure/_find_contours.py
Normal file
|
@ -0,0 +1,206 @@
|
|||
import numpy as np
|
||||
from ._find_contours_cy import _get_contour_segments
|
||||
|
||||
from collections import deque
|
||||
|
||||
_param_options = ('high', 'low')
|
||||
|
||||
|
||||
def find_contours(array, level,
|
||||
fully_connected='low', positive_orientation='low',
|
||||
*,
|
||||
mask=None):
|
||||
"""Find iso-valued contours in a 2D array for a given level value.
|
||||
|
||||
Uses the "marching squares" method to compute a the iso-valued contours of
|
||||
the input 2D array for a particular level value. Array values are linearly
|
||||
interpolated to provide better precision for the output contours.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
array : 2D ndarray of double
|
||||
Input data in which to find contours.
|
||||
level : float
|
||||
Value along which to find contours in the array.
|
||||
fully_connected : str, {'low', 'high'}
|
||||
Indicates whether array elements below the given level value are to be
|
||||
considered fully-connected (and hence elements above the value will
|
||||
only be face connected), or vice-versa. (See notes below for details.)
|
||||
positive_orientation : either 'low' or 'high'
|
||||
Indicates whether the output contours will produce positively-oriented
|
||||
polygons around islands of low- or high-valued elements. If 'low' then
|
||||
contours will wind counter- clockwise around elements below the
|
||||
iso-value. Alternately, this means that low-valued elements are always
|
||||
on the left of the contour. (See below for details.)
|
||||
mask : 2D ndarray of bool, or None
|
||||
A boolean mask, True where we want to draw contours.
|
||||
Note that NaN values are always excluded from the considered region
|
||||
(``mask`` is set to ``False`` wherever ``array`` is ``NaN``).
|
||||
|
||||
Returns
|
||||
-------
|
||||
contours : list of (n,2)-ndarrays
|
||||
Each contour is an ndarray of shape ``(n, 2)``,
|
||||
consisting of n ``(row, column)`` coordinates along the contour.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The marching squares algorithm is a special case of the marching cubes
|
||||
algorithm [1]_. A simple explanation is available here::
|
||||
|
||||
http://users.polytech.unice.fr/~lingrand/MarchingCubes/algo.html
|
||||
|
||||
There is a single ambiguous case in the marching squares algorithm: when
|
||||
a given ``2 x 2``-element square has two high-valued and two low-valued
|
||||
elements, each pair diagonally adjacent. (Where high- and low-valued is
|
||||
with respect to the contour value sought.) In this case, either the
|
||||
high-valued elements can be 'connected together' via a thin isthmus that
|
||||
separates the low-valued elements, or vice-versa. When elements are
|
||||
connected together across a diagonal, they are considered 'fully
|
||||
connected' (also known as 'face+vertex-connected' or '8-connected'). Only
|
||||
high-valued or low-valued elements can be fully-connected, the other set
|
||||
will be considered as 'face-connected' or '4-connected'. By default,
|
||||
low-valued elements are considered fully-connected; this can be altered
|
||||
with the 'fully_connected' parameter.
|
||||
|
||||
Output contours are not guaranteed to be closed: contours which intersect
|
||||
the array edge or a masked-off region (either where mask is False or where
|
||||
array is NaN) will be left open. All other contours will be closed. (The
|
||||
closed-ness of a contours can be tested by checking whether the beginning
|
||||
point is the same as the end point.)
|
||||
|
||||
Contours are oriented. By default, array values lower than the contour
|
||||
value are to the left of the contour and values greater than the contour
|
||||
value are to the right. This means that contours will wind
|
||||
counter-clockwise (i.e. in 'positive orientation') around islands of
|
||||
low-valued pixels. This behavior can be altered with the
|
||||
'positive_orientation' parameter.
|
||||
|
||||
The order of the contours in the output list is determined by the position
|
||||
of the smallest ``x,y`` (in lexicographical order) coordinate in the
|
||||
contour. This is a side-effect of how the input array is traversed, but
|
||||
can be relied upon.
|
||||
|
||||
.. warning::
|
||||
|
||||
Array coordinates/values are assumed to refer to the *center* of the
|
||||
array element. Take a simple example input: ``[0, 1]``. The interpolated
|
||||
position of 0.5 in this array is midway between the 0-element (at
|
||||
``x=0``) and the 1-element (at ``x=1``), and thus would fall at
|
||||
``x=0.5``.
|
||||
|
||||
This means that to find reasonable contours, it is best to find contours
|
||||
midway between the expected "light" and "dark" values. In particular,
|
||||
given a binarized array, *do not* choose to find contours at the low or
|
||||
high value of the array. This will often yield degenerate contours,
|
||||
especially around structures that are a single array element wide. Instead
|
||||
choose a middle value, as above.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Lorensen, William and Harvey E. Cline. Marching Cubes: A High
|
||||
Resolution 3D Surface Construction Algorithm. Computer Graphics
|
||||
(SIGGRAPH 87 Proceedings) 21(4) July 1987, p. 163-170).
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> a = np.zeros((3, 3))
|
||||
>>> a[0, 0] = 1
|
||||
>>> a
|
||||
array([[1., 0., 0.],
|
||||
[0., 0., 0.],
|
||||
[0., 0., 0.]])
|
||||
>>> find_contours(a, 0.5)
|
||||
[array([[0. , 0.5],
|
||||
[0.5, 0. ]])]
|
||||
"""
|
||||
if fully_connected not in _param_options:
|
||||
raise ValueError('Parameters "fully_connected" must be either '
|
||||
'"high" or "low".')
|
||||
if positive_orientation not in _param_options:
|
||||
raise ValueError('Parameters "positive_orientation" must be either '
|
||||
'"high" or "low".')
|
||||
if array.shape[0] < 2 or array.shape[1] < 2:
|
||||
raise ValueError("Input array must be at least 2x2.")
|
||||
if array.ndim != 2:
|
||||
raise ValueError('Only 2D arrays are supported.')
|
||||
if mask is not None:
|
||||
if mask.shape != array.shape:
|
||||
raise ValueError('Parameters "array" and "mask"'
|
||||
' must have same shape.')
|
||||
if not np.can_cast(mask.dtype, bool, casting='safe'):
|
||||
raise TypeError('Parameter "mask" must be a binary array.')
|
||||
mask = mask.astype(np.uint8, copy=False)
|
||||
|
||||
segments = _get_contour_segments(array.astype(np.double), float(level),
|
||||
fully_connected == 'high', mask=mask)
|
||||
contours = _assemble_contours(segments)
|
||||
if positive_orientation == 'high':
|
||||
contours = [c[::-1] for c in contours]
|
||||
return contours
|
||||
|
||||
|
||||
def _assemble_contours(segments):
|
||||
current_index = 0
|
||||
contours = {}
|
||||
starts = {}
|
||||
ends = {}
|
||||
for from_point, to_point in segments:
|
||||
# Ignore degenerate segments.
|
||||
# This happens when (and only when) one vertex of the square is
|
||||
# exactly the contour level, and the rest are above or below.
|
||||
# This degenerate vertex will be picked up later by neighboring
|
||||
# squares.
|
||||
if from_point == to_point:
|
||||
continue
|
||||
|
||||
tail, tail_num = starts.pop(to_point, (None, None))
|
||||
head, head_num = ends.pop(from_point, (None, None))
|
||||
|
||||
if tail is not None and head is not None:
|
||||
# We need to connect these two contours.
|
||||
if tail is head:
|
||||
# We need to closed a contour.
|
||||
# Add the end point
|
||||
head.append(to_point)
|
||||
else: # tail is not head
|
||||
# We need to join two distinct contours.
|
||||
# We want to keep the first contour segment created, so that
|
||||
# the final contours are ordered left->right, top->bottom.
|
||||
if tail_num > head_num:
|
||||
# tail was created second. Append tail to head.
|
||||
head.extend(tail)
|
||||
# remove all traces of tail:
|
||||
ends.pop(tail[-1])
|
||||
contours.pop(tail_num, None)
|
||||
# Update contour starts end ends
|
||||
starts[head[0]] = (head, head_num)
|
||||
ends[head[-1]] = (head, head_num)
|
||||
else: # tail_num <= head_num
|
||||
# head was created second. Prepend head to tail.
|
||||
tail.extendleft(reversed(head))
|
||||
# remove all traces of head:
|
||||
starts.pop(head[0])
|
||||
contours.pop(head_num, None)
|
||||
# Update contour starts end ends
|
||||
starts[tail[0]] = (tail, tail_num)
|
||||
ends[tail[-1]] = (tail, tail_num)
|
||||
elif tail is None and head is None:
|
||||
# we need to add a new contour
|
||||
new_contour = deque((from_point, to_point))
|
||||
contours[current_index] = new_contour
|
||||
starts[from_point] = (new_contour, current_index)
|
||||
ends[to_point] = (new_contour, current_index)
|
||||
current_index += 1
|
||||
elif head is None: # tail is not None
|
||||
# We've found a single contour to which the new segment should be
|
||||
# prepended.
|
||||
tail.appendleft(from_point)
|
||||
starts[from_point] = (tail, tail_num)
|
||||
else: # tail is None and head is not None:
|
||||
# We've found a single contour to which the new segment should be
|
||||
# appended
|
||||
head.append(to_point)
|
||||
ends[to_point] = (head, head_num)
|
||||
|
||||
return [np.array(contour) for _, contour in sorted(contours.items())]
|
Binary file not shown.
93
venv/Lib/site-packages/skimage/measure/_label.py
Normal file
93
venv/Lib/site-packages/skimage/measure/_label.py
Normal file
|
@ -0,0 +1,93 @@
|
|||
from ._ccomp import label_cython as clabel
|
||||
|
||||
|
||||
def label(input, neighbors=None, background=None, return_num=False,
|
||||
connectivity=None):
|
||||
r"""Label connected regions of an integer array.
|
||||
|
||||
Two pixels are connected when they are neighbors and have the same value.
|
||||
In 2D, they can be neighbors either in a 1- or 2-connected sense.
|
||||
The value refers to the maximum number of orthogonal hops to consider a
|
||||
pixel/voxel a neighbor::
|
||||
|
||||
1-connectivity 2-connectivity diagonal connection close-up
|
||||
|
||||
[ ] [ ] [ ] [ ] [ ]
|
||||
| \ | / | <- hop 2
|
||||
[ ]--[x]--[ ] [ ]--[x]--[ ] [x]--[ ]
|
||||
| / | \ hop 1
|
||||
[ ] [ ] [ ] [ ]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
input : ndarray of dtype int
|
||||
Image to label.
|
||||
neighbors : {4, 8}, int, optional
|
||||
Whether to use 4- or 8-"connectivity".
|
||||
In 3D, 4-"connectivity" means connected pixels have to share face,
|
||||
whereas with 8-"connectivity", they have to share only edge or vertex.
|
||||
**Deprecated, use** ``connectivity`` **instead.**
|
||||
background : int, optional
|
||||
Consider all pixels with this value as background pixels, and label
|
||||
them as 0. By default, 0-valued pixels are considered as background
|
||||
pixels.
|
||||
return_num : bool, optional
|
||||
Whether to return the number of assigned labels.
|
||||
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.
|
||||
|
||||
Returns
|
||||
-------
|
||||
labels : ndarray of dtype int
|
||||
Labeled array, where all connected regions are assigned the
|
||||
same integer value.
|
||||
num : int, optional
|
||||
Number of labels, which equals the maximum label index and is only
|
||||
returned if return_num is `True`.
|
||||
|
||||
See Also
|
||||
--------
|
||||
regionprops
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Christophe Fiorio and Jens Gustedt, "Two linear time Union-Find
|
||||
strategies for image processing", Theoretical Computer Science
|
||||
154 (1996), pp. 165-181.
|
||||
.. [2] Kensheng Wu, Ekow Otoo and Arie Shoshani, "Optimizing connected
|
||||
component labeling algorithms", Paper LBNL-56864, 2005,
|
||||
Lawrence Berkeley National Laboratory (University of California),
|
||||
http://repositories.cdlib.org/lbnl/LBNL-56864
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> x = np.eye(3).astype(int)
|
||||
>>> print(x)
|
||||
[[1 0 0]
|
||||
[0 1 0]
|
||||
[0 0 1]]
|
||||
>>> print(label(x, connectivity=1))
|
||||
[[1 0 0]
|
||||
[0 2 0]
|
||||
[0 0 3]]
|
||||
>>> print(label(x, connectivity=2))
|
||||
[[1 0 0]
|
||||
[0 1 0]
|
||||
[0 0 1]]
|
||||
>>> print(label(x, background=-1))
|
||||
[[1 2 2]
|
||||
[2 1 2]
|
||||
[2 2 1]]
|
||||
>>> x = np.array([[1, 0, 0],
|
||||
... [1, 1, 5],
|
||||
... [0, 0, 0]])
|
||||
>>> print(label(x))
|
||||
[[1 0 0]
|
||||
[1 1 2]
|
||||
[0 0 0]]
|
||||
"""
|
||||
return clabel(input, neighbors, background, return_num, connectivity)
|
|
@ -0,0 +1,301 @@
|
|||
import warnings
|
||||
import numpy as np
|
||||
import scipy.ndimage as ndi
|
||||
from . import _marching_cubes_classic_cy
|
||||
|
||||
|
||||
def marching_cubes_classic(volume, level=None, spacing=(1., 1., 1.),
|
||||
gradient_direction='descent'):
|
||||
"""
|
||||
Classic marching cubes algorithm to find surfaces in 3d volumetric data.
|
||||
|
||||
Note that the ``marching_cubes()`` algorithm is recommended over
|
||||
this algorithm, because it's faster and produces better results.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
volume : (M, N, P) array of doubles
|
||||
Input data volume to find isosurfaces. Will be cast to `np.float64`.
|
||||
level : float
|
||||
Contour value to search for isosurfaces in `volume`. If not
|
||||
given or None, the average of the min and max of vol is used.
|
||||
spacing : length-3 tuple of floats
|
||||
Voxel spacing in spatial dimensions corresponding to numpy array
|
||||
indexing dimensions (M, N, P) as in `volume`.
|
||||
gradient_direction : string
|
||||
Controls if the mesh was generated from an isosurface with gradient
|
||||
descent toward objects of interest (the default), or the opposite.
|
||||
The two options are:
|
||||
* descent : Object was greater than exterior
|
||||
* ascent : Exterior was greater than object
|
||||
|
||||
Returns
|
||||
-------
|
||||
verts : (V, 3) array
|
||||
Spatial coordinates for V unique mesh vertices. Coordinate order
|
||||
matches input `volume` (M, N, P).
|
||||
faces : (F, 3) array
|
||||
Define triangular faces via referencing vertex indices from ``verts``.
|
||||
This algorithm specifically outputs triangles, so each face has
|
||||
exactly three indices.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The marching cubes algorithm is implemented as described in [1]_.
|
||||
A simple explanation is available here::
|
||||
|
||||
http://users.polytech.unice.fr/~lingrand/MarchingCubes/algo.html
|
||||
|
||||
There are several known ambiguous cases in the marching cubes algorithm.
|
||||
Using point labeling as in [1]_, Figure 4, as shown::
|
||||
|
||||
v8 ------ v7
|
||||
/ | / | y
|
||||
/ | / | ^ z
|
||||
v4 ------ v3 | | /
|
||||
| v5 ----|- v6 |/ (note: NOT right handed!)
|
||||
| / | / ----> x
|
||||
| / | /
|
||||
v1 ------ v2
|
||||
|
||||
Most notably, if v4, v8, v2, and v6 are all >= `level` (or any
|
||||
generalization of this case) two parallel planes are generated by this
|
||||
algorithm, separating v4 and v8 from v2 and v6. An equally valid
|
||||
interpretation would be a single connected thin surface enclosing all
|
||||
four points. This is the best known ambiguity, though there are others.
|
||||
|
||||
This algorithm does not attempt to resolve such ambiguities; it is a naive
|
||||
implementation of marching cubes as in [1]_, but may be a good beginning
|
||||
for work with more recent techniques (Dual Marching Cubes, Extended
|
||||
Marching Cubes, Cubic Marching Squares, etc.).
|
||||
|
||||
Because of interactions between neighboring cubes, the isosurface(s)
|
||||
generated by this algorithm are NOT guaranteed to be closed, particularly
|
||||
for complicated contours. Furthermore, this algorithm does not guarantee
|
||||
a single contour will be returned. Indeed, ALL isosurfaces which cross
|
||||
`level` will be found, regardless of connectivity.
|
||||
|
||||
The output is a triangular mesh consisting of a set of unique vertices and
|
||||
connecting triangles. The order of these vertices and triangles in the
|
||||
output list is determined by the position of the smallest ``x,y,z`` (in
|
||||
lexicographical order) coordinate in the contour. This is a side-effect
|
||||
of how the input array is traversed, but can be relied upon.
|
||||
|
||||
The generated mesh guarantees coherent orientation as of version 0.12.
|
||||
|
||||
To quantify the area of an isosurface generated by this algorithm, pass
|
||||
outputs directly into `skimage.measure.mesh_surface_area`.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Lorensen, William and Harvey E. Cline. Marching Cubes: A High
|
||||
Resolution 3D Surface Construction Algorithm. Computer Graphics
|
||||
(SIGGRAPH 87 Proceedings) 21(4) July 1987, p. 163-170).
|
||||
:DOI:`10.1145/37401.37422`
|
||||
|
||||
See Also
|
||||
--------
|
||||
skimage.measure.marching_cubes
|
||||
skimage.measure.mesh_surface_area
|
||||
"""
|
||||
|
||||
# Deprecate the function in favor of marching_cubes
|
||||
warnings.warn("marching_cubes_classic is deprecated in favor of "
|
||||
"marching_cubes with `method='_lorensen'` "
|
||||
"to apply Lorensen et al. algorithm. "
|
||||
"marching_cubes_classic will be removed in version 0.19",
|
||||
FutureWarning, stacklevel=2)
|
||||
|
||||
return _marching_cubes_classic(volume, level, spacing, gradient_direction)
|
||||
|
||||
|
||||
def _marching_cubes_classic(volume, level, spacing, gradient_direction):
|
||||
"""Lorensen et al. algorithm for marching cubes. See
|
||||
marching_cubes_classic for documentation.
|
||||
|
||||
"""
|
||||
# Check inputs and ensure `volume` is C-contiguous for memoryviews
|
||||
if volume.ndim != 3:
|
||||
raise ValueError("Input volume must have 3 dimensions.")
|
||||
if level is None:
|
||||
level = 0.5 * (volume.min() + volume.max())
|
||||
else:
|
||||
level = float(level)
|
||||
if level < volume.min() or level > volume.max():
|
||||
raise ValueError("Surface level must be within volume data range.")
|
||||
if len(spacing) != 3:
|
||||
raise ValueError("`spacing` must consist of three floats.")
|
||||
|
||||
volume = np.array(volume, dtype=np.float64, order="C")
|
||||
|
||||
# Extract raw triangles using marching cubes in Cython
|
||||
# Returns a list of length-3 lists, each sub-list containing three
|
||||
# tuples. The tuples hold (x, y, z) coordinates for triangle vertices.
|
||||
# Note: this algorithm is fast, but returns degenerate "triangles" which
|
||||
# have repeated vertices - and equivalent vertices are redundantly
|
||||
# placed in every triangle they connect with.
|
||||
raw_faces = _marching_cubes_classic_cy.iterate_and_store_3d(volume,
|
||||
float(level))
|
||||
|
||||
# Find and collect unique vertices, storing triangle verts as indices.
|
||||
# Returns a true mesh with no degenerate faces.
|
||||
verts, faces = _marching_cubes_classic_cy.unpack_unique_verts(raw_faces)
|
||||
|
||||
verts = np.asarray(verts)
|
||||
faces = np.asarray(faces)
|
||||
|
||||
# Fancy indexing to define two vector arrays from triangle vertices
|
||||
faces = _correct_mesh_orientation(volume, verts[faces], faces, spacing,
|
||||
gradient_direction)
|
||||
|
||||
# Adjust for non-isotropic spacing in `verts` at time of return
|
||||
return verts * np.r_[spacing], faces
|
||||
|
||||
|
||||
def mesh_surface_area(verts, faces):
|
||||
"""
|
||||
Compute surface area, given vertices & triangular faces
|
||||
|
||||
Parameters
|
||||
----------
|
||||
verts : (V, 3) array of floats
|
||||
Array containing (x, y, z) coordinates for V unique mesh vertices.
|
||||
faces : (F, 3) array of ints
|
||||
List of length-3 lists of integers, referencing vertex coordinates as
|
||||
provided in `verts`
|
||||
|
||||
Returns
|
||||
-------
|
||||
area : float
|
||||
Surface area of mesh. Units now [coordinate units] ** 2.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The arguments expected by this function are the first two outputs from
|
||||
`skimage.measure.marching_cubes`. For unit correct output, ensure correct
|
||||
`spacing` was passed to `skimage.measure.marching_cubes`.
|
||||
|
||||
This algorithm works properly only if the ``faces`` provided are all
|
||||
triangles.
|
||||
|
||||
See Also
|
||||
--------
|
||||
skimage.measure.marching_cubes
|
||||
skimage.measure.marching_cubes_classic
|
||||
|
||||
"""
|
||||
# Fancy indexing to define two vector arrays from triangle vertices
|
||||
actual_verts = verts[faces]
|
||||
a = actual_verts[:, 0, :] - actual_verts[:, 1, :]
|
||||
b = actual_verts[:, 0, :] - actual_verts[:, 2, :]
|
||||
del actual_verts
|
||||
|
||||
# Area of triangle in 3D = 1/2 * Euclidean norm of cross product
|
||||
return ((np.cross(a, b) ** 2).sum(axis=1) ** 0.5).sum() / 2.
|
||||
|
||||
|
||||
def _correct_mesh_orientation(volume, actual_verts, faces,
|
||||
spacing=(1., 1., 1.),
|
||||
gradient_direction='descent'):
|
||||
"""
|
||||
Correct orientations of mesh faces.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
volume : (M, N, P) array of doubles
|
||||
Input data volume to find isosurfaces. Will be cast to `np.float64`.
|
||||
actual_verts : (F, 3, 3) array of floats
|
||||
Array with (face, vertex, coords) index coordinates.
|
||||
faces : (F, 3) array of ints
|
||||
List of length-3 lists of integers, referencing vertex coordinates as
|
||||
provided in `verts`.
|
||||
spacing : length-3 tuple of floats
|
||||
Voxel spacing in spatial dimensions corresponding to numpy array
|
||||
indexing dimensions (M, N, P) as in `volume`.
|
||||
gradient_direction : string
|
||||
Controls if the mesh was generated from an isosurface with gradient
|
||||
descent toward objects of interest (the default), or the opposite.
|
||||
The two options are:
|
||||
* descent : Object was greater than exterior
|
||||
* ascent : Exterior was greater than object
|
||||
|
||||
Returns
|
||||
-------
|
||||
faces_corrected (F, 3) array of ints
|
||||
Corrected list of faces referencing vertex coordinates in `verts`.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Certain applications and mesh processing algorithms require all faces
|
||||
to be oriented in a consistent way. Generally, this means a normal vector
|
||||
points "out" of the meshed shapes. This algorithm corrects the output from
|
||||
`skimage.measure.marching_cubes_classic` by flipping the orientation of
|
||||
mis-oriented faces.
|
||||
|
||||
Because marching cubes could be used to find isosurfaces either on
|
||||
gradient descent (where the desired object has greater values than the
|
||||
exterior) or ascent (where the desired object has lower values than the
|
||||
exterior), the ``gradient_direction`` kwarg allows the user to inform this
|
||||
algorithm which is correct. If the resulting mesh appears to be oriented
|
||||
completely incorrectly, try changing this option.
|
||||
|
||||
The arguments expected by this function are the exact outputs from
|
||||
`skimage.measure.marching_cubes_classic` except `actual_verts`, which is an
|
||||
uncorrected version of the fancy indexing operation `verts[faces]`.
|
||||
Only `faces` is corrected and returned as the vertices do not change,
|
||||
only the order in which they are referenced.
|
||||
|
||||
This algorithm assumes ``faces`` provided are exclusively triangles.
|
||||
|
||||
See Also
|
||||
--------
|
||||
skimage.measure.marching_cubes_classic
|
||||
skimage.measure.mesh_surface_area
|
||||
|
||||
"""
|
||||
# Calculate gradient of `volume`, then interpolate to vertices in `verts`
|
||||
grad_x, grad_y, grad_z = np.gradient(volume)
|
||||
|
||||
a = actual_verts[:, 0, :] - actual_verts[:, 1, :]
|
||||
b = actual_verts[:, 0, :] - actual_verts[:, 2, :]
|
||||
|
||||
# Find triangle centroids
|
||||
centroids = (actual_verts.sum(axis=1) / 3.).T
|
||||
|
||||
del actual_verts
|
||||
|
||||
# Interpolate face centroids into each gradient axis
|
||||
grad_centroids_x = ndi.map_coordinates(grad_x, centroids)
|
||||
grad_centroids_y = ndi.map_coordinates(grad_y, centroids)
|
||||
grad_centroids_z = ndi.map_coordinates(grad_z, centroids)
|
||||
|
||||
# Combine and normalize interpolated gradients
|
||||
grad_centroids = np.c_[grad_centroids_x, grad_centroids_y,
|
||||
grad_centroids_z]
|
||||
grad_centroids = (grad_centroids /
|
||||
(np.sum(grad_centroids ** 2,
|
||||
axis=1) ** 0.5)[:, np.newaxis])
|
||||
|
||||
# Find normal vectors for each face via cross product
|
||||
crosses = np.cross(a, b)
|
||||
crosses = crosses / (np.sum(crosses ** 2, axis=1) ** (0.5))[:, np.newaxis]
|
||||
|
||||
# Take dot product
|
||||
dotproducts = (grad_centroids * crosses).sum(axis=1)
|
||||
|
||||
# Find mis-oriented faces
|
||||
if 'descent' in gradient_direction:
|
||||
# Faces with incorrect orientations have dot product < 0
|
||||
indices = (dotproducts < 0).nonzero()[0]
|
||||
elif 'ascent' in gradient_direction:
|
||||
# Faces with incorrection orientation have dot product > 0
|
||||
indices = (dotproducts > 0).nonzero()[0]
|
||||
else:
|
||||
raise ValueError("Incorrect input %s in `gradient_direction`, see "
|
||||
"docstring." % (gradient_direction))
|
||||
|
||||
# Correct orientation and return, without modifying original data
|
||||
faces_corrected = faces.copy()
|
||||
faces_corrected[indices] = faces_corrected[indices, ::-1]
|
||||
|
||||
return faces_corrected
|
Binary file not shown.
|
@ -0,0 +1,388 @@
|
|||
import warnings
|
||||
import base64
|
||||
|
||||
import numpy as np
|
||||
|
||||
from . import _marching_cubes_lewiner_luts as mcluts
|
||||
from . import _marching_cubes_lewiner_cy
|
||||
from ._marching_cubes_classic import _marching_cubes_classic
|
||||
|
||||
|
||||
def marching_cubes(volume, level=None, *, spacing=(1., 1., 1.),
|
||||
gradient_direction='descent', step_size=1,
|
||||
allow_degenerate=True, method='lewiner', mask=None):
|
||||
"""Marching cubes algorithm to find surfaces in 3d volumetric data.
|
||||
|
||||
In contrast with Lorensen et al. approach [2], Lewiner et
|
||||
al. algorithm is faster, resolves ambiguities, and guarantees
|
||||
topologically correct results. Therefore, this algorithm generally
|
||||
a better choice.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
volume : (M, N, P) array
|
||||
Input data volume to find isosurfaces. Will internally be
|
||||
converted to float32 if necessary.
|
||||
level : float
|
||||
Contour value to search for isosurfaces in `volume`. If not
|
||||
given or None, the average of the min and max of vol is used.
|
||||
spacing : length-3 tuple of floats
|
||||
Voxel spacing in spatial dimensions corresponding to numpy array
|
||||
indexing dimensions (M, N, P) as in `volume`.
|
||||
gradient_direction : string
|
||||
Controls if the mesh was generated from an isosurface with gradient
|
||||
descent toward objects of interest (the default), or the opposite,
|
||||
considering the *left-hand* rule.
|
||||
The two options are:
|
||||
* descent : Object was greater than exterior
|
||||
* ascent : Exterior was greater than object
|
||||
step_size : int
|
||||
Step size in voxels. Default 1. Larger steps yield faster but
|
||||
coarser results. The result will always be topologically correct
|
||||
though.
|
||||
allow_degenerate : bool
|
||||
Whether to allow degenerate (i.e. zero-area) triangles in the
|
||||
end-result. Default True. If False, degenerate triangles are
|
||||
removed, at the cost of making the algorithm slower.
|
||||
method: str
|
||||
One of 'lewiner', 'lorensen' or '_lorensen'. Specify witch of
|
||||
Lewiner et al. or Lorensen et al. method will be used. The
|
||||
'_lorensen' flag correspond to an old implementation that will
|
||||
be deprecated in version 0.19.
|
||||
mask : (M, N, P) array
|
||||
Boolean array. The marching cube algorithm will be computed only on
|
||||
True elements. This will save computational time when interfaces
|
||||
are located within certain region of the volume M, N, P-e.g. the top
|
||||
half of the cube-and also allow to compute finite surfaces-i.e. open
|
||||
surfaces that do not end at the border of the cube.
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
verts : (V, 3) array
|
||||
Spatial coordinates for V unique mesh vertices. Coordinate order
|
||||
matches input `volume` (M, N, P).
|
||||
faces : (F, 3) array
|
||||
Define triangular faces via referencing vertex indices from ``verts``.
|
||||
This algorithm specifically outputs triangles, so each face has
|
||||
exactly three indices.
|
||||
normals : (V, 3) array
|
||||
The normal direction at each vertex, as calculated from the
|
||||
data.
|
||||
values : (V, ) array
|
||||
Gives a measure for the maximum value of the data in the local region
|
||||
near each vertex. This can be used by visualization tools to apply
|
||||
a colormap to the mesh.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The algorithm [1] is an improved version of Chernyaev's Marching
|
||||
Cubes 33 algorithm. It is an efficient algorithm that relies on
|
||||
heavy use of lookup tables to handle the many different cases,
|
||||
keeping the algorithm relatively easy. This implementation is
|
||||
written in Cython, ported from Lewiner's C++ implementation.
|
||||
|
||||
To quantify the area of an isosurface generated by this algorithm, pass
|
||||
verts and faces to `skimage.measure.mesh_surface_area`.
|
||||
|
||||
Regarding visualization of algorithm output, to contour a volume
|
||||
named `myvolume` about the level 0.0, using the ``mayavi`` package::
|
||||
|
||||
>>>
|
||||
>> from mayavi import mlab
|
||||
>> verts, faces, _, _ = marching_cubes(myvolume, 0.0)
|
||||
>> mlab.triangular_mesh([vert[0] for vert in verts],
|
||||
[vert[1] for vert in verts],
|
||||
[vert[2] for vert in verts],
|
||||
faces)
|
||||
>> mlab.show()
|
||||
|
||||
Similarly using the ``visvis`` package::
|
||||
|
||||
>>>
|
||||
>> import visvis as vv
|
||||
>> verts, faces, normals, values = marching_cubes(myvolume, 0.0)
|
||||
>> vv.mesh(np.fliplr(verts), faces, normals, values)
|
||||
>> vv.use().Run()
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Thomas Lewiner, Helio Lopes, Antonio Wilson Vieira and Geovan
|
||||
Tavares. Efficient implementation of Marching Cubes' cases with
|
||||
topological guarantees. Journal of Graphics Tools 8(2)
|
||||
pp. 1-15 (december 2003).
|
||||
:DOI:`10.1080/10867651.2003.10487582`
|
||||
.. [2] Lorensen, William and Harvey E. Cline. Marching Cubes: A High
|
||||
Resolution 3D Surface Construction Algorithm. Computer Graphics
|
||||
(SIGGRAPH 87 Proceedings) 21(4) July 1987, p. 163-170).
|
||||
:DOI:`10.1145/37401.37422`
|
||||
|
||||
See Also
|
||||
--------
|
||||
skimage.measure.mesh_surface_area
|
||||
|
||||
"""
|
||||
|
||||
if method == 'lewiner':
|
||||
return _marching_cubes_lewiner(volume, level, spacing,
|
||||
gradient_direction, step_size,
|
||||
allow_degenerate, use_classic=False, mask=mask)
|
||||
elif method == 'lorensen':
|
||||
return _marching_cubes_lewiner(volume, level, spacing,
|
||||
gradient_direction, step_size,
|
||||
allow_degenerate, use_classic=True, mask=mask)
|
||||
elif method == '_lorensen':
|
||||
if mask is not None:
|
||||
raise NotImplementedError(
|
||||
'Parameter `mask` is not implemented for method "_lorensen" '
|
||||
'and will be ignored.'
|
||||
)
|
||||
return _marching_cubes_classic(volume, level, spacing,
|
||||
gradient_direction)
|
||||
else:
|
||||
raise ValueError("method should be one of 'lewiner', 'lorensen' or "
|
||||
"'_lorensen'.")
|
||||
|
||||
|
||||
def marching_cubes_lewiner(volume, level=None, spacing=(1., 1., 1.),
|
||||
gradient_direction='descent', step_size=1,
|
||||
allow_degenerate=True, use_classic=False, mask=None):
|
||||
"""
|
||||
Lewiner marching cubes algorithm to find surfaces in 3d volumetric data.
|
||||
|
||||
In contrast to ``marching_cubes_classic()``, this algorithm is faster,
|
||||
resolves ambiguities, and guarantees topologically correct results.
|
||||
Therefore, this algorithm generally a better choice, unless there
|
||||
is a specific need for the classic algorithm.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
volume : (M, N, P) array
|
||||
Input data volume to find isosurfaces. Will internally be
|
||||
converted to float32 if necessary.
|
||||
level : float
|
||||
Contour value to search for isosurfaces in `volume`. If not
|
||||
given or None, the average of the min and max of vol is used.
|
||||
spacing : length-3 tuple of floats
|
||||
Voxel spacing in spatial dimensions corresponding to numpy array
|
||||
indexing dimensions (M, N, P) as in `volume`.
|
||||
gradient_direction : string
|
||||
Controls if the mesh was generated from an isosurface with gradient
|
||||
descent toward objects of interest (the default), or the opposite,
|
||||
considering the *left-hand* rule.
|
||||
The two options are:
|
||||
* descent : Object was greater than exterior
|
||||
* ascent : Exterior was greater than object
|
||||
step_size : int
|
||||
Step size in voxels. Default 1. Larger steps yield faster but
|
||||
coarser results. The result will always be topologically correct
|
||||
though.
|
||||
allow_degenerate : bool
|
||||
Whether to allow degenerate (i.e. zero-area) triangles in the
|
||||
end-result. Default True. If False, degenerate triangles are
|
||||
removed, at the cost of making the algorithm slower.
|
||||
use_classic : bool
|
||||
If given and True, the classic marching cubes by Lorensen (1987)
|
||||
is used. This option is included for reference purposes. Note
|
||||
that this algorithm has ambiguities and is not guaranteed to
|
||||
produce a topologically correct result. The results with using
|
||||
this option are *not* generally the same as the
|
||||
``marching_cubes_classic()`` function.
|
||||
mask : (M, N, P) array
|
||||
Boolean array. The marching cube algorithm will be computed only on
|
||||
True elements. This will save computational time when interfaces
|
||||
are located within certain region of the volume M, N, P-e.g. the top
|
||||
half of the cube-and also allow to compute finite surfaces-i.e. open
|
||||
surfaces that do not end at the border of the cube.
|
||||
|
||||
Returns
|
||||
-------
|
||||
verts : (V, 3) array
|
||||
Spatial coordinates for V unique mesh vertices. Coordinate order
|
||||
matches input `volume` (M, N, P).
|
||||
faces : (F, 3) array
|
||||
Define triangular faces via referencing vertex indices from ``verts``.
|
||||
This algorithm specifically outputs triangles, so each face has
|
||||
exactly three indices.
|
||||
normals : (V, 3) array
|
||||
The normal direction at each vertex, as calculated from the
|
||||
data.
|
||||
values : (V, ) array
|
||||
Gives a measure for the maximum value of the data in the local region
|
||||
near each vertex. This can be used by visualization tools to apply
|
||||
a colormap to the mesh.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The algorithm [1] is an improved version of Chernyaev's Marching
|
||||
Cubes 33 algorithm. It is an efficient algorithm that relies on
|
||||
heavy use of lookup tables to handle the many different cases,
|
||||
keeping the algorithm relatively easy. This implementation is
|
||||
written in Cython, ported from Lewiner's C++ implementation.
|
||||
|
||||
To quantify the area of an isosurface generated by this algorithm, pass
|
||||
verts and faces to `skimage.measure.mesh_surface_area`.
|
||||
|
||||
Regarding visualization of algorithm output, to contour a volume
|
||||
named `myvolume` about the level 0.0, using the ``mayavi`` package::
|
||||
|
||||
>>> from mayavi import mlab # doctest: +SKIP
|
||||
>>> verts, faces, normals, values = marching_cubes_lewiner(myvolume, 0.0) # doctest: +SKIP
|
||||
>>> mlab.triangular_mesh([vert[0] for vert in verts],
|
||||
... [vert[1] for vert in verts],
|
||||
... [vert[2] for vert in verts],
|
||||
... faces) # doctest: +SKIP
|
||||
>>> mlab.show() # doctest: +SKIP
|
||||
|
||||
Similarly using the ``visvis`` package::
|
||||
|
||||
>>> import visvis as vv # doctest: +SKIP
|
||||
>>> verts, faces, normals, values = marching_cubes_lewiner(myvolume, 0.0) # doctest: +SKIP
|
||||
>>> vv.mesh(np.fliplr(verts), faces, normals, values) # doctest: +SKIP
|
||||
>>> vv.use().Run() # doctest: +SKIP
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Thomas Lewiner, Helio Lopes, Antonio Wilson Vieira and Geovan
|
||||
Tavares. Efficient implementation of Marching Cubes' cases with
|
||||
topological guarantees. Journal of Graphics Tools 8(2)
|
||||
pp. 1-15 (december 2003).
|
||||
:DOI:`10.1080/10867651.2003.10487582`
|
||||
|
||||
See Also
|
||||
--------
|
||||
skimage.measure.marching_cubes
|
||||
skimage.measure.mesh_surface_area
|
||||
|
||||
"""
|
||||
|
||||
# Deprecate the function in favor of marching_cubes
|
||||
warnings.warn("marching_cubes_lewiner is deprecated in favor of "
|
||||
"marching_cubes. marching_cubes_lewiner will "
|
||||
"be removed in version 0.19",
|
||||
FutureWarning, stacklevel=2)
|
||||
|
||||
return _marching_cubes_lewiner(volume, level, spacing, gradient_direction,
|
||||
step_size, allow_degenerate, use_classic, mask)
|
||||
|
||||
|
||||
def _marching_cubes_lewiner(volume, level, spacing, gradient_direction,
|
||||
step_size, allow_degenerate, use_classic, mask):
|
||||
"""Lewiner et al. algorithm for marching cubes. See
|
||||
marching_cubes_lewiner for documentation.
|
||||
|
||||
"""
|
||||
|
||||
# Check volume and ensure its in the format that the alg needs
|
||||
if not isinstance(volume, np.ndarray) or (volume.ndim != 3):
|
||||
raise ValueError('Input volume should be a 3D numpy array.')
|
||||
if volume.shape[0] < 2 or volume.shape[1] < 2 or volume.shape[2] < 2:
|
||||
raise ValueError("Input array must be at least 2x2x2.")
|
||||
volume = np.ascontiguousarray(volume,
|
||||
np.float32) # no copy if not necessary
|
||||
|
||||
# Check/convert other inputs:
|
||||
# level
|
||||
if level is None:
|
||||
level = 0.5 * (volume.min() + volume.max())
|
||||
else:
|
||||
level = float(level)
|
||||
if level < volume.min() or level > volume.max():
|
||||
raise ValueError("Surface level must be within volume data range.")
|
||||
# spacing
|
||||
if len(spacing) != 3:
|
||||
raise ValueError("`spacing` must consist of three floats.")
|
||||
# step_size
|
||||
step_size = int(step_size)
|
||||
if step_size < 1:
|
||||
raise ValueError('step_size must be at least one.')
|
||||
# use_classic
|
||||
use_classic = bool(use_classic)
|
||||
|
||||
# Get LutProvider class (reuse if possible)
|
||||
L = _get_mc_luts()
|
||||
|
||||
# Check if a mask array is passed
|
||||
if mask is not None:
|
||||
if not mask.shape == volume.shape:
|
||||
raise ValueError('volume and mask must have the same shape.')
|
||||
|
||||
# Apply algorithm
|
||||
func = _marching_cubes_lewiner_cy.marching_cubes
|
||||
vertices, faces, normals, values = func(volume, level, L,
|
||||
step_size, use_classic, mask)
|
||||
|
||||
if not len(vertices):
|
||||
raise RuntimeError('No surface found at the given iso value.')
|
||||
|
||||
# Output in z-y-x order, as is common in skimage
|
||||
vertices = np.fliplr(vertices)
|
||||
normals = np.fliplr(normals)
|
||||
|
||||
# Finishing touches to output
|
||||
faces.shape = -1, 3
|
||||
if gradient_direction == 'descent':
|
||||
# MC implementation is right-handed, but gradient_direction is
|
||||
# left-handed
|
||||
faces = np.fliplr(faces)
|
||||
elif not gradient_direction == 'ascent':
|
||||
raise ValueError("Incorrect input %s in `gradient_direction`, see "
|
||||
"docstring." % (gradient_direction))
|
||||
if not np.array_equal(spacing, (1, 1, 1)):
|
||||
vertices = vertices * np.r_[spacing]
|
||||
|
||||
if allow_degenerate:
|
||||
return vertices, faces, normals, values
|
||||
else:
|
||||
fun = _marching_cubes_lewiner_cy.remove_degenerate_faces
|
||||
return fun(vertices.astype(np.float32), faces, normals, values)
|
||||
|
||||
|
||||
def _to_array(args):
|
||||
shape, text = args
|
||||
byts = base64.decodebytes(text.encode('utf-8'))
|
||||
ar = np.frombuffer(byts, dtype='int8')
|
||||
ar.shape = shape
|
||||
return ar
|
||||
|
||||
|
||||
# Map an edge-index to two relative pixel positions. The ege index
|
||||
# represents a point that lies somewhere in between these pixels.
|
||||
# Linear interpolation should be used to determine where it is exactly.
|
||||
# 0
|
||||
# 3 1 -> 0x
|
||||
# 2 xx
|
||||
EDGETORELATIVEPOSX = np.array([ [0,1],[1,1],[1,0],[0,0], [0,1],[1,1],[1,0],[0,0], [0,0],[1,1],[1,1],[0,0] ], 'int8')
|
||||
EDGETORELATIVEPOSY = np.array([ [0,0],[0,1],[1,1],[1,0], [0,0],[0,1],[1,1],[1,0], [0,0],[0,0],[1,1],[1,1] ], 'int8')
|
||||
EDGETORELATIVEPOSZ = np.array([ [0,0],[0,0],[0,0],[0,0], [1,1],[1,1],[1,1],[1,1], [0,1],[0,1],[0,1],[0,1] ], 'int8')
|
||||
|
||||
|
||||
def _get_mc_luts():
|
||||
""" Kind of lazy obtaining of the luts.
|
||||
"""
|
||||
if not hasattr(mcluts, 'THE_LUTS'):
|
||||
|
||||
mcluts.THE_LUTS = _marching_cubes_lewiner_cy.LutProvider(
|
||||
EDGETORELATIVEPOSX, EDGETORELATIVEPOSY, EDGETORELATIVEPOSZ,
|
||||
|
||||
_to_array(mcluts.CASESCLASSIC), _to_array(mcluts.CASES),
|
||||
|
||||
_to_array(mcluts.TILING1), _to_array(mcluts.TILING2), _to_array(mcluts.TILING3_1), _to_array(mcluts.TILING3_2),
|
||||
_to_array(mcluts.TILING4_1), _to_array(mcluts.TILING4_2), _to_array(mcluts.TILING5), _to_array(mcluts.TILING6_1_1),
|
||||
_to_array(mcluts.TILING6_1_2), _to_array(mcluts.TILING6_2), _to_array(mcluts.TILING7_1),
|
||||
_to_array(mcluts.TILING7_2), _to_array(mcluts.TILING7_3), _to_array(mcluts.TILING7_4_1),
|
||||
_to_array(mcluts.TILING7_4_2), _to_array(mcluts.TILING8), _to_array(mcluts.TILING9),
|
||||
_to_array(mcluts.TILING10_1_1), _to_array(mcluts.TILING10_1_1_), _to_array(mcluts.TILING10_1_2),
|
||||
_to_array(mcluts.TILING10_2), _to_array(mcluts.TILING10_2_), _to_array(mcluts.TILING11),
|
||||
_to_array(mcluts.TILING12_1_1), _to_array(mcluts.TILING12_1_1_), _to_array(mcluts.TILING12_1_2),
|
||||
_to_array(mcluts.TILING12_2), _to_array(mcluts.TILING12_2_), _to_array(mcluts.TILING13_1),
|
||||
_to_array(mcluts.TILING13_1_), _to_array(mcluts.TILING13_2), _to_array(mcluts.TILING13_2_),
|
||||
_to_array(mcluts.TILING13_3), _to_array(mcluts.TILING13_3_), _to_array(mcluts.TILING13_4),
|
||||
_to_array(mcluts.TILING13_5_1), _to_array(mcluts.TILING13_5_2), _to_array(mcluts.TILING14),
|
||||
|
||||
_to_array(mcluts.TEST3), _to_array(mcluts.TEST4), _to_array(mcluts.TEST6),
|
||||
_to_array(mcluts.TEST7), _to_array(mcluts.TEST10), _to_array(mcluts.TEST12),
|
||||
_to_array(mcluts.TEST13), _to_array(mcluts.SUBCONFIG13),
|
||||
)
|
||||
|
||||
return mcluts.THE_LUTS
|
Binary file not shown.
|
@ -0,0 +1,527 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This file was auto-generated from `mc_meta/LookUpTable.h` by
|
||||
# `mc_meta/createluts.py`.
|
||||
|
||||
#static const char casesClassic[256][16]
|
||||
CASESCLASSIC = (256, 16), """
|
||||
/////////////////////wAIA/////////////////8AAQn/////////////////AQgDCQgB////
|
||||
/////////wECCv////////////////8ACAMBAgr/////////////CQIKAAIJ/////////////wII
|
||||
AwIKCAoJCP////////8DCwL/////////////////AAsCCAsA/////////////wEJAAIDC///////
|
||||
//////8BCwIBCQsJCAv/////////AwoBCwoD/////////////wAKAQAICggLCv////////8DCQAD
|
||||
CwkLCgn/////////CQgKCggL/////////////wQHCP////////////////8EAwAHAwT/////////
|
||||
////AAEJCAQH/////////////wQBCQQHAQcDAf////////8BAgoIBAf/////////////AwQHAwAE
|
||||
AQIK/////////wkCCgkAAggEB/////////8CCgkCCQcCBwMHCQT/////CAQHAwsC////////////
|
||||
/wsEBwsCBAIABP////////8JAAEIBAcCAwv/////////BAcLCQQLCQsCCQIB/////wMKAQMLCgcI
|
||||
BP////////8BCwoBBAsBAAQHCwT/////BAcICQALCQsKCwAD/////wQHCwQLCQkLCv////////8J
|
||||
BQT/////////////////CQUEAAgD/////////////wAFBAEFAP////////////8IBQQIAwUDAQX/
|
||||
////////AQIKCQUE/////////////wMACAECCgQJBf////////8FAgoFBAIEAAL/////////AgoF
|
||||
AwIFAwUEAwQI/////wkFBAIDC/////////////8ACwIACAsECQX/////////AAUEAAEFAgML////
|
||||
/////wIBBQIFCAIICwQIBf////8KAwsKAQMJBQT/////////BAkFAAgBCAoBCAsK/////wUEAAUA
|
||||
CwULCgsAA/////8FBAgFCAoKCAv/////////CQcIBQcJ/////////////wkDAAkFAwUHA///////
|
||||
//8ABwgAAQcBBQf/////////AQUDAwUH/////////////wkHCAkFBwoBAv////////8KAQIJBQAF
|
||||
AwAFBwP/////CAACCAIFCAUHCgUC/////wIKBQIFAwMFB/////////8HCQUHCAkDCwL/////////
|
||||
CQUHCQcCCQIAAgcL/////wIDCwABCAEHCAEFB/////8LAgELAQcHAQX/////////CQUICAUHCgED
|
||||
CgML/////wUHAAUACQcLAAEACgsKAP8LCgALAAMKBQAIAAcFBwD/CwoFBwsF/////////////woG
|
||||
Bf////////////////8ACAMFCgb/////////////CQABBQoG/////////////wEIAwEJCAUKBv//
|
||||
//////8BBgUCBgH/////////////AQYFAQIGAwAI/////////wkGBQkABgACBv////////8FCQgF
|
||||
CAIFAgYDAgj/////AgMLCgYF/////////////wsACAsCAAoGBf////////8AAQkCAwsFCgb/////
|
||||
////BQoGAQkCCQsCCQgL/////wYDCwYFAwUBA/////////8ACAsACwUABQEFCwb/////AwsGAAMG
|
||||
AAYFAAUJ/////wYFCQYJCwsJCP////////8FCgYEBwj/////////////BAMABAcDBgUK////////
|
||||
/wEJAAUKBggEB/////////8KBgUBCQcBBwMHCQT/////BgECBgUBBAcI/////////wECBQUCBgMA
|
||||
BAMEB/////8IBAcJAAUABgUAAgb/////BwMJBwkEAwIJBQkGAgYJ/wMLAgcIBAoGBf////////8F
|
||||
CgYEBwIEAgACBwv/////AAEJBAcIAgMLBQoG/////wkCAQkLAgkECwcLBAUKBv8IBAcDCwUDBQEF
|
||||
Cwb/////BQELBQsGAQALBwsEAAQL/wAFCQAGBQADBgsGAwgEB/8GBQkGCQsEBwkHCwn/////CgQJ
|
||||
BgQK/////////////wQKBgQJCgAIA/////////8KAAEKBgAGBAD/////////CAMBCAEGCAYEBgEK
|
||||
/////wEECQECBAIGBP////////8DAAgBAgkCBAkCBgT/////AAIEBAIG/////////////wgDAggC
|
||||
BAQCBv////////8KBAkKBgQLAgP/////////AAgCAggLBAkKBAoG/////wMLAgABBgAGBAYBCv//
|
||||
//8GBAEGAQoECAECAQsICwH/CQYECQMGCQEDCwYD/////wgLAQgBAAsGAQkBBAYEAf8DCwYDBgAA
|
||||
BgT/////////BgQICwYI/////////////wcKBgcICggJCv////////8ABwMACgcACQoGBwr/////
|
||||
CgYHAQoHAQcIAQgA/////woGBwoHAQEHA/////////8BAgYBBggBCAkIBgf/////AgYJAgkBBgcJ
|
||||
AAkDBwMJ/wcIAAcABgYAAv////////8HAwIGBwL/////////////AgMLCgYICggJCAYH/////wIA
|
||||
BwIHCwAJBwYHCgkKB/8BCAABBwgBCgcGBwoCAwv/CwIBCwEHCgYBBgcB/////wgJBggGBwkBBgsG
|
||||
AwEDBv8ACQELBgf/////////////BwgABwAGAwsACwYA/////wcLBv////////////////8HBgv/
|
||||
////////////////AwAICwcG/////////////wABCQsHBv////////////8IAQkIAwELBwb/////
|
||||
////CgECBgsH/////////////wECCgMACAYLB/////////8CCQACCgkGCwf/////////BgsHAgoD
|
||||
CggDCgkI/////wcCAwYCB/////////////8HAAgHBgAGAgD/////////AgcGAgMHAAEJ////////
|
||||
/wEGAgEIBgEJCAgHBv////8KBwYKAQcBAwf/////////CgcGAQcKAQgHAQAI/////wADBwAHCgAK
|
||||
CQYKB/////8HBgoHCggICgn/////////BggECwgG/////////////wMGCwMABgAEBv////////8I
|
||||
BgsIBAYJAAH/////////CQQGCQYDCQMBCwMG/////wYIBAYLCAIKAf////////8BAgoDAAsABgsA
|
||||
BAb/////BAsIBAYLAAIJAgoJ/////woJAwoDAgkEAwsDBgQGA/8IAgMIBAIEBgL/////////AAQC
|
||||
BAYC/////////////wEJAAIDBAIEBgQDCP////8BCQQBBAICBAb/////////CAEDCAYBCAQGBgoB
|
||||
/////woBAAoABgYABP////////8EBgMEAwgGCgMAAwkKCQP/CgkEBgoE/////////////wQJBQcG
|
||||
C/////////////8ACAMECQULBwb/////////BQABBQQABwYL/////////wsHBggDBAMFBAMBBf//
|
||||
//8JBQQKAQIHBgv/////////BgsHAQIKAAgDBAkF/////wcGCwUECgQCCgQAAv////8DBAgDBQQD
|
||||
AgUKBQILBwb/BwIDBwYCBQQJ/////////wkFBAAIBgAGAgYIB/////8DBgIDBwYBBQAFBAD/////
|
||||
BgIIBggHAgEIBAgFAQUI/wkFBAoBBgEHBgEDB/////8BBgoBBwYBAAcIBwAJBQT/BAAKBAoFAAMK
|
||||
BgoHAwcK/wcGCgcKCAUECgQICv////8GCQUGCwkLCAn/////////AwYLAAYDAAUGAAkF/////wAL
|
||||
CAAFCwABBQUGC/////8GCwMGAwUFAwH/////////AQIKCQULCQsICwUG/////wALAwAGCwAJBgUG
|
||||
CQECCv8LCAULBQYIAAUKBQIAAgX/BgsDBgMFAgoDCgUD/////wUICQUCCAUGAgMIAv////8JBQYJ
|
||||
BgAABgL/////////AQUIAQgABQYIAwgCBgII/wEFBgIBBv////////////8BAwYBBgoDCAYFBgkI
|
||||
CQb/CgEACgAGCQUABQYA/////wADCAUGCv////////////8KBQb/////////////////CwUKBwUL
|
||||
/////////////wsFCgsHBQgDAP////////8FCwcFCgsBCQD/////////CgcFCgsHCQgBCAMB////
|
||||
/wsBAgsHAQcFAf////////8ACAMBAgcBBwUHAgv/////CQcFCQIHCQACAgsH/////wcFAgcCCwUJ
|
||||
AgMCCAkIAv8CBQoCAwUDBwX/////////CAIACAUCCAcFCgIF/////wkAAQUKAwUDBwMKAv////8J
|
||||
CAIJAgEIBwIKAgUHBQL/AQMFAwcF/////////////wAIBwAHAQEHBf////////8JAAMJAwUFAwf/
|
||||
////////CQgHBQkH/////////////wUIBAUKCAoLCP////////8FAAQFCwAFCgsLAwD/////AAEJ
|
||||
CAQKCAoLCgQF/////woLBAoEBQsDBAkEAQMBBP8CBQECCAUCCwgEBQj/////AAQLAAsDBAULAgsB
|
||||
BQEL/wACBQAFCQILBQQFCAsIBf8JBAUCCwP/////////////AgUKAwUCAwQFAwgE/////wUKAgUC
|
||||
BAQCAP////////8DCgIDBQoDCAUEBQgAAQn/BQoCBQIEAQkCCQQC/////wgEBQgFAwMFAf//////
|
||||
//8ABAUBAAX/////////////CAQFCAUDCQAFAAMF/////wkEBf////////////////8ECwcECQsJ
|
||||
Cgv/////////AAgDBAkHCQsHCQoL/////wEKCwELBAEEAAcEC/////8DAQQDBAgBCgQHBAsKCwT/
|
||||
BAsHCQsECQILCQEC/////wkHBAkLBwkBCwILAQAIA/8LBwQLBAICBAD/////////CwcECwQCCAME
|
||||
AwIE/////wIJCgIHCQIDBwcECf////8JCgcJBwQKAgcIBwACAAf/AwcKAwoCBwQKAQoABAAK/wEK
|
||||
AggHBP////////////8ECQEEAQcHAQP/////////BAkBBAEHAAgBCAcB/////wQAAwcEA///////
|
||||
//////8ECAf/////////////////CQoICgsI/////////////wMACQMJCwsJCv////////8AAQoA
|
||||
CggICgv/////////AwEKCwMK/////////////wECCwELCQkLCP////////8DAAkDCQsBAgkCCwn/
|
||||
////AAILCAAL/////////////wMCC/////////////////8CAwgCCAoKCAn/////////CQoCAAkC
|
||||
/////////////wIDCAIICgABCAEKCP////8BCgL/////////////////AQMICQEI////////////
|
||||
/wAJAf////////////////8AAwj//////////////////////////////////////w==
|
||||
"""
|
||||
|
||||
#static const char cases[256][2]
|
||||
CASES = (256, 2), """
|
||||
AP8BAAEBAgABAgMAAgMFAAEDAgEDAwUBAgUFBAUJCAABBAICAwQFAgQCBgIGCQsAAwgFBQcDCQEG
|
||||
EA4DDAwFGAEFAwECBAUDAwYHAAUKCQAEAwYEBgsOAQYRDAQLBgUZAggFBwUMCAEGEgwFDgcFHAYV
|
||||
CwQMDwUeCgUGIAYnAgwBBgQAAwUGAAIGBgMFCw4AAwkGBQcEDAEFDgsDCQQFGgMKBgYHBQwCBhMK
|
||||
AQwNBhgHBwwJDQEHCQwUBiEHDQMMAgoGBwUNCwIFEAwHCAMFHQYWCgIMEQYbDgkGIgUnAg4FFA4F
|
||||
CQUFIAsKBiMFKQIQDBcGJQcOAxAGLgQGAxUBCAEHAwIEAQYBAwcHAQYKDAACBwUGBgwLAQUPCQIO
|
||||
BgUbAgkFCAYNDgIGFAwGCgMGGQUSCAIMEAUfCwkFIgYoAg0DCwcCBg4MAwcGDQAMDgcIBhcMCgoE
|
||||
BhwMFQcKBikDDQUVCQMLCAUhDBYHCwYqAw4OCwUkBiwCEQYvAxIEBwEJAgsGCAYPCgAFEQwICwcG
|
||||
GgUTDgQMEgYdCAQFIwUoAg8FFgsFDBMGHg4KBiQGKwQECQcFJQcPAxEFLAITAxYBCgUXDAsOCAYf
|
||||
CQYHDAUqAw8LCwYmBi0EBQUtAxMCFQELCAUFJgUrAhIFLgMUAhYBDAUvAhQDFwENAhcBDgEPAP8=
|
||||
"""
|
||||
|
||||
#static const char tiling1[16][3]
|
||||
TILING1 = (16, 3), """
|
||||
AAgDAAEJAQIKAwsCBAcICQUECgYFBwYLBwsGCgUGCQQFBAgHAwILAQoCAAkBAAMI
|
||||
"""
|
||||
|
||||
#static const char tiling2[24][6]
|
||||
TILING2 = (24, 6), """
|
||||
AQgDCQgBAAsCCAsABAMABwMECQIKAAIJAAUEAQUAAwoBCwoDAQYFAgYBBwIDBgIHCQcIBQcJBggE
|
||||
CwgGCgQJBgQKCwUKBwULCwoFBwsFCgkEBgoEBgQICwYICQgHBQkHBwMCBgcCAQUGAgEGAwEKCwMK
|
||||
AAQFAQAFCQoCAAkCBAADBwQDAAILCAALAQMICQEI
|
||||
"""
|
||||
|
||||
#static const char tiling3_1[24][6]
|
||||
TILING3_1 = (24, 6), """
|
||||
AAgDAQIKCQUEAAgDAwAICwcGAQkAAgMLAAEJCAQHCQABBQoGAQIKCQUECgECBgsHCAQHAwsCAgML
|
||||
CgYFBQoGBAcIBAkFBwYLBQkECwYHBgoFCAcECwMCBQYKBwQIAgsDAgEKBwsGCgIBBAUJAQAJBgoF
|
||||
CQEABwQIAAkBCwMCCAADBgcLBAUJAwgAAwgACgIB
|
||||
"""
|
||||
|
||||
#static const char tiling3_2[24][12]
|
||||
TILING3_2 = (24, 12), """
|
||||
CgMCCggDCgEACAoAAwQIAwUEAwAJBQMJBggHBgAIBgsDAAYDCwADCwkACwIBCQsBBwkEBwEJBwgA
|
||||
AQcABgEKBgABCQAGCQYFBAoFBAIKBAkBAgQBBwILBwECBwYKAQcKAgcLAgQHAgMIBAIIBQsGBQML
|
||||
BQoCAwUCCAYHCAoGCAQFCggFCwUGCwkFCwcECQsEBgULBQkLBAcLBAsJBwYIBgoIBQQIBQgKBgsF
|
||||
CwMFAgoFAgUDCwcCBwQCCAMCCAIECwIHAgEHCgYHCgcBBQoECgIEAQkEAQQCCgEGAQAGBgAJBQYJ
|
||||
BAkHCQEHAAgHAAcBAwALAAkLAQILAQsJBwgGCAAGAwsGAwYACAQDBAUDCQADCQMFAgMKAwgKAAEK
|
||||
AAoI
|
||||
"""
|
||||
|
||||
#static const char tiling4_1[8][6]
|
||||
TILING4_1 = (8, 6), """
|
||||
AAgDBQoGAAEJCwcGAQIKCAQHCQUEAgMLBAUJCwMCCgIBBwQICQEABgcLAwgABgoF
|
||||
"""
|
||||
|
||||
#static const char tiling4_2[8][18]
|
||||
TILING4_2 = (8, 18), """
|
||||
CAUABQgGAwYIBgMKAAoDCgAFCQYBBgkHAAcJBwALAQsACwEGCgcCBwoEAQQKBAEIAggBCAIHCwQD
|
||||
BAsFAgULBQIJAwkCCQMEAwQLBQsECwUCCQIFAgkDBAMJAgcKBAoHCgQBCAEEAQgCBwIIAQYJBwkG
|
||||
CQcACwAHAAsBBgELAAUIBggFCAYDCgMGAwoABQAK
|
||||
"""
|
||||
|
||||
#static const char tiling5[48][9]
|
||||
TILING5 = (48, 9), """
|
||||
AggDAgoICgkIAQsCAQkLCQgLBAEJBAcBBwMBCAUECAMFAwEFAAoBAAgKCAsKCwQHCwIEAgAEBwAI
|
||||
BwYABgIACQMACQUDBQcDAwYLAwAGAAQGAwkAAwsJCwoJBQIKBQQCBAACCQYFCQAGAAIGAAcIAAEH
|
||||
AQUHCgABCgYABgQABgMLBgUDBQEDCgcGCgEHAQMHAQQJAQIEAgYECwECCwcBBwUBCAIDCAQCBAYC
|
||||
AgUKAgMFAwcFBwoGBwgKCAkKBgkFBgsJCwgJBQgEBQoICgsIBAsHBAkLCQoLBAcLBAsJCQsKBQQI
|
||||
BQgKCggLBgUJBgkLCwkIBwYKBwoICAoJAgoFAgUDAwUHCAMCCAIEBAIGCwIBCwEHBwEFAQkEAQQC
|
||||
AgQGCgYHCgcBAQcDBgsDBgMFBQMBCgEACgAGBgAEAAgHAAcBAQcFCQUGCQYAAAYCBQoCBQIEBAIA
|
||||
AwAJAwkLCwkKAwsGAwYAAAYECQADCQMFBQMHBwgABwAGBgACCwcECwQCAgQAAAEKAAoICAoLCAQF
|
||||
CAUDAwUBBAkBBAEHBwEDAQILAQsJCQsIAgMIAggKCggJ
|
||||
"""
|
||||
|
||||
#static const char tiling6_1_1[48][9]
|
||||
TILING6_1_1 = (48, 9), """
|
||||
BgUKAwEICQgBCwcGCQMBAwkIAQIKBwAEAAcDAwAIBQIGAgUBBQQJAgALCAsACgYFCAIAAggLCgYF
|
||||
AAQDBwMEAwAIBgQKCQoECAMACgcFBwoLCAQHCgACAAoJBwYLAAIJCgkCAgMLBAEFAQQAAAEJBgMH
|
||||
AwYCCQABCwQGBAsICwcGAQUABAAFAAEJBwULCgsFBAcIAQMKCwoDCQUECwEDAQsKCgECCAUHBQgJ
|
||||
CAQHAgYBBQEGAQIKBAYICwgGAgMLBQcJCAkHCwIDCQYEBgkKCQUEAwcCBgIHBAUJAgcDBwIGAwIL
|
||||
BAYJCgkGCwMCCQcFBwkICgIBCAYEBggLBwQIAQYCBgEFAgEKBwUICQgFBAUJAwELCgsBCAcECgMB
|
||||
AwoLCQEACwUHBQsKBgcLAAUBBQAEAQAJBgQLCAsECQEABwMGAgYDCwMCBQEEAAQBCwYHCQIAAgkK
|
||||
BwQIAgAKCQoAAAMIBQcKCwoHCAADCgQGBAoJBQYKAwQABAMHBQYKAAIICwgCCQQFCwACAAsICAAD
|
||||
BgIFAQUCCgIBBAAHAwcABgcLAQMJCAkDCgUGCAEDAQgJ
|
||||
"""
|
||||
|
||||
#static const char tiling6_1_2[48][27]
|
||||
TILING6_1_2 = (48, 27), """
|
||||
AQwDDAoDBgMKAwYIBQgGCAUMDAkIAQkMDAUKAQwDAQsMCwEGCQYBBgkHDAcJCQgMDAgDCwcMBAwA
|
||||
BAEMAQQKBwoECgcCDAIHBwMMDAMAAQIMBgwCBgMMAwYIBQgGCAUADAAFBQEMDAECAwAMAAwCDAkC
|
||||
BQIJAgULBAsFCwQMDAgLAAgMDAQJAAwCAAoMCgAFCAUABQgGDAYICAsMDAsCCgYMBAwADAUACgAF
|
||||
AAoDBgMKAwYMDAcDBAcMDAYFBAwGDAgGAwYIBgMKAAoDCgAMDAkKBAkMDAAIBQwHBQgMCAUACgAF
|
||||
AAoDDAMKCgsMDAsHCAMMAgwAAggMCAIHCgcCBwoEDAQKCgkMDAkACAQMAgwADAsABwALAAcJBgkH
|
||||
CQYMDAoJAgoMDAYLBQwBBQIMAgULBAsFCwQDDAMEBAAMDAABAgMMBwwDBwAMAAcJBgkHCQYBDAEG
|
||||
BgIMDAIDAAEMBgwEBgkMCQYBCwEGAQsADAALCwgMDAgECQAMBQwBDAYBCwEGAQsABwALAAcMDAQA
|
||||
BQQMDAcGBQwHDAkHAAcJBwALAQsACwEMDAoLBQoMDAEJAwwBDAgBBAEIAQQKBwoECgcMDAsKAwsM
|
||||
DAcIAwwBAwkMCQMECwQDBAsFDAULCwoMDAoBCQUMBwwFBwoMCgcCCAIHAggBDAEICAkMDAkFCgEM
|
||||
BgwCDAcCCAIHAggBBAEIAQQMDAUBBgUMDAQHBgwEDAoEAQQKBAEIAggBCAIMDAsIBgsMDAIKBwwF
|
||||
DAsFAgULBQIJAwkCCQMMDAgJBwgMDAMLBAwGBAsMCwQDCQMEAwkCDAIJCQoMDAoGCwIMBwwDDAQD
|
||||
CQMEAwkCBQIJAgUMDAYCBwYMDAUEAwwHAwQMBAMJAgkDCQIFDAUCAgYMDAYHBAUMBgwEDAsEAwQL
|
||||
BAMJAgkDCQIMDAoJBgoMDAILBQwHBQsMCwUCCQIFAgkDDAMJCQgMDAgHCwMMBAwGBAoMCgQBCAEE
|
||||
AQgCDAIICAsMDAsGCgIMAgwGAgcMBwIIAQgCCAEEDAQBAQUMDAUGBwQMBQwHDAoHAgcKBwIIAQgC
|
||||
CAEMDAkIBQkMDAEKAQwDDAkDBAMJAwQLBQsECwUMDAoLAQoMDAUJAQwDAQgMCAEECgQBBAoHDAcK
|
||||
CgsMDAsDCAcMBwwFBwkMCQcACwAHAAsBDAELCwoMDAoFCQEMAQwFAQYMBgELAAsBCwAHDAcAAAQM
|
||||
DAQFBgcMBAwGDAkGAQYJBgELAAsBCwAMDAgLBAgMDAAJAwwHDAAHCQcABwkGAQYJBgEMDAIGAwIM
|
||||
DAEAAQwFDAIFCwUCBQsEAwQLBAMMDAAEAQAMDAMCAAwCAAsMCwAHCQcABwkGDAYJCQoMDAoCCwYM
|
||||
AAwCDAgCBwIIAgcKBAoHCgQMDAkKAAkMDAQIBwwFDAgFAAUIBQAKAwoACgMMDAsKBwsMDAMIBgwE
|
||||
BggMCAYDCgMGAwoADAAKCgkMDAkECAAMAAwEAAUMBQAKAwoACgMGDAYDAwcMDAcEBQYMAgwADAoA
|
||||
BQAKAAUIBggFCAYMDAsIAgsMDAYKAgwAAgkMCQIFCwUCBQsEDAQLCwgMDAgACQQMAgwGDAMGCAYD
|
||||
BggFAAUIBQAMDAEFAgEMDAADAAwEDAEECgQBBAoHAgcKBwIMDAMHAAMMDAIBAwwBDAsBBgELAQYJ
|
||||
BwkGCQcMDAgJAwgMDAcLAwwBAwoMCgMGCAYDBggFDAUICAkMDAkBCgUM
|
||||
"""
|
||||
|
||||
#static const char tiling6_2[48][15]
|
||||
TILING6_2 = (48, 15), """
|
||||
AQoDBgMKAwYIBQgGCAUJAQsDCwEGCQYBBgkHCAcJBAEAAQQKBwoECgcCAwIHBgMCAwYIBQgGCAUA
|
||||
AQAFAAkCBQIJAgULBAsFCwQIAAoCCgAFCAUABQgGCwYIBAUACgAFAAoDBgMKAwYHBAgGAwYIBgMK
|
||||
AAoDCgAJBQgHCAUACgAFAAoDCwMKAggACAIHCgcCBwoECQQKAgsABwALAAcJBgkHCQYKBQIBAgUL
|
||||
BAsFCwQDAAMEBwADAAcJBgkHCQYBAgEGBgkECQYBCwEGAQsACAALBQYBCwEGAQsABwALAAcEBQkH
|
||||
AAcJBwALAQsACwEKAwgBBAEIAQQKBwoECgcLAwkBCQMECwQDBAsFCgULBwoFCgcCCAIHAggBCQEI
|
||||
BgcCCAIHAggBBAEIAQQFBgoEAQQKBAEIAggBCAILBwsFAgULBQIJAwkCCQMIBAsGCwQDCQMEAwkC
|
||||
CgIJBwQDCQMEAwkCBQIJAgUGAwQHBAMJAgkDCQIFBgUCBgsEAwQLBAMJAgkDCQIKBQsHCwUCCQIF
|
||||
AgkDCAMJBAoGCgQBCAEEAQgCCwIIAgcGBwIIAQgCCAEEBQQBBQoHAgcKBwIIAQgCCAEJAQkDBAMJ
|
||||
AwQLBQsECwUKAQgDCAEECgQBBAoHCwcKBwkFCQcACwAHAAsBCgELAQYFBgELAAsBCwAHBAcABAkG
|
||||
AQYJBgELAAsBCwAIAwAHCQcABwkGAQYJBgECAQIFCwUCBQsEAwQLBAMAAAsCCwAHCQcABwkGCgYJ
|
||||
AAgCBwIIAgcKBAoHCgQJBwgFAAUIBQAKAwoACgMLBggECAYDCgMGAwoACQAKAAUEBQAKAwoACgMG
|
||||
BwYDAgoABQAKAAUIBggFCAYLAgkACQIFCwUCBQsECAQLAgMGCAYDBggFAAUIBQABAAEECgQBBAoH
|
||||
AgcKBwIDAwsBBgELAQYJBwkGCQcIAwoBCgMGCAYDBggFCQUI
|
||||
"""
|
||||
|
||||
#static const char tiling7_1[16][9]
|
||||
TILING7_1 = (16, 9), """
|
||||
CQUECgECCAMACwcGCAMACgECAwAIBQQJBwYLCAQHCQABCwIDCgYFCwIDCQABAAEJBgUKBAcIAQIK
|
||||
BwYLBQQJAgMLBAcIBgUKCwMCCAcECgUGCgIBCwYHCQQFCQEACgUGCAcEBQYKAwILAQAJBwQIAQAJ
|
||||
AwILCAADCQQFCwYHBgcLAAMIAgEKBAUJAgEKAAMI
|
||||
"""
|
||||
|
||||
#static const char tiling7_2[16][3][15]
|
||||
TILING7_2 = (16, 3, 15), """
|
||||
AQIKAwQIBAMFAAUDBQAJAwAICQEEAgQBBAIFCgUCCQUEAAoBCgAICggCAwIIAwAIAQYKBgEHAgcB
|
||||
BwILAQIKCwMGAAYDBgAHCAcACwcGAggDCAIKCAoAAQAKCQUECwMGAAYDBgAHCAcACwcGAwQIBAMF
|
||||
AAUDBQAJAwAIBAkHCwcJBQsJCwUGAAEJAgcLBwIEAwQCBAMIAgMLCAAHAQcABwEECQQBCAQHAwkA
|
||||
CQMLCQsBAgELAgMLAAUJBQAGAQYABgEKAAEJCgIFAwUCBQMGCwYDBgUKAQsCCwEJCwkDAAMJBgUK
|
||||
CAAHAQcABwEECQQBCAQHAAUJBQAGAQYABgEKAAEJBQoECAQKBggKCAYHCwcGCQEEAgQBBAIFCgUC
|
||||
CQUEAQYKBgEHAgcBBwILAQIKBgsFCQULBwkLCQcECAQHCgIFAwUCBQMGCwYDBgUKAgcLBwIEAwQC
|
||||
BAMIAgMLBwgGCgYIBAoICgQFBwQIBQIKAgUDBgMFAwYLCgUGCwcCBAIHAgQDCAMECwMCBggHCAYK
|
||||
CAoEBQQKBgcLBAEJAQQCBQIEAgUKBAUJCgYBBwEGAQcCCwIHCgIBBQsGCwUJCwkHBAcJCgUGBwAI
|
||||
AAcBBAEHAQQJBwQICQUABgAFAAYBCgEGCQEABAoFCgQICggGBwYICwMCCQUABgAFAAYBCgEGCQEA
|
||||
BQIKAgUDBgMFAwYLCgUGAgsBCQELAwkLCQMACQEACwcCBAIHAgQDCAMECwMCBwAIAAcBBAEHAQQJ
|
||||
BwQIAAkDCwMJAQsJCwECBAUJBgMLAwYABwAGAAcIBgcLCAQDBQMEAwUACQAFCAADBwkECQcLCQsF
|
||||
BgULCAADCgYBBwEGAQcCCwIHCgIBBgMLAwYABwAGAAcIBgcLAwgCCgIIAAoICgABCgIBCAQDBQME
|
||||
AwUACQAFCAADBAEJAQQCBQIEAgUKBAUJAQoACAAKAggKCAID
|
||||
"""
|
||||
|
||||
#static const char tiling7_3[16][3][27]
|
||||
TILING7_3 = (16, 3, 27), """
|
||||
DAIKDAoFDAUEDAQIDAgDDAMADAAJDAkBDAECDAUEDAQIDAgDDAMCDAIKDAoBDAEADAAJDAkFBQQM
|
||||
CgUMAgoMAwIMCAMMAAgMAQAMCQEMBAkMDAAIDAgHDAcGDAYKDAoBDAECDAILDAsDDAMADAcGDAYK
|
||||
DAoBDAEADAAIDAgDDAMCDAILDAsHBwYMCAcMAAgMAQAMCgEMAgoMAwIMCwMMBgsMCQUMAAkMAwAM
|
||||
CwMMBgsMBwYMCAcMBAgMBQQMAwAMCwMMBgsMBQYMCQUMBAkMBwQMCAcMAAgMDAMADAAJDAkFDAUG
|
||||
DAYLDAsHDAcEDAQIDAgDDAEJDAkEDAQHDAcLDAsCDAIDDAMIDAgADAABDAQHDAcLDAsCDAIBDAEJ
|
||||
DAkADAADDAMIDAgEBAcMCQQMAQkMAgEMCwIMAwsMAAMMCAAMBwgMDAMLDAsGDAYFDAUJDAkADAAB
|
||||
DAEKDAoCDAIDDAYFDAUJDAkADAADDAMLDAsCDAIBDAEKDAoGBgUMCwYMAwsMAAMMCQAMAQkMAgEM
|
||||
CgIMBQoMCgYMAQoMAAEMCAAMBwgMBAcMCQQMBQkMBgUMAAEMCAAMBwgMBgcMCgYMBQoMBAUMCQQM
|
||||
AQkMDAABDAEKDAoGDAYHDAcIDAgEDAQFDAUJDAkACwcMAgsMAQIMCQEMBAkMBQQMCgUMBgoMBwYM
|
||||
AQIMCQEMBAkMBwQMCwcMBgsMBQYMCgUMAgoMDAECDAILDAsHDAcEDAQJDAkFDAUGDAYKDAoBCAQM
|
||||
AwgMAgMMCgIMBQoMBgUMCwYMBwsMBAcMAgMMCgIMBQoMBAUMCAQMBwgMBgcMCwYMAwsMDAIDDAMI
|
||||
DAgEDAQFDAUKDAoGDAYHDAcLDAsCDAQIDAgDDAMCDAIKDAoFDAUGDAYLDAsHDAcEDAMCDAIKDAoF
|
||||
DAUEDAQIDAgHDAcGDAYLDAsDAwIMCAMMBAgMBQQMCgUMBgoMBwYMCwcMAgsMDAcLDAsCDAIBDAEJ
|
||||
DAkEDAQFDAUKDAoGDAYHDAIBDAEJDAkEDAQHDAcLDAsGDAYFDAUKDAoCAgEMCwIMBwsMBAcMCQQM
|
||||
BQkMBgUMCgYMAQoMDAYKDAoBDAEADAAIDAgHDAcEDAQJDAkFDAUGDAEADAAIDAgHDAcGDAYKDAoF
|
||||
DAUEDAQJDAkBAQAMCgEMBgoMBwYMCAcMBAgMBQQMCQUMAAkMCwMMBgsMBQYMCQUMAAkMAQAMCgEM
|
||||
AgoMAwIMBQYMCQUMAAkMAwAMCwMMAgsMAQIMCgEMBgoMDAUGDAYLDAsDDAMADAAJDAkBDAECDAIK
|
||||
DAoFCQEMBAkMBwQMCwcMAgsMAwIMCAMMAAgMAQAMBwQMCwcMAgsMAQIMCQEMAAkMAwAMCAMMBAgM
|
||||
DAcEDAQJDAkBDAECDAILDAsDDAMADAAIDAgHDAUJDAkADAADDAMLDAsGDAYHDAcIDAgEDAQFDAAD
|
||||
DAMLDAsGDAYFDAUJDAkEDAQHDAcIDAgAAAMMCQAMBQkMBgUMCwYMBwsMBAcMCAQMAwgMCAAMBwgM
|
||||
BgcMCgYMAQoMAgEMCwIMAwsMAAMMBgcMCgYMAQoMAAEMCAAMAwgMAgMMCwIMBwsMDAYHDAcIDAgA
|
||||
DAABDAEKDAoCDAIDDAMLDAsGCgIMBQoMBAUMCAQMAwgMAAMMCQAMAQkMAgEMBAUMCAQMAwgMAgMM
|
||||
CgIMAQoMAAEMCQAMBQkMDAQFDAUKDAoCDAIDDAMIDAgADAABDAEJDAkE
|
||||
"""
|
||||
|
||||
#static const char tiling7_4_1[16][15]
|
||||
TILING7_4_1 = (16, 15), """
|
||||
AwQIBAMKAgoDBAoFCQEAAQYKBgEIAAgBBggHCwMCCwMGCQYDBgkFAAkDBwQIAgcLBwIJAQkCBwkE
|
||||
CAADAAUJBQALAwsABQsGCgIBCAAHCgcABwoGAQoABAUJCQEECwQBBAsHAgsBBQYKCgIFCAUCBQgE
|
||||
AwgCBgcLBQIKAgUIBAgFAggDCwcGBAEJAQQLBwsEAQsCCgYFBwAIAAcKBgoHAAoBCQUECQUACwAF
|
||||
AAsDBgsFAQIKCwcCCQIHAgkBBAkHAwAIBgMLAwYJBQkGAwkACAQHCgYBCAEGAQgABwgGAgMLCAQD
|
||||
CgMEAwoCBQoEAAEJ
|
||||
"""
|
||||
|
||||
#static const char tiling7_4_2[16][27]
|
||||
TILING7_4_2 = (16, 27), """
|
||||
CQQIBAkFCgUJAQoJCgECAAIBAgADCAMACQgACwYKBgsHCAcLAwgLCAMAAgADAAIBCgECCwoCCwMI
|
||||
AAgDCAAJCAkEBQQJBAUHBgcFBwYLBwsICAcLBwgECQQIAAkICQABAwEAAQMCCwIDCAsDCgUJBQoG
|
||||
CwYKAgsKCwIDAQMCAwEACQABCgkBCAAJAQkACQEKCQoFBgUKBQYEBwQGBAcIBAgJCQEKAgoBCgIL
|
||||
CgsGBwYLBgcFBAUHBQQJBQkKCgILAwsCCwMICwgHBAcIBwQGBQYEBgUKBgoLCwIKAgsDCAMLBwgL
|
||||
CAcEBgQHBAYFCgUGCwoGCgEJAQoCCwIKBgsKCwYHBQcGBwUECQQFCgkFCQAIAAkBCgEJBQoJCgUG
|
||||
BAYFBgQHCAcECQgECQUKBgoFCgYLCgsCAwILAgMBAAEDAQAJAQkKCwcIBAgHCAQJCAkAAQAJAAED
|
||||
AgMBAwILAwsICAMLAwgACQAIBAkICQQFBwUEBQcGCwYHCAsHCgYLBwsGCwcICwgDAAMIAwACAQIA
|
||||
AgEKAgoLCAQJBQkECQUKCQoBAgEKAQIAAwACAAMIAAgJ
|
||||
"""
|
||||
|
||||
#static const char tiling8[6][6]
|
||||
TILING8 = (6, 6), """
|
||||
CQgKCggLAQUDAwUHAAQCBAYCAAIEBAIGAQMFAwcFCQoICgsI
|
||||
"""
|
||||
|
||||
#static const char tiling9[8][12]
|
||||
TILING9 = (8, 12), """
|
||||
AgoFAwIFAwUEAwQIBAcLCQQLCQsCCQIBCgcGAQcKAQgHAQAIAwYLAAYDAAUGAAkFAwsGAAMGAAYF
|
||||
AAUJCgYHAQoHAQcIAQgABAsHCQsECQILCQECAgUKAwUCAwQFAwgE
|
||||
"""
|
||||
|
||||
#static const char tiling10_1_1[6][12]
|
||||
TILING10_1_1 = (6, 12), """
|
||||
BQoHCwcKCAEJAQgDAQIFBgUCBAMAAwQHCwAIAAsCBAkGCgYJCQAKAgoABggECAYLBwIDAgcGAAEE
|
||||
BQQBBwkFCQcICgELAwsB
|
||||
"""
|
||||
|
||||
#static const char tiling10_1_1_[6][12]
|
||||
TILING10_1_1_ = (6, 12), """
|
||||
BQkHCAcJCwEKAQsDAwIHBgcCBAEAAQQFCgAJAAoCBAgGCwYICAALAgsABgkECQYKBQIBAgUGAAME
|
||||
BwQDBwoFCgcLCQEIAwgB
|
||||
"""
|
||||
|
||||
#static const char tiling10_1_2[6][24]
|
||||
TILING10_1_2 = (6, 24), """
|
||||
AwsHAwcICQgHBQkHCQUKCQoBAwEKCwMKBwYFBwUEAAQFAQAFAAECAAIDBwMCBgcCCwIKBgsKCwYE
|
||||
CwQIAAgECQAEAAkKAAoCCwIKCwoGBAYKCQQKBAkABAAICwgAAgsABwYFBAcFBwQABwADAgMAAQIA
|
||||
AgEFAgUGBwgDCwcDBwsKBwoFCQUKAQkKCQEDCQMI
|
||||
"""
|
||||
|
||||
#static const char tiling10_2[6][24]
|
||||
TILING10_2 = (6, 24), """
|
||||
DAUJDAkIDAgDDAMBDAEKDAoLDAsHDAcFDAEADAAEDAQHDAcDDAMCDAIGDAYFDAUBBAgMBgQMCgYM
|
||||
CQoMAAkMAgAMCwIMCAsMDAkEDAQGDAYLDAsIDAgADAACDAIKDAoJAAMMBAAMBQQMAQUMAgEMBgIM
|
||||
BwYMAwcMCgUMCwoMAwsMAQMMCQEMCAkMBwgMBQcM
|
||||
"""
|
||||
|
||||
#static const char tiling10_2_[6][24]
|
||||
TILING10_2_ = (6, 24), """
|
||||
CAcMCQgMAQkMAwEMCwMMCgsMBQoMBwUMBAUMAAQMAwAMBwMMBgcMAgYMAQIMBQEMDAsGDAYEDAQJ
|
||||
DAkKDAoCDAIADAAIDAgLBgoMBAYMCAQMCwgMAgsMAAIMCQAMCgkMDAcEDAQADAABDAEFDAUGDAYC
|
||||
DAIDDAMHDAcLDAsKDAoBDAEDDAMIDAgJDAkFDAUH
|
||||
"""
|
||||
|
||||
#static const char tiling11[12][12]
|
||||
TILING11 = (12, 12), """
|
||||
AgoJAgkHAgcDBwkEAQYCAQgGAQkICAcGCAMBCAEGCAYEBgEKAAgLAAsFAAUBBQsGCQUHCQcCCQIA
|
||||
AgcLBQAEBQsABQoLCwMABQQABQALBQsKCwADCQcFCQIHCQACAgsHAAsIAAULAAEFBQYLCAEDCAYB
|
||||
CAQGBgoBAQIGAQYIAQgJCAYHAgkKAgcJAgMHBwQJ
|
||||
"""
|
||||
|
||||
#static const char tiling12_1_1[24][12]
|
||||
TILING12_1_1 = (24, 12), """
|
||||
BwYLCgMCAwoICQgKBgUKCQIBAgkLCAsJCgYFBwkECQcBAwEHBwYLBAgFAwUIBQMBBQQJCAEAAQgK
|
||||
CwoIAQIKAAkDBQMJAwUHCgECAAsDCwAGBAYACAMAAgkBCQIEBgQCAwAIAgsBBwELAQcFBgUKBwsE
|
||||
AgQLBAIACQUEBggHCAYAAgAGCAMABwQLCQsECwkKBAcICwADAAsJCgkLBAcIBQkGAAYJBgACCwcG
|
||||
BAoFCgQCAAIECwIDAQgACAEHBQcBAAEJAwgCBAIIAgQGAgMLAQoABgAKAAYECQABAwoCCgMFBwUD
|
||||
CQABBAUICggFCAoLCAQHBQsGCwUDAQMFBQQJBgoHAQcKBwEDCgECBQYJCwkGCQsICwIDBgcKCAoH
|
||||
CggJ
|
||||
"""
|
||||
|
||||
#static const char tiling12_1_1_[24][12]
|
||||
TILING12_1_1_ = (24, 12), """
|
||||
AwILCgcGBwoICQgKAgEKCQYFBgkLCAsJCQQFBwoGCgcBAwEHBwQIBgsFAwULBQMBAQAJCAUEBQgK
|
||||
CwoIAQAJAgoDBQMKAwUHCwMCAAoBCgAGBAYACQEAAggDCAIEBgQCAwILAAgBBwEIAQcFBgcLBQoE
|
||||
AgQKBAIACAcEBgkFCQYAAgAGCAcEAwALCQsACwkKAAMICwQHBAsJCgkLBAUJBwgGAAYIBgACCgUG
|
||||
BAsHCwQCAAIECAADAQsCCwEHBQcBAAMIAQkCBAIJAgQGAgEKAwsABgALAAYECgIBAwkACQMFBwUD
|
||||
CQQFAAEICggBCAoLCwYHBQgECAUDAQMFBQYKBAkHAQcJBwEDCgUGAQIJCwkCCQsICwYHAgMKCAoD
|
||||
CggJ
|
||||
"""
|
||||
|
||||
#static const char tiling12_1_2[24][24]
|
||||
TILING12_1_2 = (24, 24), """
|
||||
BwMLAwcICQgHBgkHCQYKAgoGCwIGAgsDBgIKAgYLCAsGBQgGCAUJAQkFCgEFAQoCCgkFCQoBAwEK
|
||||
BgMKAwYHBAcGBQQGBAUJBwgLAwsICwMBCwEGBQYBBgUEBgQHCAcEBQEJAQUKCwoFBAsFCwQIAAgE
|
||||
CQAEAAkBAQkKBQoJCgUHCgcCAwIHAgMAAgABCQEACgsCCwoGBAYKAQQKBAEAAwABAgMBAwILCAkA
|
||||
CQgEBgQIAwYIBgMCAQIDAAEDAQAJAwsIBwgLCAcFCAUAAQAFAAECAAIDCwMCBgsKAgoLCgIACgAF
|
||||
BAUABQQHBQcGCwYHCQgECAkAAgAJBQIJAgUGBwYFBAcFBwQICAQACQAEAAkKAAoDCwMKAwsHAwcI
|
||||
BAgHBAAIAAQJCgkEBwoECgcLAwsHCAMHAwgABAkIAAgJCAACCAIHBgcCBwYFBwUECQQFCwoGCgsC
|
||||
AAILBwALAAcEBQQHBgUHBQYKCwgDCAsHBQcLAgULBQIBAAECAwACAAMIAAgJBAkICQQGCQYBAgEG
|
||||
AQIDAQMACAADAgoLBgsKCwYECwQDAAMEAwABAwECCgIBCQoBCgkFBwUJAAcJBwADAgMAAQIAAgEK
|
||||
CQUBCgEFAQoLAQsACAALAAgEAAQJBQkECAsHCwgDAQMIBAEIAQQFBgUEBwYEBgcLBQoJAQkKCQED
|
||||
CQMEBwQDBAcGBAYFCgUGCgYCCwIGAgsIAggBCQEIAQkFAQUKBgoFCwcDCAMHAwgJAwkCCgIJAgoG
|
||||
AgYLBwsG
|
||||
"""
|
||||
|
||||
#static const char tiling12_2[24][24]
|
||||
TILING12_2 = (24, 24), """
|
||||
CQgMCgkMAgoMAwIMCwMMBgsMBwYMCAcMCAsMCQgMAQkMAgEMCgIMBQoMBgUMCwYMAwEMBwMMBAcM
|
||||
CQQMBQkMBgUMCgYMAQoMDAMBDAEFDAUGDAYLDAsHDAcEDAQIDAgDCwoMCAsMAAgMAQAMCQEMBAkM
|
||||
BQQMCgUMDAUHDAcDDAMCDAIKDAoBDAEADAAJDAkFBAYMAAQMAQAMCgEMAgoMAwIMCwMMBgsMBgQM
|
||||
AgYMAwIMCAMMAAgMAQAMCQEMBAkMDAcFDAUBDAEADAAIDAgDDAMCDAILDAsHDAIADAAEDAQFDAUK
|
||||
DAoGDAYHDAcLDAsCAgAMBgIMBwYMCAcMBAgMBQQMCQUMAAkMDAkKDAoLDAsHDAcEDAQIDAgDDAMA
|
||||
DAAJCgkMCwoMBwsMBAcMCAQMAwgMAAMMCQAMDAACDAIGDAYHDAcIDAgEDAQFDAUJDAkAAAIMBAAM
|
||||
BQQMCgUMBgoMBwYMCwcMAgsMBQcMAQUMAAEMCAAMAwgMAgMMCwIMBwsMDAQGDAYCDAIDDAMIDAgA
|
||||
DAABDAEJDAkEDAYEDAQADAABDAEKDAoCDAIDDAMLDAsGBwUMAwcMAgMMCgIMAQoMAAEMCQAMBQkM
|
||||
DAoLDAsIDAgADAABDAEJDAkEDAQFDAUKAQMMBQEMBgUMCwYMBwsMBAcMCAQMAwgMDAEDDAMHDAcE
|
||||
DAQJDAkFDAUGDAYKDAoBDAsIDAgJDAkBDAECDAIKDAoFDAUGDAYLDAgJDAkKDAoCDAIDDAMLDAsG
|
||||
DAYHDAcI
|
||||
"""
|
||||
|
||||
#static const char tiling12_2_[24][24]
|
||||
TILING12_2_ = (24, 24), """
|
||||
DAILDAsHDAcGDAYKDAoJDAkIDAgDDAMCDAEKDAoGDAYFDAUJDAkIDAgLDAsCDAIBDAQFDAUKDAoG
|
||||
DAYHDAcDDAMBDAEJDAkEBwYMCAcMBAgMBQQMAQUMAwEMCwMMBgsMDAAJDAkFDAUEDAQIDAgLDAsK
|
||||
DAoBDAEAAQIMCQEMAAkMAwAMBwMMBQcMCgUMAgoMDAECDAILDAsDDAMADAAEDAQGDAYKDAoBDAMA
|
||||
DAAJDAkBDAECDAIGDAYEDAQIDAgDAwAMCwMMAgsMAQIMBQEMBwUMCAcMAAgMBgUMCwYMBwsMBAcM
|
||||
AAQMAgAMCgIMBQoMDAcEDAQJDAkFDAUGDAYCDAIADAAIDAgHCAcMAAgMAwAMCwMMCgsMCQoMBAkM
|
||||
BwQMDAcIDAgADAADDAMLDAsKDAoJDAkEDAQHBAcMCQQMBQkMBgUMAgYMAAIMCAAMBwgMDAUGDAYL
|
||||
DAsHDAcEDAQADAACDAIKDAoFDAADDAMLDAsCDAIBDAEFDAUHDAcIDAgAAAMMCQAMAQkMAgEMBgIM
|
||||
BAYMCAQMAwgMAgEMCwIMAwsMAAMMBAAMBgQMCgYMAQoMDAIBDAEJDAkADAADDAMHDAcFDAUKDAoC
|
||||
CQAMBQkMBAUMCAQMCwgMCgsMAQoMAAEMDAYHDAcIDAgEDAQFDAUBDAEDDAMLDAsGBQQMCgUMBgoM
|
||||
BwYMAwcMAQMMCQEMBAkMCgEMBgoMBQYMCQUMCAkMCwgMAgsMAQIMCwIMBwsMBgcMCgYMCQoMCAkM
|
||||
AwgMAgMM
|
||||
"""
|
||||
|
||||
#static const char tiling13_1[2][12]
|
||||
TILING13_1 = (2, 12), """
|
||||
CwcGAQIKCAMACQUECAQHAgMLCQABCgYF
|
||||
"""
|
||||
|
||||
#static const char tiling13_1_[2][12]
|
||||
TILING13_1_ = (2, 12), """
|
||||
BwQICwMCAQAJBQYKBgcLCgIBAAMIBAUJ
|
||||
"""
|
||||
|
||||
#static const char tiling13_2[2][6][18]
|
||||
TILING13_2 = (2, 6, 18), """
|
||||
AQIKCwcGAwQIBAMFAAUDBQAJCAMACwcGCQEEAgQBBAIFCgUCCQUECAMAAQYKBgEHAgcBBwILCQUE
|
||||
AQIKCwMGAAYDBgAHCAcACQUECwcGAAoBCgAICggCAwIIAQIKAwAIBAkHCwcJBQsJCwUGAgMLCAQH
|
||||
AAUJBQAGAQYABgEKCQABCAQHCgIFAwUCBQMGCwYDBgUKCQABAgcLBwIEAwQCBAMIBgUKAgMLCAAH
|
||||
AQcABwEECQQBBgUKCAQHAQsCCwEJCwkDAAMJAgMLAAEJBQoECAQKBggKCAYH
|
||||
"""
|
||||
|
||||
#static const char tiling13_2_[2][6][18]
|
||||
TILING13_2_ = (2, 6, 18), """
|
||||
CgUGCwMCBwAIAAcBBAEHAQQJCwMCBwQICQUABgAFAAYBCgEGAQAJBwQIBQIKAgUDBgMFAwYLCgUG
|
||||
AQAJCwcCBAIHAgQDCAMECgUGBwQIAgsBCQELAwkLCQMACwMCCQEABAoFCgQICggGBwYIBgcLCAAD
|
||||
BAEJAQQCBQIEAgUKCAADBAUJCgYBBwEGAQcCCwIHAgEKBAUJBgMLAwYABwAGAAcIBgcLAgEKCAQD
|
||||
BQMEAwUACQAFBgcLBAUJAwgCCgIIAAoICgABCAADCgIBBQsGCwUJCwkHBAcJ
|
||||
"""
|
||||
|
||||
#static const char tiling13_3[2][12][30]
|
||||
TILING13_3 = (2, 12, 30), """
|
||||
CwcGDAIKDAoFDAUEDAQIDAgDDAMADAAJDAkBDAECAQIKCQUMAAkMAwAMCwMMBgsMBwYMCAcMBAgM
|
||||
BQQMCwcGDAUEDAQIDAgDDAMCDAIKDAoBDAEADAAJDAkFAQIKDAMADAAJDAkFDAUGDAYLDAsHDAcE
|
||||
DAQIDAgDCAMACwcMAgsMAQIMCQEMBAkMBQQMCgUMBgoMBwYMCwcGBQQMCgUMAgoMAwIMCAMMAAgM
|
||||
AQAMCQEMBAkMCAMAAQIMCQEMBAkMBwQMCwcMBgsMBQYMCgUMAgoMCQUEDAAIDAgHDAcGDAYKDAoB
|
||||
DAECDAILDAsDDAMACQUEDAcGDAYKDAoBDAEADAAIDAgDDAMCDAILDAsHCAMADAECDAILDAsHDAcE
|
||||
DAQJDAkFDAUGDAYKDAoBCQUEBwYMCAcMAAgMAQAMCgEMAgoMAwIMCwMMBgsMAQIKAwAMCwMMBgsM
|
||||
BQYMCQUMBAkMBwQMCAcMAAgMCAQHDAMLDAsGDAYFDAUJDAkADAABDAEKDAoCDAIDAgMLCgYMAQoM
|
||||
AAEMCAAMBwgMBAcMCQQMBQkMBgUMCAQHDAYFDAUJDAkADAADDAMLDAsCDAIBDAEKDAoGAgMLDAAB
|
||||
DAEKDAoGDAYHDAcIDAgEDAQFDAUJDAkAAAEJCAQMAwgMAgMMCgIMBQoMBgUMCwYMBwsMBAcMCAQH
|
||||
BgUMCwYMAwsMAAMMCQAMAQkMAgEMCgIMBQoMCQABAgMMCgIMBQoMBAUMCAQMBwgMBgcMCwYMAwsM
|
||||
BgUKDAEJDAkEDAQHDAcLDAsCDAIDDAMIDAgADAABBgUKDAQHDAcLDAsCDAIBDAEJDAkADAADDAMI
|
||||
DAgECQABDAIDDAMIDAgEDAQFDAUKDAoGDAYHDAcLDAsCBgUKBAcMCQQMAQkMAgEMCwIMAwsMAAMM
|
||||
CAAMBwgMAgMLAAEMCAAMBwgMBgcMCgYMBQoMBAUMCQQMAQkM
|
||||
"""
|
||||
|
||||
#static const char tiling13_3_[2][12][30]
|
||||
TILING13_3_ = (2, 12, 30), """
|
||||
AwILCAcMAAgMAQAMCgEMBgoMBQYMCQUMBAkMBwQMBQYKDAILDAsHDAcEDAQJDAkBDAEADAAIDAgD
|
||||
DAMCCgUGDAcEDAQJDAkBDAECDAILDAsDDAMADAAIDAgHCwMCDAEADAAIDAgHDAcGDAYKDAoFDAUE
|
||||
DAQJDAkBBwQICwMMBgsMBQYMCQUMAAkMAQAMCgEMAgoMAwIMBwQIBQYMCQUMAAkMAwAMCwMMAgsM
|
||||
AQIMCgEMBgoMCwMCAQAMCgEMBgoMBwYMCAcMBAgMBQQMCQUMAAkMAQAJDAQIDAgDDAMCDAIKDAoF
|
||||
DAUGDAYLDAsHDAcEBwQIDAUGDAYLDAsDDAMADAAJDAkBDAECDAIKDAoFAQAJDAMCDAIKDAoFDAUE
|
||||
DAQIDAgHDAcGDAYLDAsDCgUGBwQMCwcMAgsMAQIMCQEMAAkMAwAMCAMMBAgMCQEAAwIMCAMMBAgM
|
||||
BQQMCgUMBgoMBwYMCwcMAgsMAAMICQQMAQkMAgEMCwIMBwsMBgcMCgYMBQoMBAUMCwYHDAMIDAgE
|
||||
DAQFDAUKDAoCDAIBDAEJDAkADAADBgcLDAQFDAUKDAoCDAIDDAMIDAgADAABDAEJDAkECAADDAIB
|
||||
DAEJDAkEDAQHDAcLDAsGDAYFDAUKDAoCBAUJCAAMBwgMBgcMCgYMAQoMAgEMCwIMAwsMAAMMBAUJ
|
||||
BgcMCgYMAQoMAAEMCAAMAwgMAgMMCwIMBwsMCAADAgEMCwIMBwsMBAcMCQQMBQkMBgUMCgYMAQoM
|
||||
AgEKDAUJDAkADAADDAMLDAsGDAYHDAcIDAgEDAQFBAUJDAYHDAcIDAgADAABDAEKDAoCDAIDDAML
|
||||
DAsGAgEKDAADDAMLDAsGDAYFDAUJDAkEDAQHDAcIDAgABgcLBAUMCAQMAwgMAgMMCgIMAQoMAAEM
|
||||
CQAMBQkMCgIBAAMMCQAMBQkMBgUMCwYMBwsMBAcMCAQMAwgM
|
||||
"""
|
||||
|
||||
#static const char tiling13_4[2][4][36]
|
||||
TILING13_4 = (2, 4, 36), """
|
||||
DAIKDAoFDAUGDAYLDAsHDAcEDAQIDAgDDAMADAAJDAkBDAECCwMMBgsMBwYMCAcMBAgMBQQMCQUM
|
||||
AAkMAQAMCgEMAgoMAwIMCQEMBAkMBQQMCgUMBgoMBwYMCwcMAgsMAwIMCAMMAAgMAQAMDAAIDAgH
|
||||
DAcEDAQJDAkFDAUGDAYKDAoBDAECDAILDAsDDAMADAMLDAsGDAYHDAcIDAgEDAQFDAUJDAkADAAB
|
||||
DAEKDAoCDAIDCAAMBwgMBAcMCQQMBQkMBgUMCgYMAQoMAgEMCwIMAwsMAAMMCgIMBQoMBgUMCwYM
|
||||
BwsMBAcMCAQMAwgMAAMMCQAMAQkMAgEMDAEJDAkEDAQFDAUKDAoGDAYHDAcLDAsCDAIDDAMIDAgA
|
||||
DAAB
|
||||
"""
|
||||
|
||||
#static const char tiling13_5_1[2][4][18]
|
||||
TILING13_5_1 = (2, 4, 18), """
|
||||
BwYLAQAJCgMCAwoFAwUIBAgFAQIKBwQIAwALBgsACQYABgkFAwAIBQYKAQIJBAkCCwQCBAsHBQQJ
|
||||
AwILCAEAAQgHAQcKBgoHBAcIAgEKCwADAAsGAAYJBQkGAgMLBAUJAAEIBwgBCgcBBwoGAAEJBgcL
|
||||
AgMKBQoDCAUDBQgEBgUKAAMICQIBAgkEAgQLBwsE
|
||||
"""
|
||||
|
||||
#static const char tiling13_5_2[2][4][30]
|
||||
TILING13_5_2 = (2, 4, 30), """
|
||||
AQAJBwQIBwgDBwMLAgsDCwIKCwoGBQYKBgUHBAcFBwQICwMCBgsCCgYCBgoFCQUKAQkKCQEAAgAB
|
||||
AAIDBQYKCQEABAkACAQABAgHCwcIAwsICwMCAAIDAgABAwILBQYKBQoBBQEJAAkBCQAICQgEBAgH
|
||||
BAcFBgUHAgEKBAUJBAkABAAIAwgACAMLCAsHBgcLBwYEBQQGBAUJCAADBwgDCwcDBwsGCgYLAgoL
|
||||
CgIBAwECAQMABgcLCgIBBQoBCQUBBQkECAQJAAgJCAADAQMAAwECAAMIBgcLBgsCBgIKAQoCCgEJ
|
||||
CgkFBQkEBQQGBwYE
|
||||
"""
|
||||
|
||||
#static const char tiling14[12][12]
|
||||
TILING14 = (12, 12), """
|
||||
BQkIBQgCBQIGAwIIAgEFAgUIAggLBAgFCQQGCQYDCQMBCwMGAQsKAQQLAQAEBwsECAIACAUCCAcF
|
||||
CgIFAAcDAAoHAAkKBgcKAAMHAAcKAAoJBgoHCAACCAIFCAUHCgUCAQoLAQsEAQQABwQLCQYECQMG
|
||||
CQEDCwYDAgUBAggFAgsIBAUIBQgJBQIIBQYCAwgC
|
||||
"""
|
||||
|
||||
#static const char test3[24]
|
||||
TEST3 = (24,), """
|
||||
BQEEBQECAgMEAwYG+vr9/P3+/v/7/P/7
|
||||
"""
|
||||
|
||||
#static const char test4[8]
|
||||
TEST4 = (8,), """
|
||||
BwcHB/n5+fk=
|
||||
"""
|
||||
|
||||
#static const char test6[48][3]
|
||||
TEST6 = (48, 3), """
|
||||
AgcKBAcLBQcBBQcDAQcJAwcKBgcFAQcIBAcIAQcIAwcLBQcCBQcAAQcJBgcGAgcJBAcIAgcJAgcK
|
||||
BgcHAwcKBAcLAwcLBgcE+vkE/fkL/PkL/fkK+vkH/vkK/vkJ/PkI/vkJ+vkG//kJ+/kA+/kC/fkL
|
||||
//kI/PkI//kI+vkF/fkK//kJ+/kD+/kB/PkL/vkK
|
||||
"""
|
||||
|
||||
#static const char test7[16][5]
|
||||
TEST7 = (16, 5), """
|
||||
AQIFBwEDBAUHAwQBBgcEBAEFBwACAwUHAgECBgcFAgMGBwYDBAYHB/38+vkH/v36+Qb//vr5Bf79
|
||||
+/kC/P/7+QD8//r5BP38+/kD//77+QE=
|
||||
"""
|
||||
|
||||
#static const char test10[6][3]
|
||||
TEST10 = (6, 3), """
|
||||
AgQHBQYHAQMHAQMHBQYHAgQH
|
||||
"""
|
||||
|
||||
#static const char test12[24][4]
|
||||
TEST12 = (24, 4), """
|
||||
BAMHCwMCBwoCBgcFBgQHBwIBBwkFAgcBBQMHAgUBBwAFBAcDBgMHBgEGBwQBBAcIBAEHCAYBBwQD
|
||||
BgcGBAUHAwEFBwADBQcCAgUHAQECBwkEBgcHBgIHBQIDBwoDBAcL
|
||||
"""
|
||||
|
||||
#static const char test13[2][7]
|
||||
TEST13 = (2, 7), """
|
||||
AQIDBAUGBwIDBAEFBgc=
|
||||
"""
|
||||
|
||||
#static const char subconfig13[64]
|
||||
SUBCONFIG13 = (64,), """
|
||||
AAECBwP/C/8ECP//Dv///wUJDBcP/xUmERT/JBohHiwGCg0TEP8ZJRIY/yMWIB0r////Iv//HCr/
|
||||
H/8pGygnLQ==
|
||||
"""
|
469
venv/Lib/site-packages/skimage/measure/_moments.py
Normal file
469
venv/Lib/site-packages/skimage/measure/_moments.py
Normal file
|
@ -0,0 +1,469 @@
|
|||
import numpy as np
|
||||
from .._shared.utils import check_nD
|
||||
from . import _moments_cy
|
||||
import itertools
|
||||
|
||||
|
||||
def moments_coords(coords, order=3):
|
||||
"""Calculate all raw image moments up to a certain order.
|
||||
|
||||
The following properties can be calculated from raw image moments:
|
||||
* Area as: ``M[0, 0]``.
|
||||
* Centroid as: {``M[1, 0] / M[0, 0]``, ``M[0, 1] / M[0, 0]``}.
|
||||
|
||||
Note that raw moments are neither translation, scale nor rotation
|
||||
invariant.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
coords : (N, D) double or uint8 array
|
||||
Array of N points that describe an image of D dimensionality in
|
||||
Cartesian space.
|
||||
order : int, optional
|
||||
Maximum order of moments. Default is 3.
|
||||
|
||||
Returns
|
||||
-------
|
||||
M : (``order + 1``, ``order + 1``, ...) array
|
||||
Raw image moments. (D dimensions)
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Johannes Kilian. Simple Image Analysis By Moments. Durham
|
||||
University, version 0.2, Durham, 2001.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> coords = np.array([[row, col]
|
||||
... for row in range(13, 17)
|
||||
... for col in range(14, 18)], dtype=np.double)
|
||||
>>> M = moments_coords(coords)
|
||||
>>> centroid = (M[1, 0] / M[0, 0], M[0, 1] / M[0, 0])
|
||||
>>> centroid
|
||||
(14.5, 15.5)
|
||||
"""
|
||||
return moments_coords_central(coords, 0, order=order)
|
||||
|
||||
|
||||
def moments_coords_central(coords, center=None, order=3):
|
||||
"""Calculate all central image moments up to a certain order.
|
||||
|
||||
The following properties can be calculated from raw image moments:
|
||||
* Area as: ``M[0, 0]``.
|
||||
* Centroid as: {``M[1, 0] / M[0, 0]``, ``M[0, 1] / M[0, 0]``}.
|
||||
|
||||
Note that raw moments are neither translation, scale nor rotation
|
||||
invariant.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
coords : (N, D) double or uint8 array
|
||||
Array of N points that describe an image of D dimensionality in
|
||||
Cartesian space. A tuple of coordinates as returned by
|
||||
``np.nonzero`` is also accepted as input.
|
||||
center : tuple of float, optional
|
||||
Coordinates of the image centroid. This will be computed if it
|
||||
is not provided.
|
||||
order : int, optional
|
||||
Maximum order of moments. Default is 3.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Mc : (``order + 1``, ``order + 1``, ...) array
|
||||
Central image moments. (D dimensions)
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Johannes Kilian. Simple Image Analysis By Moments. Durham
|
||||
University, version 0.2, Durham, 2001.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> coords = np.array([[row, col]
|
||||
... for row in range(13, 17)
|
||||
... for col in range(14, 18)])
|
||||
>>> moments_coords_central(coords)
|
||||
array([[16., 0., 20., 0.],
|
||||
[ 0., 0., 0., 0.],
|
||||
[20., 0., 25., 0.],
|
||||
[ 0., 0., 0., 0.]])
|
||||
|
||||
As seen above, for symmetric objects, odd-order moments (columns 1 and 3,
|
||||
rows 1 and 3) are zero when centered on the centroid, or center of mass,
|
||||
of the object (the default). If we break the symmetry by adding a new
|
||||
point, this no longer holds:
|
||||
|
||||
>>> coords2 = np.concatenate((coords, [[17, 17]]), axis=0)
|
||||
>>> np.round(moments_coords_central(coords2),
|
||||
... decimals=2) # doctest: +NORMALIZE_WHITESPACE
|
||||
array([[17. , 0. , 22.12, -2.49],
|
||||
[ 0. , 3.53, 1.73, 7.4 ],
|
||||
[25.88, 6.02, 36.63, 8.83],
|
||||
[ 4.15, 19.17, 14.8 , 39.6 ]])
|
||||
|
||||
Image moments and central image moments are equivalent (by definition)
|
||||
when the center is (0, 0):
|
||||
|
||||
>>> np.allclose(moments_coords(coords),
|
||||
... moments_coords_central(coords, (0, 0)))
|
||||
True
|
||||
"""
|
||||
if isinstance(coords, tuple):
|
||||
# This format corresponds to coordinate tuples as returned by
|
||||
# e.g. np.nonzero: (row_coords, column_coords).
|
||||
# We represent them as an npoints x ndim array.
|
||||
coords = np.transpose(coords)
|
||||
check_nD(coords, 2)
|
||||
ndim = coords.shape[1]
|
||||
if center is None:
|
||||
center = np.mean(coords, axis=0)
|
||||
|
||||
# center the coordinates
|
||||
coords = coords.astype(float) - center
|
||||
|
||||
# generate all possible exponents for each axis in the given set of points
|
||||
# produces a matrix of shape (N, D, order + 1)
|
||||
coords = coords[..., np.newaxis] ** np.arange(order + 1)
|
||||
|
||||
# add extra dimensions for proper broadcasting
|
||||
coords = coords.reshape(coords.shape + (1,) * (ndim - 1))
|
||||
|
||||
calc = 1
|
||||
|
||||
for axis in range(ndim):
|
||||
# isolate each point's axis
|
||||
isolated_axis = coords[:, axis]
|
||||
|
||||
# rotate orientation of matrix for proper broadcasting
|
||||
isolated_axis = np.moveaxis(isolated_axis, 1, 1 + axis)
|
||||
|
||||
# calculate the moments for each point, one axis at a time
|
||||
calc = calc * isolated_axis
|
||||
|
||||
# sum all individual point moments to get our final answer
|
||||
Mc = np.sum(calc, axis=0)
|
||||
|
||||
return Mc
|
||||
|
||||
|
||||
def moments(image, order=3):
|
||||
"""Calculate all raw image moments up to a certain order.
|
||||
|
||||
The following properties can be calculated from raw image moments:
|
||||
* Area as: ``M[0, 0]``.
|
||||
* Centroid as: {``M[1, 0] / M[0, 0]``, ``M[0, 1] / M[0, 0]``}.
|
||||
|
||||
Note that raw moments are neither translation, scale nor rotation
|
||||
invariant.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : nD double or uint8 array
|
||||
Rasterized shape as image.
|
||||
order : int, optional
|
||||
Maximum order of moments. Default is 3.
|
||||
|
||||
Returns
|
||||
-------
|
||||
m : (``order + 1``, ``order + 1``) array
|
||||
Raw image moments.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Wilhelm Burger, Mark Burge. Principles of Digital Image Processing:
|
||||
Core Algorithms. Springer-Verlag, London, 2009.
|
||||
.. [2] B. Jähne. Digital Image Processing. Springer-Verlag,
|
||||
Berlin-Heidelberg, 6. edition, 2005.
|
||||
.. [3] T. H. Reiss. Recognizing Planar Objects Using Invariant Image
|
||||
Features, from Lecture notes in computer science, p. 676. Springer,
|
||||
Berlin, 1993.
|
||||
.. [4] https://en.wikipedia.org/wiki/Image_moment
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> image = np.zeros((20, 20), dtype=np.double)
|
||||
>>> image[13:17, 13:17] = 1
|
||||
>>> M = moments(image)
|
||||
>>> centroid = (M[1, 0] / M[0, 0], M[0, 1] / M[0, 0])
|
||||
>>> centroid
|
||||
(14.5, 14.5)
|
||||
"""
|
||||
return moments_central(image, (0,) * image.ndim, order=order)
|
||||
|
||||
|
||||
def moments_central(image, center=None, order=3, **kwargs):
|
||||
"""Calculate all central image moments up to a certain order.
|
||||
|
||||
The center coordinates (cr, cc) can be calculated from the raw moments as:
|
||||
{``M[1, 0] / M[0, 0]``, ``M[0, 1] / M[0, 0]``}.
|
||||
|
||||
Note that central moments are translation invariant but not scale and
|
||||
rotation invariant.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : nD double or uint8 array
|
||||
Rasterized shape as image.
|
||||
center : tuple of float, optional
|
||||
Coordinates of the image centroid. This will be computed if it
|
||||
is not provided.
|
||||
order : int, optional
|
||||
The maximum order of moments computed.
|
||||
|
||||
Returns
|
||||
-------
|
||||
mu : (``order + 1``, ``order + 1``) array
|
||||
Central image moments.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Wilhelm Burger, Mark Burge. Principles of Digital Image Processing:
|
||||
Core Algorithms. Springer-Verlag, London, 2009.
|
||||
.. [2] B. Jähne. Digital Image Processing. Springer-Verlag,
|
||||
Berlin-Heidelberg, 6. edition, 2005.
|
||||
.. [3] T. H. Reiss. Recognizing Planar Objects Using Invariant Image
|
||||
Features, from Lecture notes in computer science, p. 676. Springer,
|
||||
Berlin, 1993.
|
||||
.. [4] https://en.wikipedia.org/wiki/Image_moment
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> image = np.zeros((20, 20), dtype=np.double)
|
||||
>>> image[13:17, 13:17] = 1
|
||||
>>> M = moments(image)
|
||||
>>> centroid = (M[1, 0] / M[0, 0], M[0, 1] / M[0, 0])
|
||||
>>> moments_central(image, centroid)
|
||||
array([[16., 0., 20., 0.],
|
||||
[ 0., 0., 0., 0.],
|
||||
[20., 0., 25., 0.],
|
||||
[ 0., 0., 0., 0.]])
|
||||
"""
|
||||
if center is None:
|
||||
center = centroid(image)
|
||||
calc = image.astype(float)
|
||||
for dim, dim_length in enumerate(image.shape):
|
||||
delta = np.arange(dim_length, dtype=float) - center[dim]
|
||||
powers_of_delta = delta[:, np.newaxis] ** np.arange(order + 1)
|
||||
calc = np.rollaxis(calc, dim, image.ndim)
|
||||
calc = np.dot(calc, powers_of_delta)
|
||||
calc = np.rollaxis(calc, -1, dim)
|
||||
return calc
|
||||
|
||||
|
||||
def moments_normalized(mu, order=3):
|
||||
"""Calculate all normalized central image moments up to a certain order.
|
||||
|
||||
Note that normalized central moments are translation and scale invariant
|
||||
but not rotation invariant.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mu : (M,[ ...,] M) array
|
||||
Central image moments, where M must be greater than or equal
|
||||
to ``order``.
|
||||
order : int, optional
|
||||
Maximum order of moments. Default is 3.
|
||||
|
||||
Returns
|
||||
-------
|
||||
nu : (``order + 1``,[ ...,] ``order + 1``) array
|
||||
Normalized central image moments.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Wilhelm Burger, Mark Burge. Principles of Digital Image Processing:
|
||||
Core Algorithms. Springer-Verlag, London, 2009.
|
||||
.. [2] B. Jähne. Digital Image Processing. Springer-Verlag,
|
||||
Berlin-Heidelberg, 6. edition, 2005.
|
||||
.. [3] T. H. Reiss. Recognizing Planar Objects Using Invariant Image
|
||||
Features, from Lecture notes in computer science, p. 676. Springer,
|
||||
Berlin, 1993.
|
||||
.. [4] https://en.wikipedia.org/wiki/Image_moment
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> image = np.zeros((20, 20), dtype=np.double)
|
||||
>>> image[13:17, 13:17] = 1
|
||||
>>> m = moments(image)
|
||||
>>> centroid = (m[0, 1] / m[0, 0], m[1, 0] / m[0, 0])
|
||||
>>> mu = moments_central(image, centroid)
|
||||
>>> moments_normalized(mu)
|
||||
array([[ nan, nan, 0.078125 , 0. ],
|
||||
[ nan, 0. , 0. , 0. ],
|
||||
[0.078125 , 0. , 0.00610352, 0. ],
|
||||
[0. , 0. , 0. , 0. ]])
|
||||
"""
|
||||
if np.any(np.array(mu.shape) <= order):
|
||||
raise ValueError("Shape of image moments must be >= `order`")
|
||||
nu = np.zeros_like(mu)
|
||||
mu0 = mu.ravel()[0]
|
||||
for powers in itertools.product(range(order + 1), repeat=mu.ndim):
|
||||
if sum(powers) < 2:
|
||||
nu[powers] = np.nan
|
||||
else:
|
||||
nu[powers] = mu[powers] / (mu0 ** (sum(powers) / nu.ndim + 1))
|
||||
return nu
|
||||
|
||||
|
||||
def moments_hu(nu):
|
||||
"""Calculate Hu's set of image moments (2D-only).
|
||||
|
||||
Note that this set of moments is proofed to be translation, scale and
|
||||
rotation invariant.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
nu : (M, M) array
|
||||
Normalized central image moments, where M must be >= 4.
|
||||
|
||||
Returns
|
||||
-------
|
||||
nu : (7,) array
|
||||
Hu's set of image moments.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] M. K. Hu, "Visual Pattern Recognition by Moment Invariants",
|
||||
IRE Trans. Info. Theory, vol. IT-8, pp. 179-187, 1962
|
||||
.. [2] Wilhelm Burger, Mark Burge. Principles of Digital Image Processing:
|
||||
Core Algorithms. Springer-Verlag, London, 2009.
|
||||
.. [3] B. Jähne. Digital Image Processing. Springer-Verlag,
|
||||
Berlin-Heidelberg, 6. edition, 2005.
|
||||
.. [4] T. H. Reiss. Recognizing Planar Objects Using Invariant Image
|
||||
Features, from Lecture notes in computer science, p. 676. Springer,
|
||||
Berlin, 1993.
|
||||
.. [5] https://en.wikipedia.org/wiki/Image_moment
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> image = np.zeros((20, 20), dtype=np.double)
|
||||
>>> image[13:17, 13:17] = 0.5
|
||||
>>> image[10:12, 10:12] = 1
|
||||
>>> mu = moments_central(image)
|
||||
>>> nu = moments_normalized(mu)
|
||||
>>> moments_hu(nu)
|
||||
array([7.45370370e-01, 3.51165981e-01, 1.04049179e-01, 4.06442107e-02,
|
||||
2.64312299e-03, 2.40854582e-02, 4.33680869e-19])
|
||||
"""
|
||||
return _moments_cy.moments_hu(nu.astype(np.double))
|
||||
|
||||
|
||||
def centroid(image):
|
||||
"""Return the (weighted) centroid of an image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : array
|
||||
The input image.
|
||||
|
||||
Returns
|
||||
-------
|
||||
center : tuple of float, length ``image.ndim``
|
||||
The centroid of the (nonzero) pixels in ``image``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> image = np.zeros((20, 20), dtype=np.double)
|
||||
>>> image[13:17, 13:17] = 0.5
|
||||
>>> image[10:12, 10:12] = 1
|
||||
>>> centroid(image)
|
||||
array([13.16666667, 13.16666667])
|
||||
"""
|
||||
M = moments_central(image, center=(0,) * image.ndim, order=1)
|
||||
center = (M[tuple(np.eye(image.ndim, dtype=int))] # array of weighted sums
|
||||
# for each axis
|
||||
/ M[(0,) * image.ndim]) # weighted sum of all points
|
||||
return center
|
||||
|
||||
|
||||
def inertia_tensor(image, mu=None):
|
||||
"""Compute the inertia tensor of the input image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : array
|
||||
The input image.
|
||||
mu : array, optional
|
||||
The pre-computed central moments of ``image``. The inertia tensor
|
||||
computation requires the central moments of the image. If an
|
||||
application requires both the central moments and the inertia tensor
|
||||
(for example, `skimage.measure.regionprops`), then it is more
|
||||
efficient to pre-compute them and pass them to the inertia tensor
|
||||
call.
|
||||
|
||||
Returns
|
||||
-------
|
||||
T : array, shape ``(image.ndim, image.ndim)``
|
||||
The inertia tensor of the input image. :math:`T_{i, j}` contains
|
||||
the covariance of image intensity along axes :math:`i` and :math:`j`.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] https://en.wikipedia.org/wiki/Moment_of_inertia#Inertia_tensor
|
||||
.. [2] Bernd Jähne. Spatio-Temporal Image Processing: Theory and
|
||||
Scientific Applications. (Chapter 8: Tensor Methods) Springer, 1993.
|
||||
"""
|
||||
if mu is None:
|
||||
mu = moments_central(image, order=2) # don't need higher-order moments
|
||||
mu0 = mu[(0,) * image.ndim]
|
||||
result = np.zeros((image.ndim, image.ndim))
|
||||
|
||||
# nD expression to get coordinates ([2, 0], [0, 2]) (2D),
|
||||
# ([2, 0, 0], [0, 2, 0], [0, 0, 2]) (3D), etc.
|
||||
corners2 = tuple(2 * np.eye(image.ndim, dtype=int))
|
||||
d = np.diag(result)
|
||||
d.flags.writeable = True
|
||||
# See https://ocw.mit.edu/courses/aeronautics-and-astronautics/
|
||||
# 16-07-dynamics-fall-2009/lecture-notes/MIT16_07F09_Lec26.pdf
|
||||
# Iii is the sum of second-order moments of every axis *except* i, not the
|
||||
# second order moment of axis i.
|
||||
# See also https://github.com/scikit-image/scikit-image/issues/3229
|
||||
d[:] = (np.sum(mu[corners2]) - mu[corners2]) / mu0
|
||||
|
||||
for dims in itertools.combinations(range(image.ndim), 2):
|
||||
mu_index = np.zeros(image.ndim, dtype=int)
|
||||
mu_index[list(dims)] = 1
|
||||
result[dims] = -mu[tuple(mu_index)] / mu0
|
||||
result.T[dims] = -mu[tuple(mu_index)] / mu0
|
||||
return result
|
||||
|
||||
|
||||
def inertia_tensor_eigvals(image, mu=None, T=None):
|
||||
"""Compute the eigenvalues of the inertia tensor of the image.
|
||||
|
||||
The inertia tensor measures covariance of the image intensity along
|
||||
the image axes. (See `inertia_tensor`.) The relative magnitude of the
|
||||
eigenvalues of the tensor is thus a measure of the elongation of a
|
||||
(bright) object in the image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : array
|
||||
The input image.
|
||||
mu : array, optional
|
||||
The pre-computed central moments of ``image``.
|
||||
T : array, shape ``(image.ndim, image.ndim)``
|
||||
The pre-computed inertia tensor. If ``T`` is given, ``mu`` and
|
||||
``image`` are ignored.
|
||||
|
||||
Returns
|
||||
-------
|
||||
eigvals : list of float, length ``image.ndim``
|
||||
The eigenvalues of the inertia tensor of ``image``, in descending
|
||||
order.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Computing the eigenvalues requires the inertia tensor of the input image.
|
||||
This is much faster if the central moments (``mu``) are provided, or,
|
||||
alternatively, one can provide the inertia tensor (``T``) directly.
|
||||
"""
|
||||
if T is None:
|
||||
T = inertia_tensor(image, mu)
|
||||
eigvals = np.linalg.eigvalsh(T)
|
||||
# Floating point precision problems could make a positive
|
||||
# semidefinite matrix have an eigenvalue that is very slightly
|
||||
# negative. This can cause problems down the line, so set values
|
||||
# very near zero to zero.
|
||||
eigvals = np.clip(eigvals, 0, None, out=eigvals)
|
||||
return sorted(eigvals, reverse=True)
|
Binary file not shown.
BIN
venv/Lib/site-packages/skimage/measure/_pnpoly.cp36-win32.pyd
Normal file
BIN
venv/Lib/site-packages/skimage/measure/_pnpoly.cp36-win32.pyd
Normal file
Binary file not shown.
168
venv/Lib/site-packages/skimage/measure/_polygon.py
Normal file
168
venv/Lib/site-packages/skimage/measure/_polygon.py
Normal file
|
@ -0,0 +1,168 @@
|
|||
import numpy as np
|
||||
from scipy import signal
|
||||
|
||||
|
||||
def approximate_polygon(coords, tolerance):
|
||||
"""Approximate a polygonal chain with the specified tolerance.
|
||||
|
||||
It is based on the Douglas-Peucker algorithm.
|
||||
|
||||
Note that the approximated polygon is always within the convex hull of the
|
||||
original polygon.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
coords : (N, 2) array
|
||||
Coordinate array.
|
||||
tolerance : float
|
||||
Maximum distance from original points of polygon to approximated
|
||||
polygonal chain. If tolerance is 0, the original coordinate array
|
||||
is returned.
|
||||
|
||||
Returns
|
||||
-------
|
||||
coords : (M, 2) array
|
||||
Approximated polygonal chain where M <= N.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
|
||||
"""
|
||||
if tolerance <= 0:
|
||||
return coords
|
||||
|
||||
chain = np.zeros(coords.shape[0], 'bool')
|
||||
# pre-allocate distance array for all points
|
||||
dists = np.zeros(coords.shape[0])
|
||||
chain[0] = True
|
||||
chain[-1] = True
|
||||
pos_stack = [(0, chain.shape[0] - 1)]
|
||||
end_of_chain = False
|
||||
|
||||
while not end_of_chain:
|
||||
start, end = pos_stack.pop()
|
||||
# determine properties of current line segment
|
||||
r0, c0 = coords[start, :]
|
||||
r1, c1 = coords[end, :]
|
||||
dr = r1 - r0
|
||||
dc = c1 - c0
|
||||
segment_angle = - np.arctan2(dr, dc)
|
||||
segment_dist = c0 * np.sin(segment_angle) + r0 * np.cos(segment_angle)
|
||||
|
||||
# select points in-between line segment
|
||||
segment_coords = coords[start + 1:end, :]
|
||||
segment_dists = dists[start + 1:end]
|
||||
|
||||
# check whether to take perpendicular or euclidean distance with
|
||||
# inner product of vectors
|
||||
|
||||
# vectors from points -> start and end
|
||||
dr0 = segment_coords[:, 0] - r0
|
||||
dc0 = segment_coords[:, 1] - c0
|
||||
dr1 = segment_coords[:, 0] - r1
|
||||
dc1 = segment_coords[:, 1] - c1
|
||||
# vectors points -> start and end projected on start -> end vector
|
||||
projected_lengths0 = dr0 * dr + dc0 * dc
|
||||
projected_lengths1 = - dr1 * dr - dc1 * dc
|
||||
perp = np.logical_and(projected_lengths0 > 0,
|
||||
projected_lengths1 > 0)
|
||||
eucl = np.logical_not(perp)
|
||||
segment_dists[perp] = np.abs(
|
||||
segment_coords[perp, 0] * np.cos(segment_angle)
|
||||
+ segment_coords[perp, 1] * np.sin(segment_angle)
|
||||
- segment_dist
|
||||
)
|
||||
segment_dists[eucl] = np.minimum(
|
||||
# distance to start point
|
||||
np.sqrt(dc0[eucl] ** 2 + dr0[eucl] ** 2),
|
||||
# distance to end point
|
||||
np.sqrt(dc1[eucl] ** 2 + dr1[eucl] ** 2)
|
||||
)
|
||||
|
||||
if np.any(segment_dists > tolerance):
|
||||
# select point with maximum distance to line
|
||||
new_end = start + np.argmax(segment_dists) + 1
|
||||
pos_stack.append((new_end, end))
|
||||
pos_stack.append((start, new_end))
|
||||
chain[new_end] = True
|
||||
|
||||
if len(pos_stack) == 0:
|
||||
end_of_chain = True
|
||||
|
||||
return coords[chain, :]
|
||||
|
||||
|
||||
# B-Spline subdivision
|
||||
_SUBDIVISION_MASKS = {
|
||||
# degree: (mask_even, mask_odd)
|
||||
# extracted from (degree + 2)th row of Pascal's triangle
|
||||
1: ([1, 1], [1, 1]),
|
||||
2: ([3, 1], [1, 3]),
|
||||
3: ([1, 6, 1], [0, 4, 4]),
|
||||
4: ([5, 10, 1], [1, 10, 5]),
|
||||
5: ([1, 15, 15, 1], [0, 6, 20, 6]),
|
||||
6: ([7, 35, 21, 1], [1, 21, 35, 7]),
|
||||
7: ([1, 28, 70, 28, 1], [0, 8, 56, 56, 8]),
|
||||
}
|
||||
|
||||
|
||||
def subdivide_polygon(coords, degree=2, preserve_ends=False):
|
||||
"""Subdivision of polygonal curves using B-Splines.
|
||||
|
||||
Note that the resulting curve is always within the convex hull of the
|
||||
original polygon. Circular polygons stay closed after subdivision.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
coords : (N, 2) array
|
||||
Coordinate array.
|
||||
degree : {1, 2, 3, 4, 5, 6, 7}, optional
|
||||
Degree of B-Spline. Default is 2.
|
||||
preserve_ends : bool, optional
|
||||
Preserve first and last coordinate of non-circular polygon. Default is
|
||||
False.
|
||||
|
||||
Returns
|
||||
-------
|
||||
coords : (M, 2) array
|
||||
Subdivided coordinate array.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] http://mrl.nyu.edu/publications/subdiv-course2000/coursenotes00.pdf
|
||||
"""
|
||||
if degree not in _SUBDIVISION_MASKS:
|
||||
raise ValueError("Invalid B-Spline degree. Only degree 1 - 7 is "
|
||||
"supported.")
|
||||
|
||||
circular = np.all(coords[0, :] == coords[-1, :])
|
||||
|
||||
method = 'valid'
|
||||
if circular:
|
||||
# remove last coordinate because of wrapping
|
||||
coords = coords[:-1, :]
|
||||
# circular convolution by wrapping boundaries
|
||||
method = 'same'
|
||||
|
||||
mask_even, mask_odd = _SUBDIVISION_MASKS[degree]
|
||||
# divide by total weight
|
||||
mask_even = np.array(mask_even, np.float) / (2 ** degree)
|
||||
mask_odd = np.array(mask_odd, np.float) / (2 ** degree)
|
||||
|
||||
even = signal.convolve2d(coords.T, np.atleast_2d(mask_even), mode=method,
|
||||
boundary='wrap')
|
||||
odd = signal.convolve2d(coords.T, np.atleast_2d(mask_odd), mode=method,
|
||||
boundary='wrap')
|
||||
|
||||
out = np.zeros((even.shape[1] + odd.shape[1], 2))
|
||||
out[1::2] = even.T
|
||||
out[::2] = odd.T
|
||||
|
||||
if circular:
|
||||
# close polygon
|
||||
out = np.vstack([out, out[0, :]])
|
||||
|
||||
if preserve_ends and not circular:
|
||||
out = np.vstack([coords[0, :], out, coords[-1, :]])
|
||||
|
||||
return out
|
987
venv/Lib/site-packages/skimage/measure/_regionprops.py
Normal file
987
venv/Lib/site-packages/skimage/measure/_regionprops.py
Normal file
|
@ -0,0 +1,987 @@
|
|||
from warnings import warn
|
||||
from math import sqrt, atan2, pi as PI
|
||||
import numpy as np
|
||||
from scipy import ndimage as ndi
|
||||
|
||||
from ._label import label
|
||||
from . import _moments
|
||||
|
||||
from functools import wraps
|
||||
|
||||
|
||||
__all__ = ['regionprops', 'perimeter']
|
||||
|
||||
|
||||
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)
|
||||
STREL_26_3D = np.ones((3, 3, 3), dtype=np.uint8)
|
||||
PROPS = {
|
||||
'Area': 'area',
|
||||
'BoundingBox': 'bbox',
|
||||
'BoundingBoxArea': 'bbox_area',
|
||||
'CentralMoments': 'moments_central',
|
||||
'Centroid': 'centroid',
|
||||
'ConvexArea': 'convex_area',
|
||||
# 'ConvexHull',
|
||||
'ConvexImage': 'convex_image',
|
||||
'Coordinates': 'coords',
|
||||
'Eccentricity': 'eccentricity',
|
||||
'EquivDiameter': 'equivalent_diameter',
|
||||
'EulerNumber': 'euler_number',
|
||||
'Extent': 'extent',
|
||||
# 'Extrema',
|
||||
'FilledArea': 'filled_area',
|
||||
'FilledImage': 'filled_image',
|
||||
'HuMoments': 'moments_hu',
|
||||
'Image': 'image',
|
||||
'InertiaTensor': 'inertia_tensor',
|
||||
'InertiaTensorEigvals': 'inertia_tensor_eigvals',
|
||||
'IntensityImage': 'intensity_image',
|
||||
'Label': 'label',
|
||||
'LocalCentroid': 'local_centroid',
|
||||
'MajorAxisLength': 'major_axis_length',
|
||||
'MaxIntensity': 'max_intensity',
|
||||
'MeanIntensity': 'mean_intensity',
|
||||
'MinIntensity': 'min_intensity',
|
||||
'MinorAxisLength': 'minor_axis_length',
|
||||
'Moments': 'moments',
|
||||
'NormalizedMoments': 'moments_normalized',
|
||||
'Orientation': 'orientation',
|
||||
'Perimeter': 'perimeter',
|
||||
# 'PixelIdxList',
|
||||
# 'PixelList',
|
||||
'Slice': 'slice',
|
||||
'Solidity': 'solidity',
|
||||
# 'SubarrayIdx'
|
||||
'WeightedCentralMoments': 'weighted_moments_central',
|
||||
'WeightedCentroid': 'weighted_centroid',
|
||||
'WeightedHuMoments': 'weighted_moments_hu',
|
||||
'WeightedLocalCentroid': 'weighted_local_centroid',
|
||||
'WeightedMoments': 'weighted_moments',
|
||||
'WeightedNormalizedMoments': 'weighted_moments_normalized'
|
||||
}
|
||||
|
||||
OBJECT_COLUMNS = {
|
||||
'image', 'coords', 'convex_image', 'slice',
|
||||
'filled_image', 'intensity_image'
|
||||
}
|
||||
|
||||
COL_DTYPES = {
|
||||
'area': int,
|
||||
'bbox': int,
|
||||
'bbox_area': int,
|
||||
'moments_central': float,
|
||||
'centroid': float,
|
||||
'convex_area': int,
|
||||
'convex_image': object,
|
||||
'coords': object,
|
||||
'eccentricity': float,
|
||||
'equivalent_diameter': float,
|
||||
'euler_number': int,
|
||||
'extent': float,
|
||||
'filled_area': int,
|
||||
'filled_image': object,
|
||||
'moments_hu': float,
|
||||
'image': object,
|
||||
'inertia_tensor': float,
|
||||
'inertia_tensor_eigvals': float,
|
||||
'intensity_image': object,
|
||||
'label': int,
|
||||
'local_centroid': float,
|
||||
'major_axis_length': float,
|
||||
'max_intensity': int,
|
||||
'mean_intensity': float,
|
||||
'min_intensity': int,
|
||||
'minor_axis_length': float,
|
||||
'moments': float,
|
||||
'moments_normalized': float,
|
||||
'orientation': float,
|
||||
'perimeter': float,
|
||||
'slice': object,
|
||||
'solidity': float,
|
||||
'weighted_moments_central': float,
|
||||
'weighted_centroid': float,
|
||||
'weighted_moments_hu': float,
|
||||
'weighted_local_centroid': float,
|
||||
'weighted_moments': float,
|
||||
'weighted_moments_normalized': float
|
||||
}
|
||||
|
||||
PROP_VALS = set(PROPS.values())
|
||||
|
||||
|
||||
def _cached(f):
|
||||
@wraps(f)
|
||||
def wrapper(obj):
|
||||
cache = obj._cache
|
||||
prop = f.__name__
|
||||
|
||||
if not ((prop in cache) and obj._cache_active):
|
||||
cache[prop] = f(obj)
|
||||
|
||||
return cache[prop]
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def only2d(method):
|
||||
@wraps(method)
|
||||
def func2d(self, *args, **kwargs):
|
||||
if self._ndim > 2:
|
||||
raise NotImplementedError('Property %s is not implemented for '
|
||||
'3D images' % method.__name__)
|
||||
return method(self, *args, **kwargs)
|
||||
return func2d
|
||||
|
||||
|
||||
class RegionProperties:
|
||||
"""Please refer to `skimage.measure.regionprops` for more information
|
||||
on the available region properties.
|
||||
"""
|
||||
|
||||
def __init__(self, slice, label, label_image, intensity_image,
|
||||
cache_active):
|
||||
|
||||
if intensity_image is not None:
|
||||
if not intensity_image.shape == label_image.shape:
|
||||
raise ValueError('Label and intensity image must have the'
|
||||
' same shape.')
|
||||
|
||||
self.label = label
|
||||
|
||||
self._slice = slice
|
||||
self.slice = slice
|
||||
self._label_image = label_image
|
||||
self._intensity_image = intensity_image
|
||||
|
||||
self._cache_active = cache_active
|
||||
self._cache = {}
|
||||
self._ndim = label_image.ndim
|
||||
|
||||
@property
|
||||
@_cached
|
||||
def area(self):
|
||||
return np.sum(self.image)
|
||||
|
||||
@property
|
||||
def bbox(self):
|
||||
"""
|
||||
Returns
|
||||
-------
|
||||
A tuple of the bounding box's start coordinates for each dimension,
|
||||
followed by the end coordinates for each dimension
|
||||
"""
|
||||
return tuple([self.slice[i].start for i in range(self._ndim)] +
|
||||
[self.slice[i].stop for i in range(self._ndim)])
|
||||
|
||||
@property
|
||||
def bbox_area(self):
|
||||
return self.image.size
|
||||
|
||||
@property
|
||||
def centroid(self):
|
||||
return tuple(self.coords.mean(axis=0))
|
||||
|
||||
@property
|
||||
@_cached
|
||||
def convex_area(self):
|
||||
return np.sum(self.convex_image)
|
||||
|
||||
@property
|
||||
@_cached
|
||||
def convex_image(self):
|
||||
from ..morphology.convex_hull import convex_hull_image
|
||||
return convex_hull_image(self.image)
|
||||
|
||||
@property
|
||||
def coords(self):
|
||||
indices = np.nonzero(self.image)
|
||||
return np.vstack([indices[i] + self.slice[i].start
|
||||
for i in range(self._ndim)]).T
|
||||
|
||||
@property
|
||||
@only2d
|
||||
def eccentricity(self):
|
||||
l1, l2 = self.inertia_tensor_eigvals
|
||||
if l1 == 0:
|
||||
return 0
|
||||
return sqrt(1 - l2 / l1)
|
||||
|
||||
@property
|
||||
def equivalent_diameter(self):
|
||||
if self._ndim == 2:
|
||||
return sqrt(4 * self.area / PI)
|
||||
elif self._ndim == 3:
|
||||
return (6 * self.area / PI) ** (1. / 3)
|
||||
|
||||
@property
|
||||
def euler_number(self):
|
||||
euler_array = self.filled_image != self.image
|
||||
_, num = label(euler_array, connectivity=self._ndim, return_num=True,
|
||||
background=0)
|
||||
return -num + 1
|
||||
|
||||
@property
|
||||
def extent(self):
|
||||
return self.area / self.image.size
|
||||
|
||||
@property
|
||||
def filled_area(self):
|
||||
return np.sum(self.filled_image)
|
||||
|
||||
@property
|
||||
@_cached
|
||||
def filled_image(self):
|
||||
structure = np.ones((3,) * self._ndim)
|
||||
return ndi.binary_fill_holes(self.image, structure)
|
||||
|
||||
@property
|
||||
@_cached
|
||||
def image(self):
|
||||
return self._label_image[self.slice] == self.label
|
||||
|
||||
@property
|
||||
@_cached
|
||||
def inertia_tensor(self):
|
||||
mu = self.moments_central
|
||||
return _moments.inertia_tensor(self.image, mu)
|
||||
|
||||
@property
|
||||
@_cached
|
||||
def inertia_tensor_eigvals(self):
|
||||
return _moments.inertia_tensor_eigvals(self.image,
|
||||
T=self.inertia_tensor)
|
||||
|
||||
@property
|
||||
@_cached
|
||||
def intensity_image(self):
|
||||
if self._intensity_image is None:
|
||||
raise AttributeError('No intensity image specified.')
|
||||
return self._intensity_image[self.slice] * self.image
|
||||
|
||||
def _intensity_image_double(self):
|
||||
return self.intensity_image.astype(np.double)
|
||||
|
||||
@property
|
||||
def local_centroid(self):
|
||||
M = self.moments
|
||||
return tuple(M[tuple(np.eye(self._ndim, dtype=int))] /
|
||||
M[(0,) * self._ndim])
|
||||
|
||||
@property
|
||||
def max_intensity(self):
|
||||
return np.max(self.intensity_image[self.image])
|
||||
|
||||
@property
|
||||
def mean_intensity(self):
|
||||
return np.mean(self.intensity_image[self.image])
|
||||
|
||||
@property
|
||||
def min_intensity(self):
|
||||
return np.min(self.intensity_image[self.image])
|
||||
|
||||
@property
|
||||
def major_axis_length(self):
|
||||
l1 = self.inertia_tensor_eigvals[0]
|
||||
return 4 * sqrt(l1)
|
||||
|
||||
@property
|
||||
def minor_axis_length(self):
|
||||
l2 = self.inertia_tensor_eigvals[-1]
|
||||
return 4 * sqrt(l2)
|
||||
|
||||
@property
|
||||
@_cached
|
||||
def moments(self):
|
||||
M = _moments.moments(self.image.astype(np.uint8), 3)
|
||||
return M
|
||||
|
||||
@property
|
||||
@_cached
|
||||
def moments_central(self):
|
||||
mu = _moments.moments_central(self.image.astype(np.uint8),
|
||||
self.local_centroid, order=3)
|
||||
return mu
|
||||
|
||||
@property
|
||||
@only2d
|
||||
def moments_hu(self):
|
||||
return _moments.moments_hu(self.moments_normalized)
|
||||
|
||||
@property
|
||||
@_cached
|
||||
def moments_normalized(self):
|
||||
return _moments.moments_normalized(self.moments_central, 3)
|
||||
|
||||
@property
|
||||
@only2d
|
||||
def orientation(self):
|
||||
a, b, b, c = self.inertia_tensor.flat
|
||||
if a - c == 0:
|
||||
if b < 0:
|
||||
return -PI / 4.
|
||||
else:
|
||||
return PI / 4.
|
||||
else:
|
||||
return 0.5 * atan2(-2 * b, c - a)
|
||||
|
||||
@property
|
||||
@only2d
|
||||
def perimeter(self):
|
||||
return perimeter(self.image, 4)
|
||||
|
||||
@property
|
||||
def solidity(self):
|
||||
return self.area / self.convex_area
|
||||
|
||||
@property
|
||||
def weighted_centroid(self):
|
||||
ctr = self.weighted_local_centroid
|
||||
return tuple(idx + slc.start
|
||||
for idx, slc in zip(ctr, self.slice))
|
||||
|
||||
@property
|
||||
def weighted_local_centroid(self):
|
||||
M = self.weighted_moments
|
||||
return (M[tuple(np.eye(self._ndim, dtype=int))] /
|
||||
M[(0,) * self._ndim])
|
||||
|
||||
@property
|
||||
@_cached
|
||||
def weighted_moments(self):
|
||||
return _moments.moments(self._intensity_image_double(), 3)
|
||||
|
||||
@property
|
||||
@_cached
|
||||
def weighted_moments_central(self):
|
||||
ctr = self.weighted_local_centroid
|
||||
return _moments.moments_central(self._intensity_image_double(),
|
||||
center=ctr, order=3)
|
||||
|
||||
@property
|
||||
@only2d
|
||||
def weighted_moments_hu(self):
|
||||
return _moments.moments_hu(self.weighted_moments_normalized)
|
||||
|
||||
@property
|
||||
@_cached
|
||||
def weighted_moments_normalized(self):
|
||||
return _moments.moments_normalized(self.weighted_moments_central, 3)
|
||||
|
||||
def __iter__(self):
|
||||
props = PROP_VALS
|
||||
|
||||
if self._intensity_image is None:
|
||||
unavailable_props = ('intensity_image',
|
||||
'max_intensity',
|
||||
'mean_intensity',
|
||||
'min_intensity',
|
||||
'weighted_moments',
|
||||
'weighted_moments_central',
|
||||
'weighted_centroid',
|
||||
'weighted_local_centroid',
|
||||
'weighted_moments_hu',
|
||||
'weighted_moments_normalized')
|
||||
|
||||
props = props.difference(unavailable_props)
|
||||
|
||||
return iter(sorted(props))
|
||||
|
||||
def __getitem__(self, key):
|
||||
value = getattr(self, key, None)
|
||||
if value is not None:
|
||||
return value
|
||||
else: # backwards compatibility
|
||||
return getattr(self, PROPS[key])
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, RegionProperties):
|
||||
return False
|
||||
|
||||
for key in PROP_VALS:
|
||||
try:
|
||||
# so that NaNs are equal
|
||||
np.testing.assert_equal(getattr(self, key, None),
|
||||
getattr(other, key, None))
|
||||
except AssertionError:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
# For compatibility with code written prior to 0.16
|
||||
_RegionProperties = RegionProperties
|
||||
|
||||
|
||||
def _props_to_dict(regions, properties=('label', 'bbox'), separator='-'):
|
||||
"""Convert image region properties list into a column dictionary.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
regions : (N,) list
|
||||
List of RegionProperties objects as returned by :func:`regionprops`.
|
||||
properties : tuple or list of str, optional
|
||||
Properties that will be included in the resulting dictionary
|
||||
For a list of available properties, please see :func:`regionprops`.
|
||||
Users should remember to add "label" to keep track of region
|
||||
identities.
|
||||
separator : str, optional
|
||||
For non-scalar properties not listed in OBJECT_COLUMNS, each element
|
||||
will appear in its own column, with the index of that element separated
|
||||
from the property name by this separator. For example, the inertia
|
||||
tensor of a 2D region will appear in four columns:
|
||||
``inertia_tensor-0-0``, ``inertia_tensor-0-1``, ``inertia_tensor-1-0``,
|
||||
and ``inertia_tensor-1-1`` (where the separator is ``-``).
|
||||
|
||||
Object columns are those that cannot be split in this way because the
|
||||
number of columns would change depending on the object. For example,
|
||||
``image`` and ``coords``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out_dict : dict
|
||||
Dictionary mapping property names to an array of values of that
|
||||
property, one value per region. This dictionary can be used as input to
|
||||
pandas ``DataFrame`` to map property names to columns in the frame and
|
||||
regions to rows.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Each column contains either a scalar property, an object property, or an
|
||||
element in a multidimensional array.
|
||||
|
||||
Properties with scalar values for each region, such as "eccentricity", will
|
||||
appear as a float or int array with that property name as key.
|
||||
|
||||
Multidimensional properties *of fixed size* for a given image dimension,
|
||||
such as "centroid" (every centroid will have three elements in a 3D image,
|
||||
no matter the region size), will be split into that many columns, with the
|
||||
name {property_name}{separator}{element_num} (for 1D properties),
|
||||
{property_name}{separator}{elem_num0}{separator}{elem_num1} (for 2D
|
||||
properties), and so on.
|
||||
|
||||
For multidimensional properties that don't have a fixed size, such as
|
||||
"image" (the image of a region varies in size depending on the region
|
||||
size), an object array will be used, with the corresponding property name
|
||||
as the key.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage import data, util, measure
|
||||
>>> image = data.coins()
|
||||
>>> label_image = measure.label(image > 110, connectivity=image.ndim)
|
||||
>>> proplist = regionprops(label_image, image)
|
||||
>>> props = _props_to_dict(proplist, properties=['label', 'inertia_tensor',
|
||||
... 'inertia_tensor_eigvals'])
|
||||
>>> props # doctest: +ELLIPSIS +SKIP
|
||||
{'label': array([ 1, 2, ...]), ...
|
||||
'inertia_tensor-0-0': array([ 4.012...e+03, 8.51..., ...]), ...
|
||||
...,
|
||||
'inertia_tensor_eigvals-1': array([ 2.67...e+02, 2.83..., ...])}
|
||||
|
||||
The resulting dictionary can be directly passed to pandas, if installed, to
|
||||
obtain a clean DataFrame:
|
||||
|
||||
>>> import pandas as pd # doctest: +SKIP
|
||||
>>> data = pd.DataFrame(props) # doctest: +SKIP
|
||||
>>> data.head() # doctest: +SKIP
|
||||
label inertia_tensor-0-0 ... inertia_tensor_eigvals-1
|
||||
0 1 4012.909888 ... 267.065503
|
||||
1 2 8.514739 ... 2.834806
|
||||
2 3 0.666667 ... 0.000000
|
||||
3 4 0.000000 ... 0.000000
|
||||
4 5 0.222222 ... 0.111111
|
||||
|
||||
"""
|
||||
|
||||
out = {}
|
||||
n = len(regions)
|
||||
for prop in properties:
|
||||
dtype = COL_DTYPES[prop]
|
||||
column_buffer = np.zeros(n, dtype=dtype)
|
||||
r = regions[0][prop]
|
||||
|
||||
# scalars and objects are dedicated one column per prop
|
||||
# array properties are raveled into multiple columns
|
||||
# for more info, refer to notes 1
|
||||
if np.isscalar(r) or prop in OBJECT_COLUMNS:
|
||||
for i in range(n):
|
||||
column_buffer[i] = regions[i][prop]
|
||||
out[prop] = np.copy(column_buffer)
|
||||
else:
|
||||
if isinstance(r, np.ndarray):
|
||||
shape = r.shape
|
||||
else:
|
||||
shape = (len(r),)
|
||||
|
||||
for ind in np.ndindex(shape):
|
||||
for k in range(n):
|
||||
loc = ind if len(ind) > 1 else ind[0]
|
||||
column_buffer[k] = regions[k][prop][loc]
|
||||
modified_prop = separator.join(map(str, (prop,) + ind))
|
||||
out[modified_prop] = np.copy(column_buffer)
|
||||
return out
|
||||
|
||||
|
||||
def regionprops_table(label_image, intensity_image=None,
|
||||
properties=('label', 'bbox'),
|
||||
*,
|
||||
cache=True, separator='-'):
|
||||
"""Compute image properties and return them as a pandas-compatible table.
|
||||
|
||||
The table is a dictionary mapping column names to value arrays. See Notes
|
||||
section below for details.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
label_image : (N, M) ndarray
|
||||
Labeled input image. Labels with value 0 are ignored.
|
||||
intensity_image : (N, M) ndarray, optional
|
||||
Intensity (i.e., input) image with same size as labeled image.
|
||||
Default is None.
|
||||
properties : tuple or list of str, optional
|
||||
Properties that will be included in the resulting dictionary
|
||||
For a list of available properties, please see :func:`regionprops`.
|
||||
Users should remember to add "label" to keep track of region
|
||||
identities.
|
||||
cache : bool, optional
|
||||
Determine whether to cache calculated properties. The computation is
|
||||
much faster for cached properties, whereas the memory consumption
|
||||
increases.
|
||||
separator : str, optional
|
||||
For non-scalar properties not listed in OBJECT_COLUMNS, each element
|
||||
will appear in its own column, with the index of that element separated
|
||||
from the property name by this separator. For example, the inertia
|
||||
tensor of a 2D region will appear in four columns:
|
||||
``inertia_tensor-0-0``, ``inertia_tensor-0-1``, ``inertia_tensor-1-0``,
|
||||
and ``inertia_tensor-1-1`` (where the separator is ``-``).
|
||||
|
||||
Object columns are those that cannot be split in this way because the
|
||||
number of columns would change depending on the object. For example,
|
||||
``image`` and ``coords``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out_dict : dict
|
||||
Dictionary mapping property names to an array of values of that
|
||||
property, one value per region. This dictionary can be used as input to
|
||||
pandas ``DataFrame`` to map property names to columns in the frame and
|
||||
regions to rows. If the image has no regions,
|
||||
the arrays will have length 0, but the correct type.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Each column contains either a scalar property, an object property, or an
|
||||
element in a multidimensional array.
|
||||
|
||||
Properties with scalar values for each region, such as "eccentricity", will
|
||||
appear as a float or int array with that property name as key.
|
||||
|
||||
Multidimensional properties *of fixed size* for a given image dimension,
|
||||
such as "centroid" (every centroid will have three elements in a 3D image,
|
||||
no matter the region size), will be split into that many columns, with the
|
||||
name {property_name}{separator}{element_num} (for 1D properties),
|
||||
{property_name}{separator}{elem_num0}{separator}{elem_num1} (for 2D
|
||||
properties), and so on.
|
||||
|
||||
For multidimensional properties that don't have a fixed size, such as
|
||||
"image" (the image of a region varies in size depending on the region
|
||||
size), an object array will be used, with the corresponding property name
|
||||
as the key.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage import data, util, measure
|
||||
>>> image = data.coins()
|
||||
>>> label_image = measure.label(image > 110, connectivity=image.ndim)
|
||||
>>> props = regionprops_table(label_image, image,
|
||||
... properties=['label', 'inertia_tensor',
|
||||
... 'inertia_tensor_eigvals'])
|
||||
>>> props # doctest: +ELLIPSIS +SKIP
|
||||
{'label': array([ 1, 2, ...]), ...
|
||||
'inertia_tensor-0-0': array([ 4.012...e+03, 8.51..., ...]), ...
|
||||
...,
|
||||
'inertia_tensor_eigvals-1': array([ 2.67...e+02, 2.83..., ...])}
|
||||
|
||||
The resulting dictionary can be directly passed to pandas, if installed, to
|
||||
obtain a clean DataFrame:
|
||||
|
||||
>>> import pandas as pd # doctest: +SKIP
|
||||
>>> data = pd.DataFrame(props) # doctest: +SKIP
|
||||
>>> data.head() # doctest: +SKIP
|
||||
label inertia_tensor-0-0 ... inertia_tensor_eigvals-1
|
||||
0 1 4012.909888 ... 267.065503
|
||||
1 2 8.514739 ... 2.834806
|
||||
2 3 0.666667 ... 0.000000
|
||||
3 4 0.000000 ... 0.000000
|
||||
4 5 0.222222 ... 0.111111
|
||||
|
||||
[5 rows x 7 columns]
|
||||
|
||||
"""
|
||||
regions = regionprops(label_image, intensity_image=intensity_image,
|
||||
cache=cache)
|
||||
|
||||
if len(regions) == 0:
|
||||
label_image = np.zeros((3,) * label_image.ndim, dtype=int)
|
||||
label_image[(1,) * label_image.ndim] = 1
|
||||
if intensity_image is not None:
|
||||
intensity_image = np.zeros(label_image.shape,
|
||||
dtype=intensity_image.dtype)
|
||||
regions = regionprops(label_image, intensity_image=intensity_image,
|
||||
cache=cache)
|
||||
|
||||
out_d = _props_to_dict(regions, properties=properties,
|
||||
separator=separator)
|
||||
return {k: v[:0] for k, v in out_d.items()}
|
||||
|
||||
return _props_to_dict(regions, properties=properties, separator=separator)
|
||||
|
||||
|
||||
def regionprops(label_image, intensity_image=None, cache=True,
|
||||
coordinates=None):
|
||||
r"""Measure properties of labeled image regions.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
label_image : (N, M) ndarray
|
||||
Labeled input image. Labels with value 0 are ignored.
|
||||
|
||||
.. versionchanged:: 0.14.1
|
||||
Previously, ``label_image`` was processed by ``numpy.squeeze`` and
|
||||
so any number of singleton dimensions was allowed. This resulted in
|
||||
inconsistent handling of images with singleton dimensions. To
|
||||
recover the old behaviour, use
|
||||
``regionprops(np.squeeze(label_image), ...)``.
|
||||
intensity_image : (N, M) ndarray, optional
|
||||
Intensity (i.e., input) image with same size as labeled image.
|
||||
Default is None.
|
||||
cache : bool, optional
|
||||
Determine whether to cache calculated properties. The computation is
|
||||
much faster for cached properties, whereas the memory consumption
|
||||
increases.
|
||||
coordinates : DEPRECATED
|
||||
This argument is deprecated and will be removed in a future version
|
||||
of scikit-image.
|
||||
|
||||
See :ref:`Coordinate conventions <numpy-images-coordinate-conventions>`
|
||||
for more details.
|
||||
|
||||
.. deprecated:: 0.16.0
|
||||
Use "rc" coordinates everywhere. It may be sufficient to call
|
||||
``numpy.transpose`` on your label image to get the same values as
|
||||
0.15 and earlier. However, for some properties, the transformation
|
||||
will be less trivial. For example, the new orientation is
|
||||
:math:`\frac{\pi}{2}` plus the old orientation.
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
properties : list of RegionProperties
|
||||
Each item describes one labeled region, and can be accessed using the
|
||||
attributes listed below.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The following properties can be accessed as attributes or keys:
|
||||
|
||||
**area** : int
|
||||
Number of pixels of the region.
|
||||
**bbox** : tuple
|
||||
Bounding box ``(min_row, min_col, max_row, max_col)``.
|
||||
Pixels belonging to the bounding box are in the half-open interval
|
||||
``[min_row; max_row)`` and ``[min_col; max_col)``.
|
||||
**bbox_area** : int
|
||||
Number of pixels of bounding box.
|
||||
**centroid** : array
|
||||
Centroid coordinate tuple ``(row, col)``.
|
||||
**convex_area** : int
|
||||
Number of pixels of convex hull image, which is the smallest convex
|
||||
polygon that encloses the region.
|
||||
**convex_image** : (H, J) ndarray
|
||||
Binary convex hull image which has the same size as bounding box.
|
||||
**coords** : (N, 2) ndarray
|
||||
Coordinate list ``(row, col)`` of the region.
|
||||
**eccentricity** : float
|
||||
Eccentricity of the ellipse that has the same second-moments as the
|
||||
region. The eccentricity is the ratio of the focal distance
|
||||
(distance between focal points) over the major axis length.
|
||||
The value is in the interval [0, 1).
|
||||
When it is 0, the ellipse becomes a circle.
|
||||
**equivalent_diameter** : float
|
||||
The diameter of a circle with the same area as the region.
|
||||
**euler_number** : int
|
||||
Euler characteristic of region. Computed as number of objects (= 1)
|
||||
subtracted by number of holes (8-connectivity).
|
||||
**extent** : float
|
||||
Ratio of pixels in the region to pixels in the total bounding box.
|
||||
Computed as ``area / (rows * cols)``
|
||||
**filled_area** : int
|
||||
Number of pixels of the region will all the holes filled in. Describes
|
||||
the area of the filled_image.
|
||||
**filled_image** : (H, J) ndarray
|
||||
Binary region image with filled holes which has the same size as
|
||||
bounding box.
|
||||
**image** : (H, J) ndarray
|
||||
Sliced binary region image which has the same size as bounding box.
|
||||
**inertia_tensor** : ndarray
|
||||
Inertia tensor of the region for the rotation around its mass.
|
||||
**inertia_tensor_eigvals** : tuple
|
||||
The eigenvalues of the inertia tensor in decreasing order.
|
||||
**intensity_image** : ndarray
|
||||
Image inside region bounding box.
|
||||
**label** : int
|
||||
The label in the labeled input image.
|
||||
**local_centroid** : array
|
||||
Centroid coordinate tuple ``(row, col)``, relative to region bounding
|
||||
box.
|
||||
**major_axis_length** : float
|
||||
The length of the major axis of the ellipse that has the same
|
||||
normalized second central moments as the region.
|
||||
**max_intensity** : float
|
||||
Value with the greatest intensity in the region.
|
||||
**mean_intensity** : float
|
||||
Value with the mean intensity in the region.
|
||||
**min_intensity** : float
|
||||
Value with the least intensity in the region.
|
||||
**minor_axis_length** : float
|
||||
The length of the minor axis of the ellipse that has the same
|
||||
normalized second central moments as the region.
|
||||
**moments** : (3, 3) ndarray
|
||||
Spatial moments up to 3rd order::
|
||||
|
||||
m_ij = sum{ array(row, col) * row^i * col^j }
|
||||
|
||||
where the sum is over the `row`, `col` coordinates of the region.
|
||||
**moments_central** : (3, 3) ndarray
|
||||
Central moments (translation invariant) up to 3rd order::
|
||||
|
||||
mu_ij = sum{ array(row, col) * (row - row_c)^i * (col - col_c)^j }
|
||||
|
||||
where the sum is over the `row`, `col` coordinates of the region,
|
||||
and `row_c` and `col_c` are the coordinates of the region's centroid.
|
||||
**moments_hu** : tuple
|
||||
Hu moments (translation, scale and rotation invariant).
|
||||
**moments_normalized** : (3, 3) ndarray
|
||||
Normalized moments (translation and scale invariant) up to 3rd order::
|
||||
|
||||
nu_ij = mu_ij / m_00^[(i+j)/2 + 1]
|
||||
|
||||
where `m_00` is the zeroth spatial moment.
|
||||
**orientation** : float
|
||||
Angle between the 0th axis (rows) and the major
|
||||
axis of the ellipse that has the same second moments as the region,
|
||||
ranging from `-pi/2` to `pi/2` counter-clockwise.
|
||||
**perimeter** : float
|
||||
Perimeter of object which approximates the contour as a line
|
||||
through the centers of border pixels using a 4-connectivity.
|
||||
**slice** : tuple of slices
|
||||
A slice to extract the object from the source image.
|
||||
**solidity** : float
|
||||
Ratio of pixels in the region to pixels of the convex hull image.
|
||||
**weighted_centroid** : array
|
||||
Centroid coordinate tuple ``(row, col)`` weighted with intensity
|
||||
image.
|
||||
**weighted_local_centroid** : array
|
||||
Centroid coordinate tuple ``(row, col)``, relative to region bounding
|
||||
box, weighted with intensity image.
|
||||
**weighted_moments** : (3, 3) ndarray
|
||||
Spatial moments of intensity image up to 3rd order::
|
||||
|
||||
wm_ij = sum{ array(row, col) * row^i * col^j }
|
||||
|
||||
where the sum is over the `row`, `col` coordinates of the region.
|
||||
**weighted_moments_central** : (3, 3) ndarray
|
||||
Central moments (translation invariant) of intensity image up to
|
||||
3rd order::
|
||||
|
||||
wmu_ij = sum{ array(row, col) * (row - row_c)^i * (col - col_c)^j }
|
||||
|
||||
where the sum is over the `row`, `col` coordinates of the region,
|
||||
and `row_c` and `col_c` are the coordinates of the region's weighted
|
||||
centroid.
|
||||
**weighted_moments_hu** : tuple
|
||||
Hu moments (translation, scale and rotation invariant) of intensity
|
||||
image.
|
||||
**weighted_moments_normalized** : (3, 3) ndarray
|
||||
Normalized moments (translation and scale invariant) of intensity
|
||||
image up to 3rd order::
|
||||
|
||||
wnu_ij = wmu_ij / wm_00^[(i+j)/2 + 1]
|
||||
|
||||
where ``wm_00`` is the zeroth spatial moment (intensity-weighted area).
|
||||
|
||||
Each region also supports iteration, so that you can do::
|
||||
|
||||
for prop in region:
|
||||
print(prop, region[prop])
|
||||
|
||||
See Also
|
||||
--------
|
||||
label
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Wilhelm Burger, Mark Burge. Principles of Digital Image Processing:
|
||||
Core Algorithms. Springer-Verlag, London, 2009.
|
||||
.. [2] B. Jähne. Digital Image Processing. Springer-Verlag,
|
||||
Berlin-Heidelberg, 6. edition, 2005.
|
||||
.. [3] T. H. Reiss. Recognizing Planar Objects Using Invariant Image
|
||||
Features, from Lecture notes in computer science, p. 676. Springer,
|
||||
Berlin, 1993.
|
||||
.. [4] https://en.wikipedia.org/wiki/Image_moment
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage import data, util
|
||||
>>> from skimage.measure import label
|
||||
>>> img = util.img_as_ubyte(data.coins()) > 110
|
||||
>>> label_img = label(img, connectivity=img.ndim)
|
||||
>>> props = regionprops(label_img)
|
||||
>>> # centroid of first labeled object
|
||||
>>> props[0].centroid
|
||||
(22.72987986048314, 81.91228523446583)
|
||||
>>> # centroid of first labeled object
|
||||
>>> props[0]['centroid']
|
||||
(22.72987986048314, 81.91228523446583)
|
||||
|
||||
"""
|
||||
|
||||
if label_image.ndim not in (2, 3):
|
||||
raise TypeError('Only 2-D and 3-D images supported.')
|
||||
|
||||
if not np.issubdtype(label_image.dtype, np.integer):
|
||||
if np.issubdtype(label_image.dtype, np.bool_):
|
||||
raise TypeError(
|
||||
'Non-integer image types are ambiguous: '
|
||||
'use skimage.measure.label to label the connected'
|
||||
'components of label_image,'
|
||||
'or label_image.astype(np.uint8) to interpret'
|
||||
'the True values as a single label.')
|
||||
else:
|
||||
raise TypeError(
|
||||
'Non-integer label_image types are ambiguous')
|
||||
|
||||
if coordinates is not None:
|
||||
if coordinates == 'rc':
|
||||
msg = ('The coordinates keyword argument to skimage.measure.'
|
||||
'regionprops is deprecated. All features are now computed '
|
||||
'in rc (row-column) coordinates. Please remove '
|
||||
'`coordinates="rc"` from all calls to regionprops before '
|
||||
'updating scikit-image.')
|
||||
warn(msg, stacklevel=2, category=FutureWarning)
|
||||
else:
|
||||
msg = ('Values other than "rc" for the "coordinates" argument '
|
||||
'to skimage.measure.regionprops are no longer supported. '
|
||||
'You should update your code to use "rc" coordinates and '
|
||||
'stop using the "coordinates" argument, or use skimage '
|
||||
'version 0.15.x or earlier.')
|
||||
raise ValueError(msg)
|
||||
|
||||
regions = []
|
||||
|
||||
objects = ndi.find_objects(label_image)
|
||||
for i, sl in enumerate(objects):
|
||||
if sl is None:
|
||||
continue
|
||||
|
||||
label = i + 1
|
||||
|
||||
props = RegionProperties(sl, label, label_image, intensity_image,
|
||||
cache)
|
||||
regions.append(props)
|
||||
|
||||
return regions
|
||||
|
||||
|
||||
def perimeter(image, neighbourhood=4):
|
||||
"""Calculate total perimeter of all objects in binary image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : (N, M) ndarray
|
||||
2D binary image.
|
||||
neighbourhood : 4 or 8, optional
|
||||
Neighborhood connectivity for border pixel determination. It is used to
|
||||
compute the contour. A higher neighbourhood 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, neighbourhood=4) # doctest: +ELLIPSIS
|
||||
7796.867...
|
||||
>>> perimeter(img_coins, neighbourhood=8) # doctest: +ELLIPSIS
|
||||
8806.268...
|
||||
|
||||
"""
|
||||
if image.ndim != 2:
|
||||
raise NotImplementedError('`perimeter` supports 2D images only')
|
||||
|
||||
if neighbourhood == 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.double)
|
||||
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 _parse_docs():
|
||||
import re
|
||||
import textwrap
|
||||
|
||||
doc = regionprops.__doc__ or ''
|
||||
matches = re.finditer(r'\*\*(\w+)\*\* \:.*?\n(.*?)(?=\n [\*\S]+)',
|
||||
doc, flags=re.DOTALL)
|
||||
prop_doc = {m.group(1): textwrap.dedent(m.group(2)) for m in matches}
|
||||
|
||||
return prop_doc
|
||||
|
||||
|
||||
def _install_properties_docs():
|
||||
prop_doc = _parse_docs()
|
||||
|
||||
for p in [member for member in dir(RegionProperties)
|
||||
if not member.startswith('_')]:
|
||||
getattr(RegionProperties, p).__doc__ = prop_doc[p]
|
||||
|
||||
|
||||
if __debug__:
|
||||
# don't install docstrings when in optimized/non-debug mode
|
||||
_install_properties_docs()
|
|
@ -0,0 +1,36 @@
|
|||
from warnings import warn
|
||||
from ..metrics._structural_similarity import structural_similarity
|
||||
|
||||
__all__ = ['compare_ssim']
|
||||
|
||||
|
||||
def compare_ssim(X, Y, win_size=None, gradient=False,
|
||||
data_range=None, multichannel=False, gaussian_weights=False,
|
||||
full=False, **kwargs):
|
||||
warn('DEPRECATED: skimage.measure.compare_ssim has been moved to '
|
||||
'skimage.metrics.structural_similarity. It will be removed from '
|
||||
'skimage.measure in version 0.18.', stacklevel=2)
|
||||
return structural_similarity(X, Y, win_size=win_size, gradient=gradient,
|
||||
data_range=data_range,
|
||||
multichannel=multichannel,
|
||||
gaussian_weights=gaussian_weights, full=full,
|
||||
**kwargs)
|
||||
|
||||
|
||||
if structural_similarity.__doc__ is not None:
|
||||
compare_ssim.__doc__ = structural_similarity.__doc__ + """
|
||||
|
||||
Warns
|
||||
-----
|
||||
Deprecated:
|
||||
.. versionadded:: 0.16
|
||||
|
||||
This function is deprecated and will be
|
||||
removed in scikit-image 0.18. Please use the function named
|
||||
``structural_similarity`` from the ``metrics`` module instead.
|
||||
|
||||
|
||||
See also
|
||||
--------
|
||||
skimage.metrics.structural_similarity
|
||||
"""
|
87
venv/Lib/site-packages/skimage/measure/block.py
Normal file
87
venv/Lib/site-packages/skimage/measure/block.py
Normal file
|
@ -0,0 +1,87 @@
|
|||
import numpy as np
|
||||
from ..util import view_as_blocks
|
||||
|
||||
|
||||
def block_reduce(image, block_size, func=np.sum, cval=0, func_kwargs=None):
|
||||
"""Downsample image by applying function `func` to local blocks.
|
||||
|
||||
This function is useful for max and mean pooling, for example.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
N-dimensional input image.
|
||||
block_size : array_like
|
||||
Array containing down-sampling integer factor along each axis.
|
||||
func : callable
|
||||
Function object which is used to calculate the return value for each
|
||||
local block. This function must implement an ``axis`` parameter.
|
||||
Primary functions are ``numpy.sum``, ``numpy.min``, ``numpy.max``,
|
||||
``numpy.mean`` and ``numpy.median``. See also `func_kwargs`.
|
||||
cval : float
|
||||
Constant padding value if image is not perfectly divisible by the
|
||||
block size.
|
||||
func_kwargs : dict
|
||||
Keyword arguments passed to `func`. Notably useful for passing dtype
|
||||
argument to ``np.mean``. Takes dictionary of inputs, e.g.:
|
||||
``func_kwargs={'dtype': np.float16})``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
image : ndarray
|
||||
Down-sampled image with same number of dimensions as input image.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.measure import block_reduce
|
||||
>>> image = np.arange(3*3*4).reshape(3, 3, 4)
|
||||
>>> image # doctest: +NORMALIZE_WHITESPACE
|
||||
array([[[ 0, 1, 2, 3],
|
||||
[ 4, 5, 6, 7],
|
||||
[ 8, 9, 10, 11]],
|
||||
[[12, 13, 14, 15],
|
||||
[16, 17, 18, 19],
|
||||
[20, 21, 22, 23]],
|
||||
[[24, 25, 26, 27],
|
||||
[28, 29, 30, 31],
|
||||
[32, 33, 34, 35]]])
|
||||
>>> block_reduce(image, block_size=(3, 3, 1), func=np.mean)
|
||||
array([[[16., 17., 18., 19.]]])
|
||||
>>> image_max1 = block_reduce(image, block_size=(1, 3, 4), func=np.max)
|
||||
>>> image_max1 # doctest: +NORMALIZE_WHITESPACE
|
||||
array([[[11]],
|
||||
[[23]],
|
||||
[[35]]])
|
||||
>>> image_max2 = block_reduce(image, block_size=(3, 1, 4), func=np.max)
|
||||
>>> image_max2 # doctest: +NORMALIZE_WHITESPACE
|
||||
array([[[27],
|
||||
[31],
|
||||
[35]]])
|
||||
"""
|
||||
|
||||
if len(block_size) != image.ndim:
|
||||
raise ValueError("`block_size` must have the same length "
|
||||
"as `image.shape`.")
|
||||
|
||||
if func_kwargs is None:
|
||||
func_kwargs = {}
|
||||
|
||||
pad_width = []
|
||||
for i in range(len(block_size)):
|
||||
if block_size[i] < 1:
|
||||
raise ValueError("Down-sampling factors must be >= 1. Use "
|
||||
"`skimage.transform.resize` to up-sample an "
|
||||
"image.")
|
||||
if image.shape[i] % block_size[i] != 0:
|
||||
after_width = block_size[i] - (image.shape[i] % block_size[i])
|
||||
else:
|
||||
after_width = 0
|
||||
pad_width.append((0, after_width))
|
||||
|
||||
image = np.pad(image, pad_width=pad_width, mode='constant',
|
||||
constant_values=cval)
|
||||
|
||||
blocked = view_as_blocks(image, block_size)
|
||||
|
||||
return func(blocked, axis=tuple(range(image.ndim, blocked.ndim)),
|
||||
**func_kwargs)
|
40
venv/Lib/site-packages/skimage/measure/entropy.py
Normal file
40
venv/Lib/site-packages/skimage/measure/entropy.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
from numpy import unique
|
||||
from scipy.stats import entropy as scipy_entropy
|
||||
|
||||
|
||||
def shannon_entropy(image, base=2):
|
||||
"""Calculate the Shannon entropy of an image.
|
||||
|
||||
The Shannon entropy is defined as S = -sum(pk * log(pk)),
|
||||
where pk are frequency/probability of pixels of value k.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : (N, M) ndarray
|
||||
Grayscale input image.
|
||||
base : float, optional
|
||||
The logarithmic base to use.
|
||||
|
||||
Returns
|
||||
-------
|
||||
entropy : float
|
||||
|
||||
Notes
|
||||
-----
|
||||
The returned value is measured in bits or shannon (Sh) for base=2, natural
|
||||
unit (nat) for base=np.e and hartley (Hart) for base=10.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] `https://en.wikipedia.org/wiki/Entropy_(information_theory) <https://en.wikipedia.org/wiki/Entropy_(information_theory)>`_
|
||||
.. [2] https://en.wiktionary.org/wiki/Shannon_entropy
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage import data
|
||||
>>> shannon_entropy(data.camera())
|
||||
7.047955232423086
|
||||
"""
|
||||
|
||||
_, counts = unique(image, return_counts=True)
|
||||
return scipy_entropy(counts, base=base)
|
876
venv/Lib/site-packages/skimage/measure/fit.py
Normal file
876
venv/Lib/site-packages/skimage/measure/fit.py
Normal file
|
@ -0,0 +1,876 @@
|
|||
import math
|
||||
import numpy as np
|
||||
from numpy.linalg import inv, pinv
|
||||
from scipy import optimize
|
||||
from .._shared.utils import check_random_state
|
||||
|
||||
|
||||
def _check_data_dim(data, dim):
|
||||
if data.ndim != 2 or data.shape[1] != dim:
|
||||
raise ValueError('Input data must have shape (N, %d).' % dim)
|
||||
|
||||
|
||||
def _check_data_atleast_2D(data):
|
||||
if data.ndim < 2 or data.shape[1] < 2:
|
||||
raise ValueError('Input data must be at least 2D.')
|
||||
|
||||
|
||||
def _norm_along_axis(x, axis):
|
||||
"""NumPy < 1.8 does not support the `axis` argument for `np.linalg.norm`."""
|
||||
return np.sqrt(np.einsum('ij,ij->i', x, x))
|
||||
|
||||
|
||||
class BaseModel(object):
|
||||
|
||||
def __init__(self):
|
||||
self.params = None
|
||||
|
||||
|
||||
class LineModelND(BaseModel):
|
||||
"""Total least squares estimator for N-dimensional lines.
|
||||
|
||||
In contrast to ordinary least squares line estimation, this estimator
|
||||
minimizes the orthogonal distances of points to the estimated line.
|
||||
|
||||
Lines are defined by a point (origin) and a unit vector (direction)
|
||||
according to the following vector equation::
|
||||
|
||||
X = origin + lambda * direction
|
||||
|
||||
Attributes
|
||||
----------
|
||||
params : tuple
|
||||
Line model parameters in the following order `origin`, `direction`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> x = np.linspace(1, 2, 25)
|
||||
>>> y = 1.5 * x + 3
|
||||
>>> lm = LineModelND()
|
||||
>>> lm.estimate(np.array([x, y]).T)
|
||||
True
|
||||
>>> tuple(np.round(lm.params, 5))
|
||||
(array([1.5 , 5.25]), array([0.5547 , 0.83205]))
|
||||
>>> res = lm.residuals(np.array([x, y]).T)
|
||||
>>> np.abs(np.round(res, 9))
|
||||
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
|
||||
0., 0., 0., 0., 0., 0., 0., 0.])
|
||||
>>> np.round(lm.predict_y(x[:5]), 3)
|
||||
array([4.5 , 4.562, 4.625, 4.688, 4.75 ])
|
||||
>>> np.round(lm.predict_x(y[:5]), 3)
|
||||
array([1. , 1.042, 1.083, 1.125, 1.167])
|
||||
|
||||
"""
|
||||
|
||||
def estimate(self, data):
|
||||
"""Estimate line model from data.
|
||||
|
||||
This minimizes the sum of shortest (orthogonal) distances
|
||||
from the given data points to the estimated line.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : (N, dim) array
|
||||
N points in a space of dimensionality dim >= 2.
|
||||
|
||||
Returns
|
||||
-------
|
||||
success : bool
|
||||
True, if model estimation succeeds.
|
||||
"""
|
||||
_check_data_atleast_2D(data)
|
||||
|
||||
origin = data.mean(axis=0)
|
||||
data = data - origin
|
||||
|
||||
if data.shape[0] == 2: # well determined
|
||||
direction = data[1] - data[0]
|
||||
norm = np.linalg.norm(direction)
|
||||
if norm != 0: # this should not happen to be norm 0
|
||||
direction /= norm
|
||||
elif data.shape[0] > 2: # over-determined
|
||||
# Note: with full_matrices=1 Python dies with joblib parallel_for.
|
||||
_, _, v = np.linalg.svd(data, full_matrices=False)
|
||||
direction = v[0]
|
||||
else: # under-determined
|
||||
raise ValueError('At least 2 input points needed.')
|
||||
|
||||
self.params = (origin, direction)
|
||||
|
||||
return True
|
||||
|
||||
def residuals(self, data, params=None):
|
||||
"""Determine residuals of data to model.
|
||||
|
||||
For each point, the shortest (orthogonal) distance to the line is
|
||||
returned. It is obtained by projecting the data onto the line.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : (N, dim) array
|
||||
N points in a space of dimension dim.
|
||||
params : (2, ) array, optional
|
||||
Optional custom parameter set in the form (`origin`, `direction`).
|
||||
|
||||
Returns
|
||||
-------
|
||||
residuals : (N, ) array
|
||||
Residual for each data point.
|
||||
"""
|
||||
_check_data_atleast_2D(data)
|
||||
if params is None:
|
||||
if self.params is None:
|
||||
raise ValueError('Parameters cannot be None')
|
||||
params = self.params
|
||||
if len(params) != 2:
|
||||
raise ValueError('Parameters are defined by 2 sets.')
|
||||
|
||||
origin, direction = params
|
||||
res = (data - origin) - \
|
||||
((data - origin) @ direction)[..., np.newaxis] * direction
|
||||
return _norm_along_axis(res, axis=1)
|
||||
|
||||
def predict(self, x, axis=0, params=None):
|
||||
"""Predict intersection of the estimated line model with a hyperplane
|
||||
orthogonal to a given axis.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : (n, 1) array
|
||||
Coordinates along an axis.
|
||||
axis : int
|
||||
Axis orthogonal to the hyperplane intersecting the line.
|
||||
params : (2, ) array, optional
|
||||
Optional custom parameter set in the form (`origin`, `direction`).
|
||||
|
||||
Returns
|
||||
-------
|
||||
data : (n, m) array
|
||||
Predicted coordinates.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If the line is parallel to the given axis.
|
||||
"""
|
||||
if params is None:
|
||||
if self.params is None:
|
||||
raise ValueError('Parameters cannot be None')
|
||||
params = self.params
|
||||
if len(params) != 2:
|
||||
raise ValueError('Parameters are defined by 2 sets.')
|
||||
|
||||
origin, direction = params
|
||||
|
||||
if direction[axis] == 0:
|
||||
# line parallel to axis
|
||||
raise ValueError('Line parallel to axis %s' % axis)
|
||||
|
||||
l = (x - origin[axis]) / direction[axis]
|
||||
data = origin + l[..., np.newaxis] * direction
|
||||
return data
|
||||
|
||||
def predict_x(self, y, params=None):
|
||||
"""Predict x-coordinates for 2D lines using the estimated model.
|
||||
|
||||
Alias for::
|
||||
|
||||
predict(y, axis=1)[:, 0]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
y : array
|
||||
y-coordinates.
|
||||
params : (2, ) array, optional
|
||||
Optional custom parameter set in the form (`origin`, `direction`).
|
||||
|
||||
Returns
|
||||
-------
|
||||
x : array
|
||||
Predicted x-coordinates.
|
||||
|
||||
"""
|
||||
x = self.predict(y, axis=1, params=params)[:, 0]
|
||||
return x
|
||||
|
||||
def predict_y(self, x, params=None):
|
||||
"""Predict y-coordinates for 2D lines using the estimated model.
|
||||
|
||||
Alias for::
|
||||
|
||||
predict(x, axis=0)[:, 1]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array
|
||||
x-coordinates.
|
||||
params : (2, ) array, optional
|
||||
Optional custom parameter set in the form (`origin`, `direction`).
|
||||
|
||||
Returns
|
||||
-------
|
||||
y : array
|
||||
Predicted y-coordinates.
|
||||
|
||||
"""
|
||||
y = self.predict(x, axis=0, params=params)[:, 1]
|
||||
return y
|
||||
|
||||
|
||||
class CircleModel(BaseModel):
|
||||
|
||||
"""Total least squares estimator for 2D circles.
|
||||
|
||||
The functional model of the circle is::
|
||||
|
||||
r**2 = (x - xc)**2 + (y - yc)**2
|
||||
|
||||
This estimator minimizes the squared distances from all points to the
|
||||
circle::
|
||||
|
||||
min{ sum((r - sqrt((x_i - xc)**2 + (y_i - yc)**2))**2) }
|
||||
|
||||
A minimum number of 3 points is required to solve for the parameters.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
params : tuple
|
||||
Circle model parameters in the following order `xc`, `yc`, `r`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> t = np.linspace(0, 2 * np.pi, 25)
|
||||
>>> xy = CircleModel().predict_xy(t, params=(2, 3, 4))
|
||||
>>> model = CircleModel()
|
||||
>>> model.estimate(xy)
|
||||
True
|
||||
>>> tuple(np.round(model.params, 5))
|
||||
(2.0, 3.0, 4.0)
|
||||
>>> res = model.residuals(xy)
|
||||
>>> np.abs(np.round(res, 9))
|
||||
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
|
||||
0., 0., 0., 0., 0., 0., 0., 0.])
|
||||
"""
|
||||
|
||||
def estimate(self, data):
|
||||
"""Estimate circle model from data using total least squares.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : (N, 2) array
|
||||
N points with ``(x, y)`` coordinates, respectively.
|
||||
|
||||
Returns
|
||||
-------
|
||||
success : bool
|
||||
True, if model estimation succeeds.
|
||||
|
||||
"""
|
||||
|
||||
_check_data_dim(data, dim=2)
|
||||
|
||||
x = data[:, 0]
|
||||
y = data[:, 1]
|
||||
|
||||
# http://www.had2know.com/academics/best-fit-circle-least-squares.html
|
||||
x2y2 = (x ** 2 + y ** 2)
|
||||
sum_x = np.sum(x)
|
||||
sum_y = np.sum(y)
|
||||
sum_xy = np.sum(x * y)
|
||||
m1 = np.array([[np.sum(x ** 2), sum_xy, sum_x],
|
||||
[sum_xy, np.sum(y ** 2), sum_y],
|
||||
[sum_x, sum_y, float(len(x))]])
|
||||
m2 = np.array([[np.sum(x * x2y2),
|
||||
np.sum(y * x2y2),
|
||||
np.sum(x2y2)]]).T
|
||||
a, b, c = pinv(m1) @ m2
|
||||
a, b, c = a[0], b[0], c[0]
|
||||
xc = a / 2
|
||||
yc = b / 2
|
||||
r = np.sqrt(4 * c + a ** 2 + b ** 2) / 2
|
||||
|
||||
self.params = (xc, yc, r)
|
||||
|
||||
return True
|
||||
|
||||
def residuals(self, data):
|
||||
"""Determine residuals of data to model.
|
||||
|
||||
For each point the shortest distance to the circle is returned.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : (N, 2) array
|
||||
N points with ``(x, y)`` coordinates, respectively.
|
||||
|
||||
Returns
|
||||
-------
|
||||
residuals : (N, ) array
|
||||
Residual for each data point.
|
||||
|
||||
"""
|
||||
|
||||
_check_data_dim(data, dim=2)
|
||||
|
||||
xc, yc, r = self.params
|
||||
|
||||
x = data[:, 0]
|
||||
y = data[:, 1]
|
||||
|
||||
return r - np.sqrt((x - xc)**2 + (y - yc)**2)
|
||||
|
||||
def predict_xy(self, t, params=None):
|
||||
"""Predict x- and y-coordinates using the estimated model.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
t : array
|
||||
Angles in circle in radians. Angles start to count from positive
|
||||
x-axis to positive y-axis in a right-handed system.
|
||||
params : (3, ) array, optional
|
||||
Optional custom parameter set.
|
||||
|
||||
Returns
|
||||
-------
|
||||
xy : (..., 2) array
|
||||
Predicted x- and y-coordinates.
|
||||
|
||||
"""
|
||||
if params is None:
|
||||
params = self.params
|
||||
xc, yc, r = params
|
||||
|
||||
x = xc + r * np.cos(t)
|
||||
y = yc + r * np.sin(t)
|
||||
|
||||
return np.concatenate((x[..., None], y[..., None]), axis=t.ndim)
|
||||
|
||||
|
||||
class EllipseModel(BaseModel):
|
||||
"""Total least squares estimator for 2D ellipses.
|
||||
|
||||
The functional model of the ellipse is::
|
||||
|
||||
xt = xc + a*cos(theta)*cos(t) - b*sin(theta)*sin(t)
|
||||
yt = yc + a*sin(theta)*cos(t) + b*cos(theta)*sin(t)
|
||||
d = sqrt((x - xt)**2 + (y - yt)**2)
|
||||
|
||||
where ``(xt, yt)`` is the closest point on the ellipse to ``(x, y)``. Thus
|
||||
d is the shortest distance from the point to the ellipse.
|
||||
|
||||
The estimator is based on a least squares minimization. The optimal
|
||||
solution is computed directly, no iterations are required. This leads
|
||||
to a simple, stable and robust fitting method.
|
||||
|
||||
The ``params`` attribute contains the parameters in the following order::
|
||||
|
||||
xc, yc, a, b, theta
|
||||
|
||||
Attributes
|
||||
----------
|
||||
params : tuple
|
||||
Ellipse model parameters in the following order `xc`, `yc`, `a`, `b`,
|
||||
`theta`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
>>> xy = EllipseModel().predict_xy(np.linspace(0, 2 * np.pi, 25),
|
||||
... params=(10, 15, 4, 8, np.deg2rad(30)))
|
||||
>>> ellipse = EllipseModel()
|
||||
>>> ellipse.estimate(xy)
|
||||
True
|
||||
>>> np.round(ellipse.params, 2)
|
||||
array([10. , 15. , 4. , 8. , 0.52])
|
||||
>>> np.round(abs(ellipse.residuals(xy)), 5)
|
||||
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
|
||||
0., 0., 0., 0., 0., 0., 0., 0.])
|
||||
"""
|
||||
|
||||
def estimate(self, data):
|
||||
"""Estimate circle model from data using total least squares.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : (N, 2) array
|
||||
N points with ``(x, y)`` coordinates, respectively.
|
||||
|
||||
Returns
|
||||
-------
|
||||
success : bool
|
||||
True, if model estimation succeeds.
|
||||
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Halir, R.; Flusser, J. "Numerically stable direct least squares
|
||||
fitting of ellipses". In Proc. 6th International Conference in
|
||||
Central Europe on Computer Graphics and Visualization.
|
||||
WSCG (Vol. 98, pp. 125-132).
|
||||
|
||||
"""
|
||||
# Original Implementation: Ben Hammel, Nick Sullivan-Molina
|
||||
# another REFERENCE: [2] http://mathworld.wolfram.com/Ellipse.html
|
||||
_check_data_dim(data, dim=2)
|
||||
|
||||
x = data[:, 0]
|
||||
y = data[:, 1]
|
||||
|
||||
# Quadratic part of design matrix [eqn. 15] from [1]
|
||||
D1 = np.vstack([x ** 2, x * y, y ** 2]).T
|
||||
# Linear part of design matrix [eqn. 16] from [1]
|
||||
D2 = np.vstack([x, y, np.ones(len(x))]).T
|
||||
|
||||
# forming scatter matrix [eqn. 17] from [1]
|
||||
S1 = D1.T @ D1
|
||||
S2 = D1.T @ D2
|
||||
S3 = D2.T @ D2
|
||||
|
||||
# Constraint matrix [eqn. 18]
|
||||
C1 = np.array([[0., 0., 2.], [0., -1., 0.], [2., 0., 0.]])
|
||||
|
||||
try:
|
||||
# Reduced scatter matrix [eqn. 29]
|
||||
M = inv(C1) @ (S1 - S2 @ inv(S3) @ S2.T)
|
||||
except np.linalg.LinAlgError: # LinAlgError: Singular matrix
|
||||
return False
|
||||
|
||||
# M*|a b c >=l|a b c >. Find eigenvalues and eigenvectors
|
||||
# from this equation [eqn. 28]
|
||||
eig_vals, eig_vecs = np.linalg.eig(M)
|
||||
|
||||
# eigenvector must meet constraint 4ac - b^2 to be valid.
|
||||
cond = 4 * np.multiply(eig_vecs[0, :], eig_vecs[2, :]) \
|
||||
- np.power(eig_vecs[1, :], 2)
|
||||
a1 = eig_vecs[:, (cond > 0)]
|
||||
# seeks for empty matrix
|
||||
if 0 in a1.shape or len(a1.ravel()) != 3:
|
||||
return False
|
||||
a, b, c = a1.ravel()
|
||||
|
||||
# |d f g> = -S3^(-1)*S2^(T)*|a b c> [eqn. 24]
|
||||
a2 = -inv(S3) @ S2.T @ a1
|
||||
d, f, g = a2.ravel()
|
||||
|
||||
# eigenvectors are the coefficients of an ellipse in general form
|
||||
# a*x^2 + 2*b*x*y + c*y^2 + 2*d*x + 2*f*y + g = 0 (eqn. 15) from [2]
|
||||
b /= 2.
|
||||
d /= 2.
|
||||
f /= 2.
|
||||
|
||||
# finding center of ellipse [eqn.19 and 20] from [2]
|
||||
x0 = (c * d - b * f) / (b ** 2. - a * c)
|
||||
y0 = (a * f - b * d) / (b ** 2. - a * c)
|
||||
|
||||
# Find the semi-axes lengths [eqn. 21 and 22] from [2]
|
||||
numerator = a * f ** 2 + c * d ** 2 + g * b ** 2 \
|
||||
- 2 * b * d * f - a * c * g
|
||||
term = np.sqrt((a - c) ** 2 + 4 * b ** 2)
|
||||
denominator1 = (b ** 2 - a * c) * (term - (a + c))
|
||||
denominator2 = (b ** 2 - a * c) * (- term - (a + c))
|
||||
width = np.sqrt(2 * numerator / denominator1)
|
||||
height = np.sqrt(2 * numerator / denominator2)
|
||||
|
||||
# angle of counterclockwise rotation of major-axis of ellipse
|
||||
# to x-axis [eqn. 23] from [2].
|
||||
phi = 0.5 * np.arctan((2. * b) / (a - c))
|
||||
if a > c:
|
||||
phi += 0.5 * np.pi
|
||||
|
||||
self.params = np.nan_to_num([x0, y0, width, height, phi]).tolist()
|
||||
self.params = [float(np.real(x)) for x in self.params]
|
||||
return True
|
||||
|
||||
def residuals(self, data):
|
||||
"""Determine residuals of data to model.
|
||||
|
||||
For each point the shortest distance to the ellipse is returned.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : (N, 2) array
|
||||
N points with ``(x, y)`` coordinates, respectively.
|
||||
|
||||
Returns
|
||||
-------
|
||||
residuals : (N, ) array
|
||||
Residual for each data point.
|
||||
|
||||
"""
|
||||
|
||||
_check_data_dim(data, dim=2)
|
||||
|
||||
xc, yc, a, b, theta = self.params
|
||||
|
||||
ctheta = math.cos(theta)
|
||||
stheta = math.sin(theta)
|
||||
|
||||
x = data[:, 0]
|
||||
y = data[:, 1]
|
||||
|
||||
N = data.shape[0]
|
||||
|
||||
def fun(t, xi, yi):
|
||||
ct = math.cos(t)
|
||||
st = math.sin(t)
|
||||
xt = xc + a * ctheta * ct - b * stheta * st
|
||||
yt = yc + a * stheta * ct + b * ctheta * st
|
||||
return (xi - xt) ** 2 + (yi - yt) ** 2
|
||||
|
||||
# def Dfun(t, xi, yi):
|
||||
# ct = math.cos(t)
|
||||
# st = math.sin(t)
|
||||
# xt = xc + a * ctheta * ct - b * stheta * st
|
||||
# yt = yc + a * stheta * ct + b * ctheta * st
|
||||
# dfx_t = - 2 * (xi - xt) * (- a * ctheta * st
|
||||
# - b * stheta * ct)
|
||||
# dfy_t = - 2 * (yi - yt) * (- a * stheta * st
|
||||
# + b * ctheta * ct)
|
||||
# return [dfx_t + dfy_t]
|
||||
|
||||
residuals = np.empty((N, ), dtype=np.double)
|
||||
|
||||
# initial guess for parameter t of closest point on ellipse
|
||||
t0 = np.arctan2(y - yc, x - xc) - theta
|
||||
|
||||
# determine shortest distance to ellipse for each point
|
||||
for i in range(N):
|
||||
xi = x[i]
|
||||
yi = y[i]
|
||||
# faster without Dfun, because of the python overhead
|
||||
t, _ = optimize.leastsq(fun, t0[i], args=(xi, yi))
|
||||
residuals[i] = np.sqrt(fun(t, xi, yi))
|
||||
|
||||
return residuals
|
||||
|
||||
def predict_xy(self, t, params=None):
|
||||
"""Predict x- and y-coordinates using the estimated model.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
t : array
|
||||
Angles in circle in radians. Angles start to count from positive
|
||||
x-axis to positive y-axis in a right-handed system.
|
||||
params : (5, ) array, optional
|
||||
Optional custom parameter set.
|
||||
|
||||
Returns
|
||||
-------
|
||||
xy : (..., 2) array
|
||||
Predicted x- and y-coordinates.
|
||||
|
||||
"""
|
||||
|
||||
if params is None:
|
||||
params = self.params
|
||||
|
||||
xc, yc, a, b, theta = params
|
||||
|
||||
ct = np.cos(t)
|
||||
st = np.sin(t)
|
||||
ctheta = math.cos(theta)
|
||||
stheta = math.sin(theta)
|
||||
|
||||
x = xc + a * ctheta * ct - b * stheta * st
|
||||
y = yc + a * stheta * ct + b * ctheta * st
|
||||
|
||||
return np.concatenate((x[..., None], y[..., None]), axis=t.ndim)
|
||||
|
||||
|
||||
def _dynamic_max_trials(n_inliers, n_samples, min_samples, probability):
|
||||
"""Determine number trials such that at least one outlier-free subset is
|
||||
sampled for the given inlier/outlier ratio.
|
||||
Parameters
|
||||
----------
|
||||
n_inliers : int
|
||||
Number of inliers in the data.
|
||||
n_samples : int
|
||||
Total number of samples in the data.
|
||||
min_samples : int
|
||||
Minimum number of samples chosen randomly from original data.
|
||||
probability : float
|
||||
Probability (confidence) that one outlier-free sample is generated.
|
||||
Returns
|
||||
-------
|
||||
trials : int
|
||||
Number of trials.
|
||||
"""
|
||||
if n_inliers == 0:
|
||||
return np.inf
|
||||
|
||||
nom = 1 - probability
|
||||
if nom == 0:
|
||||
return np.inf
|
||||
|
||||
inlier_ratio = n_inliers / float(n_samples)
|
||||
denom = 1 - inlier_ratio ** min_samples
|
||||
if denom == 0:
|
||||
return 1
|
||||
elif denom == 1:
|
||||
return np.inf
|
||||
|
||||
nom = np.log(nom)
|
||||
denom = np.log(denom)
|
||||
if denom == 0:
|
||||
return 0
|
||||
|
||||
return int(np.ceil(nom / denom))
|
||||
|
||||
|
||||
def ransac(data, model_class, min_samples, residual_threshold,
|
||||
is_data_valid=None, is_model_valid=None,
|
||||
max_trials=100, stop_sample_num=np.inf, stop_residuals_sum=0,
|
||||
stop_probability=1, random_state=None, initial_inliers=None):
|
||||
"""Fit a model to data with the RANSAC (random sample consensus) algorithm.
|
||||
|
||||
RANSAC is an iterative algorithm for the robust estimation of parameters
|
||||
from a subset of inliers from the complete data set. Each iteration
|
||||
performs the following tasks:
|
||||
|
||||
1. Select `min_samples` random samples from the original data and check
|
||||
whether the set of data is valid (see `is_data_valid`).
|
||||
2. Estimate a model to the random subset
|
||||
(`model_cls.estimate(*data[random_subset]`) and check whether the
|
||||
estimated model is valid (see `is_model_valid`).
|
||||
3. Classify all data as inliers or outliers by calculating the residuals
|
||||
to the estimated model (`model_cls.residuals(*data)`) - all data samples
|
||||
with residuals smaller than the `residual_threshold` are considered as
|
||||
inliers.
|
||||
4. Save estimated model as best model if number of inlier samples is
|
||||
maximal. In case the current estimated model has the same number of
|
||||
inliers, it is only considered as the best model if it has less sum of
|
||||
residuals.
|
||||
|
||||
These steps are performed either a maximum number of times or until one of
|
||||
the special stop criteria are met. The final model is estimated using all
|
||||
inlier samples of the previously determined best model.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : [list, tuple of] (N, ...) array
|
||||
Data set to which the model is fitted, where N is the number of data
|
||||
points and the remaining dimension are depending on model requirements.
|
||||
If the model class requires multiple input data arrays (e.g. source and
|
||||
destination coordinates of ``skimage.transform.AffineTransform``),
|
||||
they can be optionally passed as tuple or list. Note, that in this case
|
||||
the functions ``estimate(*data)``, ``residuals(*data)``,
|
||||
``is_model_valid(model, *random_data)`` and
|
||||
``is_data_valid(*random_data)`` must all take each data array as
|
||||
separate arguments.
|
||||
model_class : object
|
||||
Object with the following object methods:
|
||||
|
||||
* ``success = estimate(*data)``
|
||||
* ``residuals(*data)``
|
||||
|
||||
where `success` indicates whether the model estimation succeeded
|
||||
(`True` or `None` for success, `False` for failure).
|
||||
min_samples : int in range (0, N)
|
||||
The minimum number of data points to fit a model to.
|
||||
residual_threshold : float larger than 0
|
||||
Maximum distance for a data point to be classified as an inlier.
|
||||
is_data_valid : function, optional
|
||||
This function is called with the randomly selected data before the
|
||||
model is fitted to it: `is_data_valid(*random_data)`.
|
||||
is_model_valid : function, optional
|
||||
This function is called with the estimated model and the randomly
|
||||
selected data: `is_model_valid(model, *random_data)`, .
|
||||
max_trials : int, optional
|
||||
Maximum number of iterations for random sample selection.
|
||||
stop_sample_num : int, optional
|
||||
Stop iteration if at least this number of inliers are found.
|
||||
stop_residuals_sum : float, optional
|
||||
Stop iteration if sum of residuals is less than or equal to this
|
||||
threshold.
|
||||
stop_probability : float in range [0, 1], optional
|
||||
RANSAC iteration stops if at least one outlier-free set of the
|
||||
training data is sampled with ``probability >= stop_probability``,
|
||||
depending on the current best model's inlier ratio and the number
|
||||
of trials. This requires to generate at least N samples (trials):
|
||||
|
||||
N >= log(1 - probability) / log(1 - e**m)
|
||||
|
||||
where the probability (confidence) is typically set to a high value
|
||||
such as 0.99, e is the current fraction of inliers w.r.t. the
|
||||
total number of samples, and m is the min_samples value.
|
||||
random_state : int, RandomState instance or None, optional
|
||||
If int, random_state is the seed used by the random number generator;
|
||||
If RandomState instance, random_state is the random number generator;
|
||||
If None, the random number generator is the RandomState instance used
|
||||
by `np.random`.
|
||||
initial_inliers : array-like of bool, shape (N,), optional
|
||||
Initial samples selection for model estimation
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
model : object
|
||||
Best model with largest consensus set.
|
||||
inliers : (N, ) array
|
||||
Boolean mask of inliers classified as ``True``.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] "RANSAC", Wikipedia, https://en.wikipedia.org/wiki/RANSAC
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Generate ellipse data without tilt and add noise:
|
||||
|
||||
>>> t = np.linspace(0, 2 * np.pi, 50)
|
||||
>>> xc, yc = 20, 30
|
||||
>>> a, b = 5, 10
|
||||
>>> x = xc + a * np.cos(t)
|
||||
>>> y = yc + b * np.sin(t)
|
||||
>>> data = np.column_stack([x, y])
|
||||
>>> np.random.seed(seed=1234)
|
||||
>>> data += np.random.normal(size=data.shape)
|
||||
|
||||
Add some faulty data:
|
||||
|
||||
>>> data[0] = (100, 100)
|
||||
>>> data[1] = (110, 120)
|
||||
>>> data[2] = (120, 130)
|
||||
>>> data[3] = (140, 130)
|
||||
|
||||
Estimate ellipse model using all available data:
|
||||
|
||||
>>> model = EllipseModel()
|
||||
>>> model.estimate(data)
|
||||
True
|
||||
>>> np.round(model.params) # doctest: +SKIP
|
||||
array([ 72., 75., 77., 14., 1.])
|
||||
|
||||
Estimate ellipse model using RANSAC:
|
||||
|
||||
>>> ransac_model, inliers = ransac(data, EllipseModel, 20, 3, max_trials=50)
|
||||
>>> abs(np.round(ransac_model.params))
|
||||
array([20., 30., 5., 10., 0.])
|
||||
>>> inliers # doctest: +SKIP
|
||||
array([False, False, False, False, True, True, True, True, True,
|
||||
True, True, True, True, True, True, True, True, True,
|
||||
True, True, True, True, True, True, True, True, True,
|
||||
True, True, True, True, True, True, True, True, True,
|
||||
True, True, True, True, True, True, True, True, True,
|
||||
True, True, True, True, True], dtype=bool)
|
||||
>>> sum(inliers) > 40
|
||||
True
|
||||
|
||||
RANSAC can be used to robustly estimate a geometric transformation. In this section,
|
||||
we also show how to use a proportion of the total samples, rather than an absolute number.
|
||||
|
||||
>>> from skimage.transform import SimilarityTransform
|
||||
>>> np.random.seed(0)
|
||||
>>> src = 100 * np.random.rand(50, 2)
|
||||
>>> model0 = SimilarityTransform(scale=0.5, rotation=1, translation=(10, 20))
|
||||
>>> dst = model0(src)
|
||||
>>> dst[0] = (10000, 10000)
|
||||
>>> dst[1] = (-100, 100)
|
||||
>>> dst[2] = (50, 50)
|
||||
>>> ratio = 0.5 # use half of the samples
|
||||
>>> min_samples = int(ratio * len(src))
|
||||
>>> model, inliers = ransac((src, dst), SimilarityTransform, min_samples, 10,
|
||||
... initial_inliers=np.ones(len(src), dtype=bool))
|
||||
>>> inliers
|
||||
array([False, False, False, True, True, True, True, True, True,
|
||||
True, True, True, True, True, True, True, True, True,
|
||||
True, True, True, True, True, True, True, True, True,
|
||||
True, True, True, True, True, True, True, True, True,
|
||||
True, True, True, True, True, True, True, True, True,
|
||||
True, True, True, True, True])
|
||||
|
||||
"""
|
||||
|
||||
best_model = None
|
||||
best_inlier_num = 0
|
||||
best_inlier_residuals_sum = np.inf
|
||||
best_inliers = None
|
||||
|
||||
random_state = check_random_state(random_state)
|
||||
|
||||
# in case data is not pair of input and output, male it like it
|
||||
if not isinstance(data, (tuple, list)):
|
||||
data = (data, )
|
||||
num_samples = len(data[0])
|
||||
|
||||
if not (0 < min_samples < num_samples):
|
||||
raise ValueError("`min_samples` must be in range (0, <number-of-samples>)")
|
||||
|
||||
if residual_threshold < 0:
|
||||
raise ValueError("`residual_threshold` must be greater than zero")
|
||||
|
||||
if max_trials < 0:
|
||||
raise ValueError("`max_trials` must be greater than zero")
|
||||
|
||||
if not (0 <= stop_probability <= 1):
|
||||
raise ValueError("`stop_probability` must be in range [0, 1]")
|
||||
|
||||
if initial_inliers is not None and len(initial_inliers) != num_samples:
|
||||
raise ValueError("RANSAC received a vector of initial inliers (length %i)"
|
||||
" that didn't match the number of samples (%i)."
|
||||
" The vector of initial inliers should have the same length"
|
||||
" as the number of samples and contain only True (this sample"
|
||||
" is an initial inlier) and False (this one isn't) values."
|
||||
% (len(initial_inliers), num_samples))
|
||||
|
||||
# for the first run use initial guess of inliers
|
||||
spl_idxs = (initial_inliers if initial_inliers is not None
|
||||
else random_state.choice(num_samples, min_samples, replace=False))
|
||||
|
||||
for num_trials in range(max_trials):
|
||||
# do sample selection according data pairs
|
||||
samples = [d[spl_idxs] for d in data]
|
||||
# for next iteration choose random sample set and be sure that no samples repeat
|
||||
spl_idxs = random_state.choice(num_samples, min_samples, replace=False)
|
||||
|
||||
# optional check if random sample set is valid
|
||||
if is_data_valid is not None and not is_data_valid(*samples):
|
||||
continue
|
||||
|
||||
# estimate model for current random sample set
|
||||
sample_model = model_class()
|
||||
|
||||
success = sample_model.estimate(*samples)
|
||||
# backwards compatibility
|
||||
if success is not None and not success:
|
||||
continue
|
||||
|
||||
# optional check if estimated model is valid
|
||||
if is_model_valid is not None and not is_model_valid(sample_model, *samples):
|
||||
continue
|
||||
|
||||
sample_model_residuals = np.abs(sample_model.residuals(*data))
|
||||
# consensus set / inliers
|
||||
sample_model_inliers = sample_model_residuals < residual_threshold
|
||||
sample_model_residuals_sum = np.sum(sample_model_residuals ** 2)
|
||||
|
||||
# choose as new best model if number of inliers is maximal
|
||||
sample_inlier_num = np.sum(sample_model_inliers)
|
||||
if (
|
||||
# more inliers
|
||||
sample_inlier_num > best_inlier_num
|
||||
# same number of inliers but less "error" in terms of residuals
|
||||
or (sample_inlier_num == best_inlier_num
|
||||
and sample_model_residuals_sum < best_inlier_residuals_sum)
|
||||
):
|
||||
best_model = sample_model
|
||||
best_inlier_num = sample_inlier_num
|
||||
best_inlier_residuals_sum = sample_model_residuals_sum
|
||||
best_inliers = sample_model_inliers
|
||||
dynamic_max_trials = _dynamic_max_trials(best_inlier_num,
|
||||
num_samples,
|
||||
min_samples,
|
||||
stop_probability)
|
||||
if (best_inlier_num >= stop_sample_num
|
||||
or best_inlier_residuals_sum <= stop_residuals_sum
|
||||
or num_trials >= dynamic_max_trials):
|
||||
break
|
||||
|
||||
# estimate final model using all inliers
|
||||
if best_inliers is not None:
|
||||
# select inliers for each data array
|
||||
data_inliers = [d[best_inliers] for d in data]
|
||||
best_model.estimate(*data_inliers)
|
||||
|
||||
return best_model, best_inliers
|
53
venv/Lib/site-packages/skimage/measure/pnpoly.py
Normal file
53
venv/Lib/site-packages/skimage/measure/pnpoly.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
from ._pnpoly import _grid_points_in_poly, _points_in_poly
|
||||
|
||||
|
||||
def grid_points_in_poly(shape, verts):
|
||||
"""Test whether points on a specified grid are inside a polygon.
|
||||
|
||||
For each ``(r, c)`` coordinate on a grid, i.e. ``(0, 0)``, ``(0, 1)`` etc.,
|
||||
test whether that point lies inside a polygon.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
shape : tuple (M, N)
|
||||
Shape of the grid.
|
||||
verts : (V, 2) array
|
||||
Specify the V vertices of the polygon, sorted either clockwise
|
||||
or anti-clockwise. The first point may (but does not need to be)
|
||||
duplicated.
|
||||
|
||||
See Also
|
||||
--------
|
||||
points_in_poly
|
||||
|
||||
Returns
|
||||
-------
|
||||
mask : (M, N) ndarray of bool
|
||||
True where the grid falls inside the polygon.
|
||||
|
||||
"""
|
||||
return _grid_points_in_poly(shape, verts)
|
||||
|
||||
|
||||
def points_in_poly(points, verts):
|
||||
"""Test whether points lie inside a polygon.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
points : (N, 2) array
|
||||
Input points, ``(x, y)``.
|
||||
verts : (M, 2) array
|
||||
Vertices of the polygon, sorted either clockwise or anti-clockwise.
|
||||
The first point may (but does not need to be) duplicated.
|
||||
|
||||
See Also
|
||||
--------
|
||||
grid_points_in_poly
|
||||
|
||||
Returns
|
||||
-------
|
||||
mask : (N,) array of bool
|
||||
True if corresponding point is inside the polygon.
|
||||
|
||||
"""
|
||||
return _points_in_poly(points, verts)
|
174
venv/Lib/site-packages/skimage/measure/profile.py
Normal file
174
venv/Lib/site-packages/skimage/measure/profile.py
Normal file
|
@ -0,0 +1,174 @@
|
|||
from warnings import warn
|
||||
import numpy as np
|
||||
from scipy import ndimage as ndi
|
||||
|
||||
from .._shared.utils import _validate_interpolation_order
|
||||
|
||||
|
||||
def profile_line(image, src, dst, linewidth=1,
|
||||
order=None, mode=None, cval=0.0,
|
||||
*, reduce_func=np.mean):
|
||||
"""Return the intensity profile of an image measured along a scan line.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray, shape (M, N[, C])
|
||||
The image, either grayscale (2D array) or multichannel
|
||||
(3D array, where the final axis contains the channel
|
||||
information).
|
||||
src : array_like, shape (2, )
|
||||
The coordinates of the start point of the scan line.
|
||||
dst : array_like, shape (2, )
|
||||
The coordinates of the end point of the scan
|
||||
line. The destination point is *included* in the profile, in
|
||||
contrast to standard numpy indexing.
|
||||
linewidth : int, optional
|
||||
Width of the scan, perpendicular to the line
|
||||
order : int in {0, 1, 2, 3, 4, 5}, optional
|
||||
The order of the spline interpolation, default is 0 if
|
||||
image.dtype is bool and 1 otherwise. The order has to be in
|
||||
the range 0-5. See `skimage.transform.warp` for detail.
|
||||
mode : {'constant', 'nearest', 'reflect', 'mirror', 'wrap'}, optional
|
||||
How to compute any values falling outside of the image.
|
||||
cval : float, optional
|
||||
If `mode` is 'constant', what constant value to use outside the image.
|
||||
reduce_func : callable, optional
|
||||
Function used to calculate the aggregation of pixel values
|
||||
perpendicular to the profile_line direction when `linewidth` > 1.
|
||||
If set to None the unreduced array will be returned.
|
||||
|
||||
Returns
|
||||
-------
|
||||
return_value : array
|
||||
The intensity profile along the scan line. The length of the profile
|
||||
is the ceil of the computed length of the scan line.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> x = np.array([[1, 1, 1, 2, 2, 2]])
|
||||
>>> img = np.vstack([np.zeros_like(x), x, x, x, np.zeros_like(x)])
|
||||
>>> img
|
||||
array([[0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 2, 2, 2],
|
||||
[1, 1, 1, 2, 2, 2],
|
||||
[1, 1, 1, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0]])
|
||||
>>> profile_line(img, (2, 1), (2, 4))
|
||||
array([1., 1., 2., 2.])
|
||||
>>> profile_line(img, (1, 0), (1, 6), cval=4)
|
||||
array([1., 1., 1., 2., 2., 2., 4.])
|
||||
|
||||
The destination point is included in the profile, in contrast to
|
||||
standard numpy indexing.
|
||||
For example:
|
||||
|
||||
>>> profile_line(img, (1, 0), (1, 6)) # The final point is out of bounds
|
||||
array([1., 1., 1., 2., 2., 2., 0.])
|
||||
>>> profile_line(img, (1, 0), (1, 5)) # This accesses the full first row
|
||||
array([1., 1., 1., 2., 2., 2.])
|
||||
|
||||
For different reduce_func inputs:
|
||||
|
||||
>>> profile_line(img, (1, 0), (1, 3), linewidth=3, reduce_func=np.mean)
|
||||
array([0.66666667, 0.66666667, 0.66666667, 1.33333333])
|
||||
>>> profile_line(img, (1, 0), (1, 3), linewidth=3, reduce_func=np.max)
|
||||
array([1, 1, 1, 2])
|
||||
>>> profile_line(img, (1, 0), (1, 3), linewidth=3, reduce_func=np.sum)
|
||||
array([2, 2, 2, 4])
|
||||
|
||||
The unreduced array will be returned when `reduce_func` is None or when
|
||||
`reduce_func` acts on each pixel value individually.
|
||||
|
||||
>>> profile_line(img, (1, 2), (4, 2), linewidth=3, order=0,
|
||||
... reduce_func=None)
|
||||
array([[1, 1, 2],
|
||||
[1, 1, 2],
|
||||
[1, 1, 2],
|
||||
[0, 0, 0]])
|
||||
>>> profile_line(img, (1, 0), (1, 3), linewidth=3, reduce_func=np.sqrt)
|
||||
array([[1. , 1. , 0. ],
|
||||
[1. , 1. , 0. ],
|
||||
[1. , 1. , 0. ],
|
||||
[1.41421356, 1.41421356, 0. ]])
|
||||
"""
|
||||
|
||||
order = _validate_interpolation_order(image.dtype, order)
|
||||
|
||||
if mode is None:
|
||||
warn("Default out of bounds interpolation mode 'constant' is "
|
||||
"deprecated. In version 0.19 it will be set to 'reflect'. "
|
||||
"To avoid this warning, set `mode=` explicitly.",
|
||||
FutureWarning, stacklevel=2)
|
||||
mode = 'constant'
|
||||
|
||||
perp_lines = _line_profile_coordinates(src, dst, linewidth=linewidth)
|
||||
if image.ndim == 3:
|
||||
pixels = [ndi.map_coordinates(image[..., i], perp_lines,
|
||||
prefilter=order > 1,
|
||||
order=order, mode=mode,
|
||||
cval=cval) for i in
|
||||
range(image.shape[2])]
|
||||
pixels = np.transpose(np.asarray(pixels), (1, 2, 0))
|
||||
else:
|
||||
pixels = ndi.map_coordinates(image, perp_lines, prefilter=order > 1,
|
||||
order=order, mode=mode, cval=cval)
|
||||
# The outputted array with reduce_func=None gives an array where the
|
||||
# row values (axis=1) are flipped. Here, we make this consistent.
|
||||
pixels = np.flip(pixels, axis=1)
|
||||
|
||||
if reduce_func is None:
|
||||
intensities = pixels
|
||||
else:
|
||||
try:
|
||||
intensities = reduce_func(pixels, axis=1)
|
||||
except TypeError: # function doesn't allow axis kwarg
|
||||
intensities = np.apply_along_axis(reduce_func, arr=pixels, axis=1)
|
||||
|
||||
return intensities
|
||||
|
||||
|
||||
def _line_profile_coordinates(src, dst, linewidth=1):
|
||||
"""Return the coordinates of the profile of an image along a scan line.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
src : 2-tuple of numeric scalar (float or int)
|
||||
The start point of the scan line.
|
||||
dst : 2-tuple of numeric scalar (float or int)
|
||||
The end point of the scan line.
|
||||
linewidth : int, optional
|
||||
Width of the scan, perpendicular to the line
|
||||
|
||||
Returns
|
||||
-------
|
||||
coords : array, shape (2, N, C), float
|
||||
The coordinates of the profile along the scan line. The length of the
|
||||
profile is the ceil of the computed length of the scan line.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This is a utility method meant to be used internally by skimage functions.
|
||||
The destination point is included in the profile, in contrast to
|
||||
standard numpy indexing.
|
||||
"""
|
||||
src_row, src_col = src = np.asarray(src, dtype=float)
|
||||
dst_row, dst_col = dst = np.asarray(dst, dtype=float)
|
||||
d_row, d_col = dst - src
|
||||
theta = np.arctan2(d_row, d_col)
|
||||
|
||||
length = int(np.ceil(np.hypot(d_row, d_col) + 1))
|
||||
# we add one above because we include the last point in the profile
|
||||
# (in contrast to standard numpy indexing)
|
||||
line_col = np.linspace(src_col, dst_col, length)
|
||||
line_row = np.linspace(src_row, dst_row, length)
|
||||
|
||||
# we subtract 1 from linewidth to change from pixel-counting
|
||||
# (make this line 3 pixels wide) to point distances (the
|
||||
# distance between pixel centers)
|
||||
col_width = (linewidth - 1) * np.sin(-theta) / 2
|
||||
row_width = (linewidth - 1) * np.cos(theta) / 2
|
||||
perp_rows = np.array([np.linspace(row_i - row_width, row_i + row_width,
|
||||
linewidth) for row_i in line_row])
|
||||
perp_cols = np.array([np.linspace(col_i - col_width, col_i + col_width,
|
||||
linewidth) for col_i in line_col])
|
||||
return np.array([perp_rows, perp_cols])
|
46
venv/Lib/site-packages/skimage/measure/setup.py
Normal file
46
venv/Lib/site-packages/skimage/measure/setup.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from skimage._build import cython
|
||||
|
||||
import os
|
||||
base_path = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
|
||||
def configuration(parent_package='', top_path=None):
|
||||
from numpy.distutils.misc_util import Configuration, get_numpy_include_dirs
|
||||
|
||||
config = Configuration('measure', parent_package, top_path)
|
||||
|
||||
cython(['_ccomp.pyx',
|
||||
'_find_contours_cy.pyx',
|
||||
'_moments_cy.pyx',
|
||||
'_marching_cubes_classic_cy.pyx',
|
||||
'_marching_cubes_lewiner_cy.pyx',
|
||||
'_pnpoly.pyx'], working_path=base_path)
|
||||
|
||||
config.add_extension('_ccomp', sources=['_ccomp.c'],
|
||||
include_dirs=[get_numpy_include_dirs()])
|
||||
config.add_extension('_find_contours_cy', sources=['_find_contours_cy.c'],
|
||||
include_dirs=[get_numpy_include_dirs()])
|
||||
config.add_extension('_moments_cy', sources=['_moments_cy.c'],
|
||||
include_dirs=[get_numpy_include_dirs()])
|
||||
config.add_extension('_marching_cubes_classic_cy',
|
||||
sources=['_marching_cubes_classic_cy.c'],
|
||||
include_dirs=[get_numpy_include_dirs()])
|
||||
config.add_extension('_marching_cubes_lewiner_cy',
|
||||
sources=['_marching_cubes_lewiner_cy.c'],
|
||||
include_dirs=[get_numpy_include_dirs()])
|
||||
config.add_extension('_pnpoly', sources=['_pnpoly.c'],
|
||||
include_dirs=[get_numpy_include_dirs(), '../_shared'])
|
||||
|
||||
return config
|
||||
|
||||
if __name__ == '__main__':
|
||||
from numpy.distutils.core import setup
|
||||
setup(maintainer='scikit-image Developers',
|
||||
maintainer_email='scikit-image@python.org',
|
||||
description='Graph-based Image-processing Algorithms',
|
||||
url='https://github.com/scikit-image/scikit-image',
|
||||
license='Modified BSD',
|
||||
**(configuration(top_path='').todict())
|
||||
)
|
81
venv/Lib/site-packages/skimage/measure/simple_metrics.py
Normal file
81
venv/Lib/site-packages/skimage/measure/simple_metrics.py
Normal file
|
@ -0,0 +1,81 @@
|
|||
from warnings import warn
|
||||
from ..metrics.simple_metrics import (mean_squared_error,
|
||||
peak_signal_noise_ratio,
|
||||
normalized_root_mse)
|
||||
|
||||
__all__ = ['compare_mse',
|
||||
'compare_nrmse',
|
||||
'compare_psnr',
|
||||
]
|
||||
|
||||
|
||||
def compare_mse(im1, im2):
|
||||
warn('DEPRECATED: skimage.measure.compare_mse has been moved to '
|
||||
'skimage.metrics.mean_squared_error. It will be removed from '
|
||||
'skimage.measure in version 0.18.', stacklevel=2)
|
||||
return mean_squared_error(im1, im2)
|
||||
|
||||
|
||||
if mean_squared_error.__doc__ is not None:
|
||||
compare_mse.__doc__ = mean_squared_error.__doc__ + """
|
||||
Warns
|
||||
-----
|
||||
Deprecated:
|
||||
.. versionadded:: 0.16
|
||||
|
||||
This function is deprecated and will be removed in scikit-image 0.18.
|
||||
Please use the function named ``mean_squared_error`` from the
|
||||
``metrics`` module instead.
|
||||
|
||||
See also
|
||||
--------
|
||||
skimage.metrics.mean_squared_error
|
||||
"""
|
||||
|
||||
|
||||
def compare_nrmse(im_true, im_test, norm_type='euclidean'):
|
||||
warn('DEPRECATED: skimage.measure.compare_nrmse has been moved to '
|
||||
'skimage.metrics.normalized_root_mse. It will be removed from '
|
||||
'skimage.measure in version 0.18.', stacklevel=2)
|
||||
return normalized_root_mse(im_true, im_test, normalization=norm_type)
|
||||
|
||||
|
||||
if normalized_root_mse.__doc__ is not None:
|
||||
compare_nrmse.__doc__ = normalized_root_mse.__doc__ + """
|
||||
Warns
|
||||
-----
|
||||
Deprecated:
|
||||
.. versionadded:: 0.16
|
||||
|
||||
This function is deprecated and will be removed in scikit-image 0.18.
|
||||
Please use the function named ``normalized_root_mse`` from the
|
||||
``metrics`` module instead.
|
||||
|
||||
See also
|
||||
--------
|
||||
skimage.metrics.normalized_root_mse
|
||||
"""
|
||||
|
||||
|
||||
def compare_psnr(im_true, im_test, data_range=None):
|
||||
warn('DEPRECATED: skimage.measure.compare_psnr has been moved to '
|
||||
'skimage.metrics.peak_signal_noise_ratio. It will be removed from '
|
||||
'skimage.measure in version 0.18.', stacklevel=2)
|
||||
return peak_signal_noise_ratio(im_true, im_test, data_range=data_range)
|
||||
|
||||
|
||||
if peak_signal_noise_ratio.__doc__ is not None:
|
||||
compare_psnr.__doc__ = peak_signal_noise_ratio.__doc__ + """
|
||||
Warns
|
||||
-----
|
||||
Deprecated:
|
||||
.. versionadded:: 0.16
|
||||
|
||||
This function is deprecated and will be removed in scikit-image 0.18.
|
||||
Please use the function named ``peak_signal_noise_ratio`` from the
|
||||
``metrics`` module instead.
|
||||
|
||||
See also
|
||||
--------
|
||||
skimage.metrics.peak_signal_noise_ratio
|
||||
"""
|
9
venv/Lib/site-packages/skimage/measure/tests/__init__.py
Normal file
9
venv/Lib/site-packages/skimage/measure/tests/__init__.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
from ..._shared.testing import setup_test, teardown_test
|
||||
|
||||
|
||||
def setup():
|
||||
setup_test()
|
||||
|
||||
|
||||
def teardown():
|
||||
teardown_test()
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
117
venv/Lib/site-packages/skimage/measure/tests/test_block.py
Normal file
117
venv/Lib/site-packages/skimage/measure/tests/test_block.py
Normal file
|
@ -0,0 +1,117 @@
|
|||
import numpy as np
|
||||
from skimage.measure import block_reduce
|
||||
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import assert_equal
|
||||
|
||||
|
||||
def test_block_reduce_sum():
|
||||
image1 = np.arange(4 * 6).reshape(4, 6)
|
||||
out1 = block_reduce(image1, (2, 3))
|
||||
expected1 = np.array([[ 24, 42],
|
||||
[ 96, 114]])
|
||||
assert_equal(expected1, out1)
|
||||
|
||||
image2 = np.arange(5 * 8).reshape(5, 8)
|
||||
out2 = block_reduce(image2, (3, 3))
|
||||
expected2 = np.array([[ 81, 108, 87],
|
||||
[174, 192, 138]])
|
||||
assert_equal(expected2, out2)
|
||||
|
||||
|
||||
def test_block_reduce_mean():
|
||||
image1 = np.arange(4 * 6).reshape(4, 6)
|
||||
out1 = block_reduce(image1, (2, 3), func=np.mean)
|
||||
expected1 = np.array([[ 4., 7.],
|
||||
[ 16., 19.]])
|
||||
assert_equal(expected1, out1)
|
||||
|
||||
image2 = np.arange(5 * 8).reshape(5, 8)
|
||||
out2 = block_reduce(image2, (4, 5), func=np.mean)
|
||||
expected2 = np.array([[14. , 10.8],
|
||||
[ 8.5, 5.7]])
|
||||
assert_equal(expected2, out2)
|
||||
|
||||
|
||||
def test_block_reduce_median():
|
||||
image1 = np.arange(4 * 6).reshape(4, 6)
|
||||
out1 = block_reduce(image1, (2, 3), func=np.median)
|
||||
expected1 = np.array([[ 4., 7.],
|
||||
[ 16., 19.]])
|
||||
assert_equal(expected1, out1)
|
||||
|
||||
image2 = np.arange(5 * 8).reshape(5, 8)
|
||||
out2 = block_reduce(image2, (4, 5), func=np.median)
|
||||
expected2 = np.array([[ 14., 6.5],
|
||||
[ 0., 0. ]])
|
||||
assert_equal(expected2, out2)
|
||||
|
||||
image3 = np.array([[1, 5, 5, 5], [5, 5, 5, 1000]])
|
||||
out3 = block_reduce(image3, (2, 4), func=np.median)
|
||||
assert_equal(5, out3)
|
||||
|
||||
|
||||
def test_block_reduce_min():
|
||||
image1 = np.arange(4 * 6).reshape(4, 6)
|
||||
out1 = block_reduce(image1, (2, 3), func=np.min)
|
||||
expected1 = np.array([[ 0, 3],
|
||||
[12, 15]])
|
||||
assert_equal(expected1, out1)
|
||||
|
||||
image2 = np.arange(5 * 8).reshape(5, 8)
|
||||
out2 = block_reduce(image2, (4, 5), func=np.min)
|
||||
expected2 = np.array([[0, 0],
|
||||
[0, 0]])
|
||||
assert_equal(expected2, out2)
|
||||
|
||||
|
||||
def test_block_reduce_max():
|
||||
image1 = np.arange(4 * 6).reshape(4, 6)
|
||||
out1 = block_reduce(image1, (2, 3), func=np.max)
|
||||
expected1 = np.array([[ 8, 11],
|
||||
[20, 23]])
|
||||
assert_equal(expected1, out1)
|
||||
|
||||
image2 = np.arange(5 * 8).reshape(5, 8)
|
||||
out2 = block_reduce(image2, (4, 5), func=np.max)
|
||||
expected2 = np.array([[28, 31],
|
||||
[36, 39]])
|
||||
assert_equal(expected2, out2)
|
||||
|
||||
|
||||
def test_invalid_block_size():
|
||||
image = np.arange(4 * 6).reshape(4, 6)
|
||||
|
||||
with testing.raises(ValueError):
|
||||
block_reduce(image, [1, 2, 3])
|
||||
with testing.raises(ValueError):
|
||||
block_reduce(image, [1, 0.5])
|
||||
|
||||
|
||||
def test_func_kwargs_same_dtype():
|
||||
image = np.array([[97, 123, 173, 227],
|
||||
[217, 241, 221, 214],
|
||||
[211, 11, 170, 53],
|
||||
[214, 205, 101, 57]], dtype=np.uint8)
|
||||
|
||||
out = block_reduce(image, (2, 2), func=np.mean,
|
||||
func_kwargs={'dtype': np.uint8})
|
||||
expected = np.array([[41, 16], [32, 31]], dtype=np.uint8)
|
||||
|
||||
assert_equal(out, expected)
|
||||
assert out.dtype == expected.dtype
|
||||
|
||||
|
||||
def test_func_kwargs_different_dtype():
|
||||
image = np.array([[0.45745366, 0.67479345, 0.20949775, 0.3147348],
|
||||
[0.7209286, 0.88915504, 0.66153409, 0.07919526],
|
||||
[0.04640037, 0.54008495, 0.34664343, 0.56152301],
|
||||
[0.58085003, 0.80144708, 0.87844473, 0.29811511]],
|
||||
dtype=np.float64)
|
||||
|
||||
out = block_reduce(image, (2, 2), func=np.mean,
|
||||
func_kwargs={'dtype': np.float16})
|
||||
expected = np.array([[0.6855, 0.3164], [0.4922, 0.521]], dtype=np.float16)
|
||||
|
||||
assert_equal(out, expected)
|
||||
assert out.dtype == expected.dtype
|
16
venv/Lib/site-packages/skimage/measure/tests/test_entropy.py
Normal file
16
venv/Lib/site-packages/skimage/measure/tests/test_entropy.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
import numpy as np
|
||||
from skimage.measure import shannon_entropy
|
||||
|
||||
from skimage._shared.testing import assert_almost_equal
|
||||
|
||||
|
||||
def test_shannon_ones():
|
||||
img = np.ones((10, 10))
|
||||
res = shannon_entropy(img, base=np.e)
|
||||
assert_almost_equal(res, 0.0)
|
||||
|
||||
|
||||
def test_shannon_all_unique():
|
||||
img = np.arange(64)
|
||||
res = shannon_entropy(img, base=2)
|
||||
assert_almost_equal(res, np.log(64) / np.log(2))
|
|
@ -0,0 +1,132 @@
|
|||
import numpy as np
|
||||
from skimage.measure import find_contours
|
||||
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import assert_array_equal
|
||||
from pytest import raises
|
||||
|
||||
|
||||
a = np.ones((8, 8), dtype=np.float32)
|
||||
a[1:-1, 1] = 0
|
||||
a[1, 1:-1] = 0
|
||||
|
||||
x, y = np.mgrid[-1:1:5j, -1:1:5j]
|
||||
r = np.sqrt(x**2 + y**2)
|
||||
|
||||
|
||||
def test_binary():
|
||||
ref = [[6. , 1.5],
|
||||
[5. , 1.5],
|
||||
[4. , 1.5],
|
||||
[3. , 1.5],
|
||||
[2. , 1.5],
|
||||
[1.5, 2. ],
|
||||
[1.5, 3. ],
|
||||
[1.5, 4. ],
|
||||
[1.5, 5. ],
|
||||
[1.5, 6. ],
|
||||
[1. , 6.5],
|
||||
[0.5, 6. ],
|
||||
[0.5, 5. ],
|
||||
[0.5, 4. ],
|
||||
[0.5, 3. ],
|
||||
[0.5, 2. ],
|
||||
[0.5, 1. ],
|
||||
[1. , 0.5],
|
||||
[2. , 0.5],
|
||||
[3. , 0.5],
|
||||
[4. , 0.5],
|
||||
[5. , 0.5],
|
||||
[6. , 0.5],
|
||||
[6.5, 1. ],
|
||||
[6. , 1.5]]
|
||||
|
||||
contours = find_contours(a, 0.5, positive_orientation='high')
|
||||
assert len(contours) == 1
|
||||
assert_array_equal(contours[0][::-1], ref)
|
||||
|
||||
|
||||
# target contour for mask tests
|
||||
mask_contour = [
|
||||
[6. , 0.5],
|
||||
[5. , 0.5],
|
||||
[4. , 0.5],
|
||||
[3. , 0.5],
|
||||
[2. , 0.5],
|
||||
[1. , 0.5],
|
||||
[0.5, 1. ],
|
||||
[0.5, 2. ],
|
||||
[0.5, 3. ],
|
||||
[0.5, 4. ],
|
||||
[0.5, 5. ],
|
||||
[0.5, 6. ],
|
||||
[1. , 6.5],
|
||||
[1.5, 6. ],
|
||||
[1.5, 5. ],
|
||||
[1.5, 4. ],
|
||||
[1.5, 3. ],
|
||||
[1.5, 2. ],
|
||||
[2. , 1.5],
|
||||
[3. , 1.5],
|
||||
[4. , 1.5],
|
||||
[5. , 1.5],
|
||||
[6. , 1.5],
|
||||
]
|
||||
|
||||
mask = np.ones((8, 8), dtype=bool)
|
||||
# Some missing data that should result in a hole in the contour:
|
||||
mask[7, 0:3] = False
|
||||
|
||||
|
||||
def test_nodata():
|
||||
# Test missing data via NaNs in input array
|
||||
b = np.copy(a)
|
||||
b[~mask] = np.nan
|
||||
contours = find_contours(b, 0.5, positive_orientation='high')
|
||||
assert len(contours) == 1
|
||||
assert_array_equal(contours[0], mask_contour)
|
||||
|
||||
|
||||
def test_mask():
|
||||
# Test missing data via explicit masking
|
||||
contours = find_contours(a, 0.5, positive_orientation='high', mask=mask)
|
||||
assert len(contours) == 1
|
||||
assert_array_equal(contours[0], mask_contour)
|
||||
|
||||
|
||||
def test_mask_shape():
|
||||
bad_mask = np.ones((8, 7), dtype=bool)
|
||||
with raises(ValueError, match='shape'):
|
||||
find_contours(a, 0, mask=bad_mask)
|
||||
|
||||
|
||||
def test_mask_dtype():
|
||||
bad_mask = np.ones((8,8), dtype=np.uint8)
|
||||
with raises(TypeError, match='binary'):
|
||||
find_contours(a, 0, mask=bad_mask)
|
||||
|
||||
|
||||
def test_float():
|
||||
contours = find_contours(r, 0.5)
|
||||
assert len(contours) == 1
|
||||
assert_array_equal(contours[0],
|
||||
[[ 2., 3.],
|
||||
[ 1., 2.],
|
||||
[ 2., 1.],
|
||||
[ 3., 2.],
|
||||
[ 2., 3.]])
|
||||
|
||||
|
||||
def test_memory_order():
|
||||
contours = find_contours(np.ascontiguousarray(r), 0.5)
|
||||
assert len(contours) == 1
|
||||
|
||||
contours = find_contours(np.asfortranarray(r), 0.5)
|
||||
assert len(contours) == 1
|
||||
|
||||
|
||||
def test_invalid_input():
|
||||
with testing.raises(ValueError):
|
||||
find_contours(r, 0.5, 'foo', 'bar')
|
||||
with testing.raises(ValueError):
|
||||
find_contours(r[..., None], 0.5)
|
388
venv/Lib/site-packages/skimage/measure/tests/test_fit.py
Normal file
388
venv/Lib/site-packages/skimage/measure/tests/test_fit.py
Normal file
|
@ -0,0 +1,388 @@
|
|||
import numpy as np
|
||||
from skimage.measure import LineModelND, CircleModel, EllipseModel, ransac
|
||||
from skimage.transform import AffineTransform
|
||||
from skimage.measure.fit import _dynamic_max_trials
|
||||
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import (assert_equal, assert_almost_equal,
|
||||
assert_array_less, xfail, arch32)
|
||||
|
||||
|
||||
def test_line_model_invalid_input():
|
||||
with testing.raises(ValueError):
|
||||
LineModelND().estimate(np.empty((1, 3)))
|
||||
|
||||
|
||||
def test_line_model_predict():
|
||||
model = LineModelND()
|
||||
model.params = ((0, 0), (1, 1))
|
||||
x = np.arange(-10, 10)
|
||||
y = model.predict_y(x)
|
||||
assert_almost_equal(x, model.predict_x(y))
|
||||
|
||||
|
||||
def test_line_model_nd_invalid_input():
|
||||
with testing.raises(ValueError):
|
||||
LineModelND().predict_x(np.zeros(1))
|
||||
|
||||
with testing.raises(ValueError):
|
||||
LineModelND().predict_y(np.zeros(1))
|
||||
|
||||
with testing.raises(ValueError):
|
||||
LineModelND().predict_x(np.zeros(1), np.zeros(1))
|
||||
|
||||
with testing.raises(ValueError):
|
||||
LineModelND().predict_y(np.zeros(1))
|
||||
|
||||
with testing.raises(ValueError):
|
||||
LineModelND().predict_y(np.zeros(1), np.zeros(1))
|
||||
|
||||
with testing.raises(ValueError):
|
||||
LineModelND().estimate(np.empty((1, 3)))
|
||||
|
||||
with testing.raises(ValueError):
|
||||
LineModelND().residuals(np.empty((1, 3)))
|
||||
|
||||
data = np.empty((1, 2))
|
||||
with testing.raises(ValueError):
|
||||
LineModelND().estimate(data)
|
||||
|
||||
|
||||
def test_line_model_nd_predict():
|
||||
model = LineModelND()
|
||||
model.params = (np.array([0, 0]), np.array([0.2, 0.8]))
|
||||
x = np.arange(-10, 10)
|
||||
y = model.predict_y(x)
|
||||
assert_almost_equal(x, model.predict_x(y))
|
||||
|
||||
|
||||
def test_line_model_nd_estimate():
|
||||
# generate original data without noise
|
||||
model0 = LineModelND()
|
||||
model0.params = (np.array([0, 0, 0], dtype='float'),
|
||||
np.array([1, 1, 1], dtype='float')/np.sqrt(3))
|
||||
# we scale the unit vector with a factor 10 when generating points on the
|
||||
# line in order to compensate for the scale of the random noise
|
||||
data0 = (model0.params[0] +
|
||||
10 * np.arange(-100, 100)[..., np.newaxis] * model0.params[1])
|
||||
|
||||
# add gaussian noise to data
|
||||
random_state = np.random.RandomState(1234)
|
||||
data = data0 + random_state.normal(size=data0.shape)
|
||||
|
||||
# estimate parameters of noisy data
|
||||
model_est = LineModelND()
|
||||
model_est.estimate(data)
|
||||
# assert_almost_equal(model_est.residuals(data0), np.zeros(len(data)), 1)
|
||||
|
||||
# test whether estimated parameters are correct
|
||||
# we use the following geometric property: two aligned vectors have
|
||||
# a cross-product equal to zero
|
||||
# test if direction vectors are aligned
|
||||
assert_almost_equal(np.linalg.norm(np.cross(model0.params[1],
|
||||
model_est.params[1])), 0, 1)
|
||||
# test if origins are aligned with the direction
|
||||
a = model_est.params[0] - model0.params[0]
|
||||
if np.linalg.norm(a) > 0:
|
||||
a /= np.linalg.norm(a)
|
||||
assert_almost_equal(np.linalg.norm(np.cross(model0.params[1], a)), 0, 1)
|
||||
|
||||
|
||||
def test_line_model_nd_residuals():
|
||||
model = LineModelND()
|
||||
model.params = (np.array([0, 0, 0]), np.array([0, 0, 1]))
|
||||
assert_equal(abs(model.residuals(np.array([[0, 0, 0]]))), 0)
|
||||
assert_equal(abs(model.residuals(np.array([[0, 0, 1]]))), 0)
|
||||
assert_equal(abs(model.residuals(np.array([[10, 0, 0]]))), 10)
|
||||
# test params argument in model.rediduals
|
||||
data = np.array([[10, 0, 0]])
|
||||
params = (np.array([0, 0, 0]), np.array([2, 0, 0]))
|
||||
assert_equal(abs(model.residuals(data, params=params)), 30)
|
||||
|
||||
|
||||
def test_line_modelND_under_determined():
|
||||
data = np.empty((1, 3))
|
||||
with testing.raises(ValueError):
|
||||
LineModelND().estimate(data)
|
||||
|
||||
|
||||
def test_circle_model_invalid_input():
|
||||
with testing.raises(ValueError):
|
||||
CircleModel().estimate(np.empty((5, 3)))
|
||||
|
||||
|
||||
def test_circle_model_predict():
|
||||
model = CircleModel()
|
||||
r = 5
|
||||
model.params = (0, 0, r)
|
||||
t = np.arange(0, 2 * np.pi, np.pi / 2)
|
||||
|
||||
xy = np.array(((5, 0), (0, 5), (-5, 0), (0, -5)))
|
||||
assert_almost_equal(xy, model.predict_xy(t))
|
||||
|
||||
|
||||
def test_circle_model_estimate():
|
||||
# generate original data without noise
|
||||
model0 = CircleModel()
|
||||
model0.params = (10, 12, 3)
|
||||
t = np.linspace(0, 2 * np.pi, 1000)
|
||||
data0 = model0.predict_xy(t)
|
||||
|
||||
# add gaussian noise to data
|
||||
random_state = np.random.RandomState(1234)
|
||||
data = data0 + random_state.normal(size=data0.shape)
|
||||
|
||||
# estimate parameters of noisy data
|
||||
model_est = CircleModel()
|
||||
model_est.estimate(data)
|
||||
|
||||
# test whether estimated parameters almost equal original parameters
|
||||
assert_almost_equal(model0.params, model_est.params, 0)
|
||||
|
||||
|
||||
def test_circle_model_residuals():
|
||||
model = CircleModel()
|
||||
model.params = (0, 0, 5)
|
||||
assert_almost_equal(abs(model.residuals(np.array([[5, 0]]))), 0)
|
||||
assert_almost_equal(abs(model.residuals(np.array([[6, 6]]))),
|
||||
np.sqrt(2 * 6**2) - 5)
|
||||
assert_almost_equal(abs(model.residuals(np.array([[10, 0]]))), 5)
|
||||
|
||||
|
||||
def test_ellipse_model_invalid_input():
|
||||
with testing.raises(ValueError):
|
||||
EllipseModel().estimate(np.empty((5, 3)))
|
||||
|
||||
|
||||
def test_ellipse_model_predict():
|
||||
model = EllipseModel()
|
||||
model.params = (0, 0, 5, 10, 0)
|
||||
t = np.arange(0, 2 * np.pi, np.pi / 2)
|
||||
|
||||
xy = np.array(((5, 0), (0, 10), (-5, 0), (0, -10)))
|
||||
assert_almost_equal(xy, model.predict_xy(t))
|
||||
|
||||
|
||||
def test_ellipse_model_estimate():
|
||||
for angle in range(0, 180, 15):
|
||||
rad = np.deg2rad(angle)
|
||||
# generate original data without noise
|
||||
model0 = EllipseModel()
|
||||
model0.params = (10, 20, 15, 25, rad)
|
||||
t = np.linspace(0, 2 * np.pi, 100)
|
||||
data0 = model0.predict_xy(t)
|
||||
|
||||
# add gaussian noise to data
|
||||
random_state = np.random.RandomState(1234)
|
||||
data = data0 + random_state.normal(size=data0.shape)
|
||||
|
||||
# estimate parameters of noisy data
|
||||
model_est = EllipseModel()
|
||||
model_est.estimate(data)
|
||||
|
||||
# test whether estimated parameters almost equal original parameters
|
||||
assert_almost_equal(model0.params[:2], model_est.params[:2], 0)
|
||||
res = model_est.residuals(data0)
|
||||
assert_array_less(res, np.ones(res.shape))
|
||||
|
||||
|
||||
def test_ellipse_model_estimate_from_data():
|
||||
data = np.array([
|
||||
[264, 854], [265, 875], [268, 863], [270, 857], [275, 905], [285, 915],
|
||||
[305, 925], [324, 934], [335, 764], [336, 915], [345, 925], [345, 945],
|
||||
[354, 933], [355, 745], [364, 936], [365, 754], [375, 745], [375, 735],
|
||||
[385, 736], [395, 735], [394, 935], [405, 727], [415, 736], [415, 727],
|
||||
[425, 727], [426, 929], [435, 735], [444, 933], [445, 735], [455, 724],
|
||||
[465, 934], [465, 735], [475, 908], [475, 726], [485, 753], [485, 728],
|
||||
[492, 762], [495, 745], [491, 910], [493, 909], [499, 904], [505, 905],
|
||||
[504, 747], [515, 743], [516, 752], [524, 855], [525, 844], [525, 885],
|
||||
[533, 845], [533, 873], [535, 883], [545, 874], [543, 864], [553, 865],
|
||||
[553, 845], [554, 825], [554, 835], [563, 845], [565, 826], [563, 855],
|
||||
[563, 795], [565, 735], [573, 778], [572, 815], [574, 804], [575, 665],
|
||||
[575, 685], [574, 705], [574, 745], [575, 875], [572, 732], [582, 795],
|
||||
[579, 709], [583, 805], [583, 854], [586, 755], [584, 824], [585, 655],
|
||||
[581, 718], [586, 844], [585, 915], [587, 905], [594, 824], [593, 855],
|
||||
[590, 891], [594, 776], [596, 767], [593, 763], [603, 785], [604, 775],
|
||||
[603, 885], [605, 753], [605, 655], [606, 935], [603, 761], [613, 802],
|
||||
[613, 945], [613, 965], [615, 693], [617, 665], [623, 962], [624, 972],
|
||||
[625, 995], [633, 673], [633, 965], [633, 683], [633, 692], [633, 954],
|
||||
[634, 1016], [635, 664], [641, 804], [637, 999], [641, 956], [643, 946],
|
||||
[643, 926], [644, 975], [643, 655], [646, 705], [651, 664], [651, 984],
|
||||
[647, 665], [651, 715], [651, 725], [651, 734], [647, 809], [651, 825],
|
||||
[651, 873], [647, 900], [652, 917], [651, 944], [652, 742], [648, 811],
|
||||
[651, 994], [652, 783], [650, 911], [654, 879]])
|
||||
|
||||
# estimate parameters of real data
|
||||
model = EllipseModel()
|
||||
model.estimate(data)
|
||||
|
||||
# test whether estimated parameters are smaller then 1000, so means stable
|
||||
assert_array_less(np.abs(model.params[:4]), np.array([2e3] * 4))
|
||||
|
||||
|
||||
@xfail(condition=arch32,
|
||||
reason=('Known test failure on 32-bit platforms. See links for '
|
||||
'details: '
|
||||
'https://github.com/scikit-image/scikit-image/issues/3091 '
|
||||
'https://github.com/scikit-image/scikit-image/issues/2670'))
|
||||
def test_ellipse_model_estimate_failers():
|
||||
# estimate parameters of real data
|
||||
model = EllipseModel()
|
||||
assert not model.estimate(np.ones((5, 2)))
|
||||
assert not model.estimate(np.array([[50, 80], [51, 81], [52, 80]]))
|
||||
|
||||
|
||||
def test_ellipse_model_residuals():
|
||||
model = EllipseModel()
|
||||
# vertical line through origin
|
||||
model.params = (0, 0, 10, 5, 0)
|
||||
assert_almost_equal(abs(model.residuals(np.array([[10, 0]]))), 0)
|
||||
assert_almost_equal(abs(model.residuals(np.array([[0, 5]]))), 0)
|
||||
assert_almost_equal(abs(model.residuals(np.array([[0, 10]]))), 5)
|
||||
|
||||
|
||||
def test_ransac_shape():
|
||||
# generate original data without noise
|
||||
model0 = CircleModel()
|
||||
model0.params = (10, 12, 3)
|
||||
t = np.linspace(0, 2 * np.pi, 1000)
|
||||
data0 = model0.predict_xy(t)
|
||||
|
||||
# add some faulty data
|
||||
outliers = (10, 30, 200)
|
||||
data0[outliers[0], :] = (1000, 1000)
|
||||
data0[outliers[1], :] = (-50, 50)
|
||||
data0[outliers[2], :] = (-100, -10)
|
||||
|
||||
# estimate parameters of corrupted data
|
||||
model_est, inliers = ransac(data0, CircleModel, 3, 5, random_state=1)
|
||||
|
||||
# test whether estimated parameters equal original parameters
|
||||
assert_almost_equal(model0.params, model_est.params)
|
||||
for outlier in outliers:
|
||||
assert outlier not in inliers
|
||||
|
||||
|
||||
def test_ransac_geometric():
|
||||
random_state = np.random.RandomState(1)
|
||||
|
||||
# generate original data without noise
|
||||
src = 100 * random_state.random_sample((50, 2))
|
||||
model0 = AffineTransform(scale=(0.5, 0.3), rotation=1,
|
||||
translation=(10, 20))
|
||||
dst = model0(src)
|
||||
|
||||
# add some faulty data
|
||||
outliers = (0, 5, 20)
|
||||
dst[outliers[0]] = (10000, 10000)
|
||||
dst[outliers[1]] = (-100, 100)
|
||||
dst[outliers[2]] = (50, 50)
|
||||
|
||||
# estimate parameters of corrupted data
|
||||
model_est, inliers = ransac((src, dst), AffineTransform, 2, 20,
|
||||
random_state=random_state)
|
||||
|
||||
# test whether estimated parameters equal original parameters
|
||||
assert_almost_equal(model0.params, model_est.params)
|
||||
assert np.all(np.nonzero(inliers == False)[0] == outliers)
|
||||
|
||||
|
||||
def test_ransac_is_data_valid():
|
||||
def is_data_valid(data):
|
||||
return data.shape[0] > 2
|
||||
model, inliers = ransac(np.empty((10, 2)), LineModelND, 2, np.inf,
|
||||
is_data_valid=is_data_valid, random_state=1)
|
||||
assert_equal(model, None)
|
||||
assert_equal(inliers, None)
|
||||
|
||||
|
||||
def test_ransac_is_model_valid():
|
||||
def is_model_valid(model, data):
|
||||
return False
|
||||
model, inliers = ransac(np.empty((10, 2)), LineModelND, 2, np.inf,
|
||||
is_model_valid=is_model_valid, random_state=1)
|
||||
assert_equal(model, None)
|
||||
assert_equal(inliers, None)
|
||||
|
||||
|
||||
def test_ransac_dynamic_max_trials():
|
||||
# Numbers hand-calculated and confirmed on page 119 (Table 4.3) in
|
||||
# Hartley, R.~I. and Zisserman, A., 2004,
|
||||
# Multiple View Geometry in Computer Vision, Second Edition,
|
||||
# Cambridge University Press, ISBN: 0521540518
|
||||
|
||||
# e = 0%, min_samples = X
|
||||
assert_equal(_dynamic_max_trials(100, 100, 2, 0.99), 1)
|
||||
|
||||
# e = 5%, min_samples = 2
|
||||
assert_equal(_dynamic_max_trials(95, 100, 2, 0.99), 2)
|
||||
# e = 10%, min_samples = 2
|
||||
assert_equal(_dynamic_max_trials(90, 100, 2, 0.99), 3)
|
||||
# e = 30%, min_samples = 2
|
||||
assert_equal(_dynamic_max_trials(70, 100, 2, 0.99), 7)
|
||||
# e = 50%, min_samples = 2
|
||||
assert_equal(_dynamic_max_trials(50, 100, 2, 0.99), 17)
|
||||
|
||||
# e = 5%, min_samples = 8
|
||||
assert_equal(_dynamic_max_trials(95, 100, 8, 0.99), 5)
|
||||
# e = 10%, min_samples = 8
|
||||
assert_equal(_dynamic_max_trials(90, 100, 8, 0.99), 9)
|
||||
# e = 30%, min_samples = 8
|
||||
assert_equal(_dynamic_max_trials(70, 100, 8, 0.99), 78)
|
||||
# e = 50%, min_samples = 8
|
||||
assert_equal(_dynamic_max_trials(50, 100, 8, 0.99), 1177)
|
||||
|
||||
# e = 0%, min_samples = 5
|
||||
assert_equal(_dynamic_max_trials(1, 100, 5, 0), 0)
|
||||
assert_equal(_dynamic_max_trials(1, 100, 5, 1), np.inf)
|
||||
|
||||
|
||||
def test_ransac_invalid_input():
|
||||
# `residual_threshold` must be greater than zero
|
||||
with testing.raises(ValueError):
|
||||
ransac(np.zeros((10, 2)), None, min_samples=2,
|
||||
residual_threshold=-0.5)
|
||||
# "`max_trials` must be greater than zero"
|
||||
with testing.raises(ValueError):
|
||||
ransac(np.zeros((10, 2)), None, min_samples=2,
|
||||
residual_threshold=0, max_trials=-1)
|
||||
# `stop_probability` must be in range (0, 1)
|
||||
with testing.raises(ValueError):
|
||||
ransac(np.zeros((10, 2)), None, min_samples=2,
|
||||
residual_threshold=0, stop_probability=-1)
|
||||
# `stop_probability` must be in range (0, 1)
|
||||
with testing.raises(ValueError):
|
||||
ransac(np.zeros((10, 2)), None, min_samples=2,
|
||||
residual_threshold=0, stop_probability=1.01)
|
||||
# `min_samples` as ratio must be in range (0, nb)
|
||||
with testing.raises(ValueError):
|
||||
ransac(np.zeros((10, 2)), None, min_samples=0,
|
||||
residual_threshold=0)
|
||||
# `min_samples` as ratio must be in range (0, nb)
|
||||
with testing.raises(ValueError):
|
||||
ransac(np.zeros((10, 2)), None, min_samples=10,
|
||||
residual_threshold=0)
|
||||
# `min_samples` must be greater than zero
|
||||
with testing.raises(ValueError):
|
||||
ransac(np.zeros((10, 2)), None, min_samples=-1,
|
||||
residual_threshold=0)
|
||||
|
||||
|
||||
def test_ransac_sample_duplicates():
|
||||
class DummyModel(object):
|
||||
|
||||
"""Dummy model to check for duplicates."""
|
||||
|
||||
def estimate(self, data):
|
||||
# Assert that all data points are unique.
|
||||
assert_equal(np.unique(data).size, data.size)
|
||||
return True
|
||||
|
||||
def residuals(self, data):
|
||||
return np.ones(len(data), dtype=np.double)
|
||||
|
||||
# Create dataset with four unique points. Force 10 iterations
|
||||
# and check that there are no duplicated data points.
|
||||
data = np.arange(4)
|
||||
ransac(data, DummyModel, min_samples=3, residual_threshold=0.0,
|
||||
max_trials=10)
|
|
@ -0,0 +1,180 @@
|
|||
import numpy as np
|
||||
from skimage.draw import ellipsoid, ellipsoid_stats
|
||||
from skimage.measure import marching_cubes, mesh_surface_area
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import assert_array_equal
|
||||
import pytest
|
||||
|
||||
def test_marching_cubes_isotropic():
|
||||
ellipsoid_isotropic = ellipsoid(6, 10, 16, levelset=True)
|
||||
_, surf = ellipsoid_stats(6, 10, 16)
|
||||
|
||||
# Classic
|
||||
verts, faces = marching_cubes(ellipsoid_isotropic, 0., method='_lorensen')
|
||||
surf_calc = mesh_surface_area(verts, faces)
|
||||
# Test within 1% tolerance for isotropic. Will always underestimate.
|
||||
assert surf > surf_calc and surf_calc > surf * 0.99
|
||||
|
||||
# Lewiner
|
||||
verts, faces = marching_cubes(ellipsoid_isotropic, 0.)[:2]
|
||||
surf_calc = mesh_surface_area(verts, faces)
|
||||
# Test within 1% tolerance for isotropic. Will always underestimate.
|
||||
assert surf > surf_calc and surf_calc > surf * 0.99
|
||||
|
||||
|
||||
def test_marching_cubes_anisotropic():
|
||||
# test spacing as numpy array (and not just tuple)
|
||||
spacing = np.array([1., 10 / 6., 16 / 6.])
|
||||
ellipsoid_anisotropic = ellipsoid(6, 10, 16, spacing=spacing,
|
||||
levelset=True)
|
||||
_, surf = ellipsoid_stats(6, 10, 16)
|
||||
|
||||
# Classic
|
||||
verts, faces = marching_cubes(ellipsoid_anisotropic, 0.,
|
||||
spacing=spacing, method='_lorensen')
|
||||
surf_calc = mesh_surface_area(verts, faces)
|
||||
# Test within 1.5% tolerance for anisotropic. Will always underestimate.
|
||||
assert surf > surf_calc and surf_calc > surf * 0.985
|
||||
|
||||
# Lewiner
|
||||
verts, faces = marching_cubes(ellipsoid_anisotropic, 0.,
|
||||
spacing=spacing)[:2]
|
||||
surf_calc = mesh_surface_area(verts, faces)
|
||||
# Test within 1.5% tolerance for anisotropic. Will always underestimate.
|
||||
assert surf > surf_calc and surf_calc > surf * 0.985
|
||||
|
||||
# Test marching cube with mask
|
||||
with pytest.raises(ValueError):
|
||||
verts, faces = marching_cubes(
|
||||
ellipsoid_anisotropic, 0., spacing=spacing,
|
||||
mask=np.array([]))[:2]
|
||||
|
||||
# Test spacing together with allow_degenerate=False
|
||||
marching_cubes(ellipsoid_anisotropic, 0, spacing=spacing,
|
||||
allow_degenerate=False)
|
||||
|
||||
|
||||
def test_invalid_input():
|
||||
# Classic
|
||||
with testing.raises(ValueError):
|
||||
marching_cubes(np.zeros((2, 2, 1)), 0, method='_lorensen')
|
||||
with testing.raises(ValueError):
|
||||
marching_cubes(np.zeros((2, 2, 1)), 1, method='_lorensen')
|
||||
with testing.raises(ValueError):
|
||||
marching_cubes(np.ones((3, 3, 3)), 1, spacing=(1, 2), method='_lorensen')
|
||||
with testing.raises(ValueError):
|
||||
marching_cubes(np.zeros((20, 20)), 0, method='_lorensen')
|
||||
|
||||
# Lewiner
|
||||
with testing.raises(ValueError):
|
||||
marching_cubes(np.zeros((2, 2, 1)), 0)
|
||||
with testing.raises(ValueError):
|
||||
marching_cubes(np.zeros((2, 2, 1)), 1)
|
||||
with testing.raises(ValueError):
|
||||
marching_cubes(np.ones((3, 3, 3)), 1, spacing=(1, 2))
|
||||
with testing.raises(ValueError):
|
||||
marching_cubes(np.zeros((20, 20)), 0)
|
||||
|
||||
|
||||
def test_both_algs_same_result_ellipse():
|
||||
# Performing this test on data that does not have ambiguities
|
||||
|
||||
sphere_small = ellipsoid(1, 1, 1, levelset=True)
|
||||
|
||||
vertices1, faces1 = marching_cubes(sphere_small, 0, method='_lorensen')[:2]
|
||||
vertices2, faces2 = marching_cubes(sphere_small, 0,
|
||||
allow_degenerate=False)[:2]
|
||||
vertices3, faces3 = marching_cubes(sphere_small, 0,
|
||||
allow_degenerate=False,
|
||||
method='lorensen')[:2]
|
||||
|
||||
# Order is different, best we can do is test equal shape and same
|
||||
# vertices present
|
||||
assert _same_mesh(vertices1, faces1, vertices2, faces2)
|
||||
assert _same_mesh(vertices1, faces1, vertices3, faces3)
|
||||
|
||||
|
||||
def _same_mesh(vertices1, faces1, vertices2, faces2, tol=1e-10):
|
||||
""" Compare two meshes, using a certain tolerance and invariant to
|
||||
the order of the faces.
|
||||
"""
|
||||
# Unwind vertices
|
||||
triangles1 = vertices1[np.array(faces1)]
|
||||
triangles2 = vertices2[np.array(faces2)]
|
||||
# Sort vertices within each triangle
|
||||
triang1 = [np.concatenate(sorted(t, key=lambda x:tuple(x)))
|
||||
for t in triangles1]
|
||||
triang2 = [np.concatenate(sorted(t, key=lambda x:tuple(x)))
|
||||
for t in triangles2]
|
||||
# Sort the resulting 9-element "tuples"
|
||||
triang1 = np.array(sorted([tuple(x) for x in triang1]))
|
||||
triang2 = np.array(sorted([tuple(x) for x in triang2]))
|
||||
return (triang1.shape == triang2.shape and
|
||||
np.allclose(triang1, triang2, 0, tol))
|
||||
|
||||
|
||||
def test_both_algs_same_result_donut():
|
||||
# Performing this test on data that does not have ambiguities
|
||||
n = 48
|
||||
a, b = 2.5/n, -1.25
|
||||
|
||||
vol = np.empty((n, n, n), 'float32')
|
||||
for iz in range(vol.shape[0]):
|
||||
for iy in range(vol.shape[1]):
|
||||
for ix in range(vol.shape[2]):
|
||||
# Double-torii formula by Thomas Lewiner
|
||||
z, y, x = float(iz)*a+b, float(iy)*a+b, float(ix)*a+b
|
||||
vol[iz,iy,ix] = ( (
|
||||
(8*x)**2 + (8*y-2)**2 + (8*z)**2 + 16 - 1.85*1.85 ) * ( (8*x)**2 +
|
||||
(8*y-2)**2 + (8*z)**2 + 16 - 1.85*1.85 ) - 64 * ( (8*x)**2 + (8*y-2)**2 )
|
||||
) * ( ( (8*x)**2 + ((8*y-2)+4)*((8*y-2)+4) + (8*z)**2 + 16 - 1.85*1.85 )
|
||||
* ( (8*x)**2 + ((8*y-2)+4)*((8*y-2)+4) + (8*z)**2 + 16 - 1.85*1.85 ) -
|
||||
64 * ( ((8*y-2)+4)*((8*y-2)+4) + (8*z)**2
|
||||
) ) + 1025
|
||||
|
||||
vertices1, faces1 = marching_cubes(vol, 0, method='_lorensen')[:2]
|
||||
vertices2, faces2 = marching_cubes(vol, 0)[:2]
|
||||
vertices3, faces3 = marching_cubes(vol, 0, method='lorensen')[:2]
|
||||
|
||||
# Old and new alg are different
|
||||
assert not _same_mesh(vertices1, faces1, vertices2, faces2)
|
||||
# New classic and new Lewiner are different
|
||||
assert not _same_mesh(vertices2, faces2, vertices3, faces3)
|
||||
# Would have been nice if old and new classic would have been the same
|
||||
# assert _same_mesh(vertices1, faces1, vertices3, faces3, 5)
|
||||
|
||||
|
||||
def test_masked_marching_cubes():
|
||||
|
||||
ellipsoid_scalar = ellipsoid(6, 10, 16, levelset=True)
|
||||
mask = np.ones_like(ellipsoid_scalar, dtype=bool)
|
||||
mask[:10, :, :] = False
|
||||
mask[:, :, 20:] = False
|
||||
ver, faces, _, _ = marching_cubes(ellipsoid_scalar, 0, mask=mask)
|
||||
area = mesh_surface_area(ver, faces)
|
||||
|
||||
np.testing.assert_allclose(area, 299.56878662109375, rtol=.01)
|
||||
|
||||
|
||||
def test_masked_marching_cubes_empty():
|
||||
ellipsoid_scalar = ellipsoid(6, 10, 16, levelset=True)
|
||||
mask = np.array([])
|
||||
with pytest.raises(ValueError):
|
||||
_ = marching_cubes(ellipsoid_scalar, 0, mask=mask)
|
||||
|
||||
|
||||
def test_masked_marching_cubes_old_lewiner():
|
||||
ellipsoid_scalar = ellipsoid(6, 10, 16, levelset=True)
|
||||
mask = np.array([])
|
||||
with pytest.raises(NotImplementedError):
|
||||
_ = marching_cubes(ellipsoid_scalar, 0, mask=mask, method='_lorensen')
|
||||
|
||||
|
||||
def test_masked_marching_cubes_all_true():
|
||||
ellipsoid_scalar = ellipsoid(6, 10, 16, levelset=True)
|
||||
mask = np.ones_like(ellipsoid_scalar, dtype=bool)
|
||||
ver_m, faces_m, _, _ = marching_cubes(ellipsoid_scalar, 0, mask=mask)
|
||||
ver, faces, _, _ = marching_cubes(ellipsoid_scalar, 0, mask=mask)
|
||||
np.testing.assert_allclose(ver_m, ver, rtol=.00001)
|
||||
np.testing.assert_allclose(faces_m, faces, rtol=.00001)
|
||||
|
187
venv/Lib/site-packages/skimage/measure/tests/test_moments.py
Normal file
187
venv/Lib/site-packages/skimage/measure/tests/test_moments.py
Normal file
|
@ -0,0 +1,187 @@
|
|||
import numpy as np
|
||||
from scipy import ndimage as ndi
|
||||
from skimage import draw
|
||||
from skimage.measure import (moments, moments_central, moments_coords,
|
||||
moments_coords_central, moments_normalized,
|
||||
moments_hu, centroid, inertia_tensor,
|
||||
inertia_tensor_eigvals)
|
||||
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import (assert_equal, assert_almost_equal,
|
||||
assert_allclose)
|
||||
from skimage._shared._warnings import expected_warnings
|
||||
|
||||
|
||||
def test_moments():
|
||||
image = np.zeros((20, 20), dtype=np.double)
|
||||
image[14, 14] = 1
|
||||
image[15, 15] = 1
|
||||
image[14, 15] = 0.5
|
||||
image[15, 14] = 0.5
|
||||
m = moments(image)
|
||||
assert_equal(m[0, 0], 3)
|
||||
assert_almost_equal(m[1, 0] / m[0, 0], 14.5)
|
||||
assert_almost_equal(m[0, 1] / m[0, 0], 14.5)
|
||||
|
||||
|
||||
def test_moments_central():
|
||||
image = np.zeros((20, 20), dtype=np.double)
|
||||
image[14, 14] = 1
|
||||
image[15, 15] = 1
|
||||
image[14, 15] = 0.5
|
||||
image[15, 14] = 0.5
|
||||
mu = moments_central(image, (14.5, 14.5))
|
||||
|
||||
# check for proper centroid computation
|
||||
mu_calc_centroid = moments_central(image)
|
||||
assert_equal(mu, mu_calc_centroid)
|
||||
|
||||
# shift image by dx=2, dy=2
|
||||
image2 = np.zeros((20, 20), dtype=np.double)
|
||||
image2[16, 16] = 1
|
||||
image2[17, 17] = 1
|
||||
image2[16, 17] = 0.5
|
||||
image2[17, 16] = 0.5
|
||||
mu2 = moments_central(image2, (14.5 + 2, 14.5 + 2))
|
||||
# central moments must be translation invariant
|
||||
assert_equal(mu, mu2)
|
||||
|
||||
|
||||
def test_moments_coords():
|
||||
image = np.zeros((20, 20), dtype=np.double)
|
||||
image[13:17, 13:17] = 1
|
||||
mu_image = moments(image)
|
||||
|
||||
coords = np.array([[r, c] for r in range(13, 17)
|
||||
for c in range(13, 17)], dtype=np.double)
|
||||
mu_coords = moments_coords(coords)
|
||||
assert_almost_equal(mu_coords, mu_image)
|
||||
|
||||
|
||||
def test_moments_central_coords():
|
||||
image = np.zeros((20, 20), dtype=np.double)
|
||||
image[13:17, 13:17] = 1
|
||||
mu_image = moments_central(image, (14.5, 14.5))
|
||||
|
||||
coords = np.array([[r, c] for r in range(13, 17)
|
||||
for c in range(13, 17)], dtype=np.double)
|
||||
mu_coords = moments_coords_central(coords, (14.5, 14.5))
|
||||
assert_almost_equal(mu_coords, mu_image)
|
||||
|
||||
# ensure that center is being calculated normally
|
||||
mu_coords_calc_centroid = moments_coords_central(coords)
|
||||
assert_almost_equal(mu_coords_calc_centroid, mu_coords)
|
||||
|
||||
# shift image by dx=3 dy=3
|
||||
image = np.zeros((20, 20), dtype=np.double)
|
||||
image[16:20, 16:20] = 1
|
||||
mu_image = moments_central(image, (14.5, 14.5))
|
||||
|
||||
coords = np.array([[r, c] for r in range(16, 20)
|
||||
for c in range(16, 20)], dtype=np.double)
|
||||
mu_coords = moments_coords_central(coords, (14.5, 14.5))
|
||||
assert_almost_equal(mu_coords, mu_image)
|
||||
|
||||
|
||||
def test_moments_normalized():
|
||||
image = np.zeros((20, 20), dtype=np.double)
|
||||
image[13:17, 13:17] = 1
|
||||
mu = moments_central(image, (14.5, 14.5))
|
||||
nu = moments_normalized(mu)
|
||||
# shift image by dx=-3, dy=-3 and scale by 0.5
|
||||
image2 = np.zeros((20, 20), dtype=np.double)
|
||||
image2[11:13, 11:13] = 1
|
||||
mu2 = moments_central(image2, (11.5, 11.5))
|
||||
nu2 = moments_normalized(mu2)
|
||||
# central moments must be translation and scale invariant
|
||||
assert_almost_equal(nu, nu2, decimal=1)
|
||||
|
||||
|
||||
def test_moments_normalized_3d():
|
||||
image = draw.ellipsoid(1, 1, 10)
|
||||
mu_image = moments_central(image)
|
||||
nu = moments_normalized(mu_image)
|
||||
assert nu[0, 0, 2] > nu[0, 2, 0]
|
||||
assert_almost_equal(nu[0, 2, 0], nu[2, 0, 0])
|
||||
|
||||
coords = np.where(image)
|
||||
mu_coords = moments_coords_central(coords)
|
||||
assert_almost_equal(mu_coords, mu_image)
|
||||
|
||||
|
||||
def test_moments_normalized_invalid():
|
||||
with testing.raises(ValueError):
|
||||
moments_normalized(np.zeros((3, 3)), 3)
|
||||
with testing.raises(ValueError):
|
||||
moments_normalized(np.zeros((3, 3)), 4)
|
||||
|
||||
|
||||
def test_moments_hu():
|
||||
image = np.zeros((20, 20), dtype=np.double)
|
||||
image[13:15, 13:17] = 1
|
||||
mu = moments_central(image, (13.5, 14.5))
|
||||
nu = moments_normalized(mu)
|
||||
hu = moments_hu(nu)
|
||||
# shift image by dx=2, dy=3, scale by 0.5 and rotate by 90deg
|
||||
image2 = np.zeros((20, 20), dtype=np.double)
|
||||
image2[11, 11:13] = 1
|
||||
image2 = image2.T
|
||||
mu2 = moments_central(image2, (11.5, 11))
|
||||
nu2 = moments_normalized(mu2)
|
||||
hu2 = moments_hu(nu2)
|
||||
# central moments must be translation and scale invariant
|
||||
assert_almost_equal(hu, hu2, decimal=1)
|
||||
|
||||
|
||||
def test_centroid():
|
||||
image = np.zeros((20, 20), dtype=np.double)
|
||||
image[14, 14:16] = 1
|
||||
image[15, 14:16] = 1/3
|
||||
image_centroid = centroid(image)
|
||||
assert_allclose(image_centroid, (14.25, 14.5))
|
||||
|
||||
|
||||
def test_inertia_tensor_2d():
|
||||
image = np.zeros((40, 40))
|
||||
image[15:25, 5:35] = 1 # big horizontal rectangle (aligned with axis 1)
|
||||
T = inertia_tensor(image)
|
||||
assert T[0, 0] > T[1, 1]
|
||||
np.testing.assert_allclose(T[0, 1], 0)
|
||||
v0, v1 = inertia_tensor_eigvals(image, T=T)
|
||||
np.testing.assert_allclose(np.sqrt(v0/v1), 3, rtol=0.01, atol=0.05)
|
||||
|
||||
|
||||
def test_inertia_tensor_3d():
|
||||
image = draw.ellipsoid(10, 5, 3)
|
||||
T0 = inertia_tensor(image)
|
||||
eig0, V0 = np.linalg.eig(T0)
|
||||
# principal axis of ellipse = eigenvector of smallest eigenvalue
|
||||
v0 = V0[:, np.argmin(eig0)]
|
||||
|
||||
assert np.allclose(v0, [1, 0, 0]) or np.allclose(-v0, [1, 0, 0])
|
||||
|
||||
imrot = ndi.rotate(image.astype(float), 30, axes=(0, 1), order=1)
|
||||
Tr = inertia_tensor(imrot)
|
||||
eigr, Vr = np.linalg.eig(Tr)
|
||||
vr = Vr[:, np.argmin(eigr)]
|
||||
|
||||
# Check that axis has rotated by expected amount
|
||||
pi, cos, sin = np.pi, np.cos, np.sin
|
||||
R = np.array([[ cos(pi/6), -sin(pi/6), 0],
|
||||
[ sin(pi/6), cos(pi/6), 0],
|
||||
[ 0, 0, 1]])
|
||||
expected_vr = R @ v0
|
||||
assert (np.allclose(vr, expected_vr, atol=1e-3, rtol=0.01) or
|
||||
np.allclose(-vr, expected_vr, atol=1e-3, rtol=0.01))
|
||||
|
||||
|
||||
def test_inertia_tensor_eigvals():
|
||||
# Floating point precision problems could make a positive
|
||||
# semidefinite matrix have an eigenvalue that is very slightly
|
||||
# negative. Check that we have caught and fixed this problem.
|
||||
image = np.array([[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]])
|
||||
# mu = np.array([[3, 0, 98], [0, 14, 0], [2, 0, 98]])
|
||||
eigvals = inertia_tensor_eigvals(image=image)
|
||||
assert (min(eigvals) >= 0)
|
35
venv/Lib/site-packages/skimage/measure/tests/test_pnpoly.py
Normal file
35
venv/Lib/site-packages/skimage/measure/tests/test_pnpoly.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
import numpy as np
|
||||
from skimage.measure import points_in_poly, grid_points_in_poly
|
||||
|
||||
from skimage._shared.testing import assert_array_equal
|
||||
|
||||
|
||||
class TestNpnpoly():
|
||||
def test_square(self):
|
||||
v = np.array([[0, 0],
|
||||
[0, 1],
|
||||
[1, 1],
|
||||
[1, 0]])
|
||||
assert(points_in_poly([[0.5, 0.5]], v)[0])
|
||||
assert(not points_in_poly([[-0.1, 0.1]], v)[0])
|
||||
|
||||
def test_triangle(self):
|
||||
v = np.array([[0, 0],
|
||||
[1, 0],
|
||||
[0.5, 0.75]])
|
||||
assert(points_in_poly([[0.5, 0.7]], v)[0])
|
||||
assert(not points_in_poly([[0.5, 0.76]], v)[0])
|
||||
assert(not points_in_poly([[0.7, 0.5]], v)[0])
|
||||
|
||||
def test_type(self):
|
||||
assert(points_in_poly([[0, 0]], [[0, 0]]).dtype == np.bool)
|
||||
|
||||
|
||||
def test_grid_points_in_poly():
|
||||
v = np.array([[0, 0],
|
||||
[5, 0],
|
||||
[5, 5]])
|
||||
|
||||
expected = np.tril(np.ones((5, 5), dtype=bool))
|
||||
|
||||
assert_array_equal(grid_points_in_poly((5, 5), v), expected)
|
64
venv/Lib/site-packages/skimage/measure/tests/test_polygon.py
Normal file
64
venv/Lib/site-packages/skimage/measure/tests/test_polygon.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
import numpy as np
|
||||
from skimage.measure import approximate_polygon, subdivide_polygon
|
||||
from skimage.measure._polygon import _SUBDIVISION_MASKS
|
||||
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import assert_array_equal, assert_equal
|
||||
|
||||
|
||||
square = np.array([
|
||||
[0, 0], [0, 1], [0, 2], [0, 3],
|
||||
[1, 3], [2, 3], [3, 3],
|
||||
[3, 2], [3, 1], [3, 0],
|
||||
[2, 0], [1, 0], [0, 0]
|
||||
])
|
||||
|
||||
|
||||
def test_approximate_polygon():
|
||||
out = approximate_polygon(square, 0.1)
|
||||
assert_array_equal(out, square[(0, 3, 6, 9, 12), :])
|
||||
|
||||
out = approximate_polygon(square, 2.2)
|
||||
assert_array_equal(out, square[(0, 6, 12), :])
|
||||
|
||||
out = approximate_polygon(square[(0, 1, 3, 4, 5, 6, 7, 9, 11, 12), :], 0.1)
|
||||
assert_array_equal(out, square[(0, 3, 6, 9, 12), :])
|
||||
|
||||
out = approximate_polygon(square, -1)
|
||||
assert_array_equal(out, square)
|
||||
out = approximate_polygon(square, 0)
|
||||
assert_array_equal(out, square)
|
||||
|
||||
|
||||
def test_subdivide_polygon():
|
||||
new_square1 = square
|
||||
new_square2 = square[:-1]
|
||||
new_square3 = square[:-1]
|
||||
# test iterative subdvision
|
||||
for _ in range(10):
|
||||
square1, square2, square3 = new_square1, new_square2, new_square3
|
||||
# test different B-Spline degrees
|
||||
for degree in range(1, 7):
|
||||
mask_len = len(_SUBDIVISION_MASKS[degree][0])
|
||||
# test circular
|
||||
new_square1 = subdivide_polygon(square1, degree)
|
||||
assert_array_equal(new_square1[-1], new_square1[0])
|
||||
assert_equal(new_square1.shape[0],
|
||||
2 * square1.shape[0] - 1)
|
||||
# test non-circular
|
||||
new_square2 = subdivide_polygon(square2, degree)
|
||||
assert_equal(new_square2.shape[0],
|
||||
2 * (square2.shape[0] - mask_len + 1))
|
||||
# test non-circular, preserve_ends
|
||||
new_square3 = subdivide_polygon(square3, degree, True)
|
||||
assert_equal(new_square3[0], square3[0])
|
||||
assert_equal(new_square3[-1], square3[-1])
|
||||
|
||||
assert_equal(new_square3.shape[0],
|
||||
2 * (square3.shape[0] - mask_len + 2))
|
||||
|
||||
# not supported B-Spline degree
|
||||
with testing.raises(ValueError):
|
||||
subdivide_polygon(square, 0)
|
||||
with testing.raises(ValueError):
|
||||
subdivide_polygon(square, 8)
|
214
venv/Lib/site-packages/skimage/measure/tests/test_profile.py
Normal file
214
venv/Lib/site-packages/skimage/measure/tests/test_profile.py
Normal file
|
@ -0,0 +1,214 @@
|
|||
import numpy as np
|
||||
|
||||
from ..._shared.testing import assert_equal, assert_almost_equal
|
||||
from ..._shared._warnings import expected_warnings
|
||||
from ..profile import profile_line
|
||||
|
||||
image = np.arange(100).reshape((10, 10)).astype(np.float)
|
||||
|
||||
|
||||
def test_horizontal_rightward():
|
||||
prof = profile_line(image, (0, 2), (0, 8), order=0, mode='constant')
|
||||
expected_prof = np.arange(2, 9)
|
||||
assert_equal(prof, expected_prof)
|
||||
|
||||
|
||||
def test_horizontal_leftward():
|
||||
prof = profile_line(image, (0, 8), (0, 2), order=0, mode='constant')
|
||||
expected_prof = np.arange(8, 1, -1)
|
||||
assert_equal(prof, expected_prof)
|
||||
|
||||
|
||||
def test_vertical_downward():
|
||||
prof = profile_line(image, (2, 5), (8, 5), order=0, mode='constant')
|
||||
expected_prof = np.arange(25, 95, 10)
|
||||
assert_equal(prof, expected_prof)
|
||||
|
||||
|
||||
def test_vertical_upward():
|
||||
prof = profile_line(image, (8, 5), (2, 5), order=0, mode='constant')
|
||||
expected_prof = np.arange(85, 15, -10)
|
||||
assert_equal(prof, expected_prof)
|
||||
|
||||
|
||||
def test_45deg_right_downward():
|
||||
prof = profile_line(image, (2, 2), (8, 8), order=0, mode='constant')
|
||||
expected_prof = np.array([22, 33, 33, 44, 55, 55, 66, 77, 77, 88])
|
||||
# repeats are due to aliasing using nearest neighbor interpolation.
|
||||
# to see this, imagine a diagonal line with markers every unit of
|
||||
# length traversing a checkerboard pattern of squares also of unit
|
||||
# length. Because the line is diagonal, sometimes more than one
|
||||
# marker will fall on the same checkerboard box.
|
||||
assert_almost_equal(prof, expected_prof)
|
||||
|
||||
|
||||
def test_45deg_right_downward_interpolated():
|
||||
prof = profile_line(image, (2, 2), (8, 8), order=1, mode='constant')
|
||||
expected_prof = np.linspace(22, 88, 10)
|
||||
assert_almost_equal(prof, expected_prof)
|
||||
|
||||
|
||||
def test_45deg_right_upward():
|
||||
prof = profile_line(image, (8, 2), (2, 8), order=1, mode='constant')
|
||||
expected_prof = np.arange(82, 27, -6)
|
||||
assert_almost_equal(prof, expected_prof)
|
||||
|
||||
|
||||
def test_45deg_left_upward():
|
||||
prof = profile_line(image, (8, 8), (2, 2), order=1, mode='constant')
|
||||
expected_prof = np.arange(88, 21, -22. / 3)
|
||||
assert_almost_equal(prof, expected_prof)
|
||||
|
||||
|
||||
def test_45deg_left_downward():
|
||||
prof = profile_line(image, (2, 8), (8, 2), order=1, mode='constant')
|
||||
expected_prof = np.arange(28, 83, 6)
|
||||
assert_almost_equal(prof, expected_prof)
|
||||
|
||||
|
||||
def test_pythagorean_triangle_right_downward():
|
||||
prof = profile_line(image, (1, 1), (7, 9), order=0, mode='constant')
|
||||
expected_prof = np.array([11, 22, 23, 33, 34, 45, 56, 57, 67, 68, 79])
|
||||
assert_equal(prof, expected_prof)
|
||||
|
||||
|
||||
def test_pythagorean_triangle_right_downward_interpolated():
|
||||
prof = profile_line(image, (1, 1), (7, 9), order=1, mode='constant')
|
||||
expected_prof = np.linspace(11, 79, 11)
|
||||
assert_almost_equal(prof, expected_prof)
|
||||
|
||||
|
||||
pyth_image = np.zeros((6, 7), np.float)
|
||||
line = ((1, 2, 2, 3, 3, 4), (1, 2, 3, 3, 4, 5))
|
||||
below = ((2, 2, 3, 4, 4, 5), (0, 1, 2, 3, 4, 4))
|
||||
above = ((0, 1, 1, 2, 3, 3), (2, 2, 3, 4, 5, 6))
|
||||
pyth_image[line] = 1.8
|
||||
pyth_image[below] = 0.6
|
||||
pyth_image[above] = 0.6
|
||||
|
||||
|
||||
def test_pythagorean_triangle_right_downward_linewidth():
|
||||
prof = profile_line(pyth_image, (1, 1), (4, 5), linewidth=3, order=0,
|
||||
mode='constant')
|
||||
expected_prof = np.ones(6)
|
||||
assert_almost_equal(prof, expected_prof)
|
||||
|
||||
|
||||
def test_pythagorean_triangle_right_upward_linewidth():
|
||||
prof = profile_line(pyth_image[::-1, :], (4, 1), (1, 5),
|
||||
linewidth=3, order=0, mode='constant')
|
||||
expected_prof = np.ones(6)
|
||||
assert_almost_equal(prof, expected_prof)
|
||||
|
||||
|
||||
def test_pythagorean_triangle_transpose_left_down_linewidth():
|
||||
prof = profile_line(pyth_image.T[:, ::-1], (1, 4), (5, 1),
|
||||
linewidth=3, order=0, mode='constant')
|
||||
expected_prof = np.ones(6)
|
||||
assert_almost_equal(prof, expected_prof)
|
||||
|
||||
|
||||
def test_reduce_func_mean():
|
||||
prof = profile_line(pyth_image, (0, 1), (3, 1), linewidth=3, order=0,
|
||||
reduce_func=np.mean, mode='reflect')
|
||||
expected_prof = pyth_image[:4, :3].mean(1)
|
||||
assert_almost_equal(prof, expected_prof)
|
||||
|
||||
|
||||
def test_reduce_func_max():
|
||||
prof = profile_line(pyth_image, (0, 1), (3, 1), linewidth=3, order=0,
|
||||
reduce_func=np.max, mode='reflect')
|
||||
expected_prof = pyth_image[:4, :3].max(1)
|
||||
assert_almost_equal(prof, expected_prof)
|
||||
|
||||
|
||||
def test_reduce_func_sum():
|
||||
prof = profile_line(pyth_image, (0, 1), (3, 1), linewidth=3, order=0,
|
||||
reduce_func=np.sum, mode='reflect')
|
||||
expected_prof = pyth_image[:4, :3].sum(1)
|
||||
assert_almost_equal(prof, expected_prof)
|
||||
|
||||
|
||||
def test_reduce_func_mean_linewidth_1():
|
||||
prof = profile_line(pyth_image, (0, 1), (3, 1), linewidth=1, order=0,
|
||||
reduce_func=np.mean, mode='constant')
|
||||
expected_prof = pyth_image[:4, 1]
|
||||
assert_almost_equal(prof, expected_prof)
|
||||
|
||||
|
||||
def test_reduce_func_None_linewidth_1():
|
||||
prof = profile_line(pyth_image, (1, 2), (4, 2), linewidth=1,
|
||||
order=0, reduce_func=None, mode='constant')
|
||||
expected_prof = pyth_image[1:5, 2, np.newaxis]
|
||||
assert_almost_equal(prof, expected_prof)
|
||||
|
||||
|
||||
def test_reduce_func_None_linewidth_3():
|
||||
prof = profile_line(pyth_image, (1, 2), (4, 2), linewidth=3,
|
||||
order=0, reduce_func=None, mode='constant')
|
||||
expected_prof = pyth_image[1:5, 1:4]
|
||||
assert_almost_equal(prof, expected_prof)
|
||||
|
||||
|
||||
def test_reduce_func_lambda_linewidth_3():
|
||||
def reduce_func(x):
|
||||
return x + x ** 2
|
||||
prof = profile_line(pyth_image, (1, 2), (4, 2), linewidth=3, order=0,
|
||||
reduce_func=reduce_func, mode='constant')
|
||||
expected_prof = np.apply_along_axis(reduce_func,
|
||||
arr=pyth_image[1:5, 1:4], axis=1)
|
||||
assert_almost_equal(prof, expected_prof)
|
||||
|
||||
|
||||
def test_reduce_func_sqrt_linewidth_3():
|
||||
def reduce_func(x):
|
||||
return x ** 0.5
|
||||
prof = profile_line(pyth_image, (1, 2), (4, 2), linewidth=3,
|
||||
order=0, reduce_func=reduce_func,
|
||||
mode='constant')
|
||||
expected_prof = np.apply_along_axis(reduce_func,
|
||||
arr=pyth_image[1:5, 1:4], axis=1)
|
||||
assert_almost_equal(prof, expected_prof)
|
||||
|
||||
|
||||
def test_reduce_func_sumofsqrt_linewidth_3():
|
||||
def reduce_func(x):
|
||||
return np.sum(x ** 0.5)
|
||||
prof = profile_line(pyth_image, (1, 2), (4, 2), linewidth=3, order=0,
|
||||
reduce_func=reduce_func, mode='constant')
|
||||
expected_prof = np.apply_along_axis(reduce_func,
|
||||
arr=pyth_image[1:5, 1:4], axis=1)
|
||||
assert_almost_equal(prof, expected_prof)
|
||||
|
||||
|
||||
def test_oob_coodinates():
|
||||
offset = 2
|
||||
idx = pyth_image.shape[0] + offset
|
||||
prof = profile_line(pyth_image, (-offset, 2), (idx, 2), linewidth=1,
|
||||
order=0, reduce_func=None, mode='constant')
|
||||
expected_prof = np.vstack([np.zeros((offset, 1)),
|
||||
pyth_image[:, 2, np.newaxis],
|
||||
np.zeros((offset + 1, 1))])
|
||||
assert_almost_equal(prof, expected_prof)
|
||||
|
||||
|
||||
def test_bool_array_input():
|
||||
|
||||
shape = (200, 200)
|
||||
center_x, center_y = (140, 150)
|
||||
radius = 20
|
||||
x, y = np.meshgrid(range(shape[1]), range(shape[0]))
|
||||
mask = (y - center_y) ** 2 + (x - center_x) ** 2 < radius ** 2
|
||||
src = (center_y, center_x)
|
||||
phi = 4 * np.pi / 9.
|
||||
dy = 31 * np.cos(phi)
|
||||
dx = 31 * np.sin(phi)
|
||||
dst = (center_y + dy, center_x + dx)
|
||||
|
||||
profile_u8 = profile_line(mask.astype(np.uint8), src, dst)
|
||||
assert all(profile_u8[:radius] == 1)
|
||||
|
||||
profile_b = profile_line(mask, src, dst)
|
||||
assert all(profile_b[:radius] == 1)
|
||||
|
||||
assert all(profile_b == profile_u8)
|
575
venv/Lib/site-packages/skimage/measure/tests/test_regionprops.py
Normal file
575
venv/Lib/site-packages/skimage/measure/tests/test_regionprops.py
Normal file
|
@ -0,0 +1,575 @@
|
|||
import math
|
||||
|
||||
import numpy as np
|
||||
from numpy import array
|
||||
|
||||
from skimage._shared._warnings import expected_warnings
|
||||
from skimage.measure._regionprops import (regionprops, PROPS, perimeter,
|
||||
_parse_docs, _props_to_dict,
|
||||
regionprops_table, OBJECT_COLUMNS,
|
||||
COL_DTYPES)
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import (assert_array_equal, assert_almost_equal,
|
||||
assert_array_almost_equal, assert_equal)
|
||||
|
||||
|
||||
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]]
|
||||
)
|
||||
INTENSITY_SAMPLE = SAMPLE.copy()
|
||||
INTENSITY_SAMPLE[1, 9:11] = 2
|
||||
|
||||
SAMPLE_3D = np.zeros((6, 6, 6), dtype=np.uint8)
|
||||
SAMPLE_3D[1:3, 1:3, 1:3] = 1
|
||||
SAMPLE_3D[3, 2, 2] = 1
|
||||
INTENSITY_SAMPLE_3D = SAMPLE_3D.copy()
|
||||
|
||||
|
||||
def test_all_props():
|
||||
region = regionprops(SAMPLE, INTENSITY_SAMPLE)[0]
|
||||
for prop in PROPS:
|
||||
try:
|
||||
assert_almost_equal(region[prop], getattr(region, PROPS[prop]))
|
||||
except TypeError: # the `slice` property causes this
|
||||
pass
|
||||
|
||||
|
||||
def test_all_props_3d():
|
||||
region = regionprops(SAMPLE_3D, INTENSITY_SAMPLE_3D)[0]
|
||||
for prop in PROPS:
|
||||
try:
|
||||
assert_almost_equal(region[prop], getattr(region, PROPS[prop]))
|
||||
except (NotImplementedError, TypeError):
|
||||
pass
|
||||
|
||||
|
||||
def test_dtype():
|
||||
regionprops(np.zeros((10, 10), dtype=np.int))
|
||||
regionprops(np.zeros((10, 10), dtype=np.uint))
|
||||
with testing.raises(TypeError):
|
||||
regionprops(np.zeros((10, 10), dtype=np.float))
|
||||
with testing.raises(TypeError):
|
||||
regionprops(np.zeros((10, 10), dtype=np.double))
|
||||
with testing.raises(TypeError):
|
||||
regionprops(np.zeros((10, 10), dtype=np.bool))
|
||||
|
||||
|
||||
def test_ndim():
|
||||
regionprops(np.zeros((10, 10), dtype=np.int))
|
||||
regionprops(np.zeros((10, 10, 1), dtype=np.int))
|
||||
regionprops(np.zeros((10, 10, 10), dtype=np.int))
|
||||
regionprops(np.zeros((1, 1), dtype=np.int))
|
||||
regionprops(np.zeros((1, 1, 1), dtype=np.int))
|
||||
with testing.raises(TypeError):
|
||||
regionprops(np.zeros((10, 10, 10, 2), dtype=np.int))
|
||||
|
||||
|
||||
def test_area():
|
||||
area = regionprops(SAMPLE)[0].area
|
||||
assert area == np.sum(SAMPLE)
|
||||
area = regionprops(SAMPLE_3D)[0].area
|
||||
assert area == np.sum(SAMPLE_3D)
|
||||
|
||||
|
||||
def test_bbox():
|
||||
bbox = regionprops(SAMPLE)[0].bbox
|
||||
assert_array_almost_equal(bbox, (0, 0, SAMPLE.shape[0], SAMPLE.shape[1]))
|
||||
|
||||
SAMPLE_mod = SAMPLE.copy()
|
||||
SAMPLE_mod[:, -1] = 0
|
||||
bbox = regionprops(SAMPLE_mod)[0].bbox
|
||||
assert_array_almost_equal(bbox, (0, 0, SAMPLE.shape[0], SAMPLE.shape[1]-1))
|
||||
|
||||
bbox = regionprops(SAMPLE_3D)[0].bbox
|
||||
assert_array_almost_equal(bbox, (1, 1, 1, 4, 3, 3))
|
||||
|
||||
|
||||
def test_bbox_area():
|
||||
padded = np.pad(SAMPLE, 5, mode='constant')
|
||||
bbox_area = regionprops(padded)[0].bbox_area
|
||||
assert_array_almost_equal(bbox_area, SAMPLE.size)
|
||||
|
||||
|
||||
def test_moments_central():
|
||||
mu = regionprops(SAMPLE)[0].moments_central
|
||||
# determined with OpenCV
|
||||
assert_almost_equal(mu[2, 0], 436.00000000000045)
|
||||
# different from OpenCV results, bug in OpenCV
|
||||
assert_almost_equal(mu[3, 0], -737.333333333333)
|
||||
assert_almost_equal(mu[1, 1], -87.33333333333303)
|
||||
assert_almost_equal(mu[2, 1], -127.5555555555593)
|
||||
assert_almost_equal(mu[0, 2], 1259.7777777777774)
|
||||
assert_almost_equal(mu[1, 2], 2000.296296296291)
|
||||
assert_almost_equal(mu[0, 3], -760.0246913580195)
|
||||
|
||||
|
||||
def test_centroid():
|
||||
centroid = regionprops(SAMPLE)[0].centroid
|
||||
# determined with MATLAB
|
||||
assert_array_almost_equal(centroid, (5.66666666666666, 9.444444444444444))
|
||||
|
||||
|
||||
def test_centroid_3d():
|
||||
centroid = regionprops(SAMPLE_3D)[0].centroid
|
||||
# determined by mean along axis 1 of SAMPLE_3D.nonzero()
|
||||
assert_array_almost_equal(centroid, (1.66666667, 1.55555556, 1.55555556))
|
||||
|
||||
|
||||
def test_convex_area():
|
||||
area = regionprops(SAMPLE)[0].convex_area
|
||||
# determined with MATLAB
|
||||
assert area == 124
|
||||
|
||||
|
||||
def test_convex_image():
|
||||
img = regionprops(SAMPLE)[0].convex_image
|
||||
# determined with MATLAB
|
||||
ref = np.array(
|
||||
[[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, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 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, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]
|
||||
)
|
||||
assert_array_equal(img, ref)
|
||||
|
||||
|
||||
def test_coordinates():
|
||||
sample = np.zeros((10, 10), dtype=np.int8)
|
||||
coords = np.array([[3, 2], [3, 3], [3, 4]])
|
||||
sample[coords[:, 0], coords[:, 1]] = 1
|
||||
prop_coords = regionprops(sample)[0].coords
|
||||
assert_array_equal(prop_coords, coords)
|
||||
|
||||
sample = np.zeros((6, 6, 6), dtype=np.int8)
|
||||
coords = np.array([[1, 1, 1], [1, 2, 1], [1, 3, 1]])
|
||||
sample[coords[:, 0], coords[:, 1], coords[:, 2]] = 1
|
||||
prop_coords = regionprops(sample)[0].coords
|
||||
assert_array_equal(prop_coords, coords)
|
||||
|
||||
|
||||
def test_slice():
|
||||
padded = np.pad(SAMPLE, ((2, 4), (5, 2)), mode='constant')
|
||||
nrow, ncol = SAMPLE.shape
|
||||
result = regionprops(padded)[0].slice
|
||||
expected = (slice(2, 2+nrow), slice(5, 5+ncol))
|
||||
assert_equal(result, expected)
|
||||
|
||||
|
||||
def test_eccentricity():
|
||||
eps = regionprops(SAMPLE)[0].eccentricity
|
||||
assert_almost_equal(eps, 0.814629313427)
|
||||
|
||||
img = np.zeros((5, 5), dtype=np.int)
|
||||
img[2, 2] = 1
|
||||
eps = regionprops(img)[0].eccentricity
|
||||
assert_almost_equal(eps, 0)
|
||||
|
||||
|
||||
def test_equiv_diameter():
|
||||
diameter = regionprops(SAMPLE)[0].equivalent_diameter
|
||||
# determined with MATLAB
|
||||
assert_almost_equal(diameter, 9.57461472963)
|
||||
|
||||
|
||||
def test_euler_number():
|
||||
en = regionprops(SAMPLE)[0].euler_number
|
||||
assert en == 1
|
||||
|
||||
SAMPLE_mod = SAMPLE.copy()
|
||||
SAMPLE_mod[7, -3] = 0
|
||||
en = regionprops(SAMPLE_mod)[0].euler_number
|
||||
assert en == 0
|
||||
|
||||
|
||||
def test_extent():
|
||||
extent = regionprops(SAMPLE)[0].extent
|
||||
assert_almost_equal(extent, 0.4)
|
||||
|
||||
|
||||
def test_moments_hu():
|
||||
hu = regionprops(SAMPLE)[0].moments_hu
|
||||
ref = np.array([
|
||||
3.27117627e-01,
|
||||
2.63869194e-02,
|
||||
2.35390060e-02,
|
||||
1.23151193e-03,
|
||||
1.38882330e-06,
|
||||
-2.72586158e-05,
|
||||
-6.48350653e-06
|
||||
])
|
||||
# bug in OpenCV caused in Central Moments calculation?
|
||||
assert_array_almost_equal(hu, ref)
|
||||
|
||||
|
||||
def test_image():
|
||||
img = regionprops(SAMPLE)[0].image
|
||||
assert_array_equal(img, SAMPLE)
|
||||
|
||||
img = regionprops(SAMPLE_3D)[0].image
|
||||
assert_array_equal(img, SAMPLE_3D[1:4, 1:3, 1:3])
|
||||
|
||||
|
||||
def test_label():
|
||||
label = regionprops(SAMPLE)[0].label
|
||||
assert_array_equal(label, 1)
|
||||
|
||||
label = regionprops(SAMPLE_3D)[0].label
|
||||
assert_array_equal(label, 1)
|
||||
|
||||
|
||||
def test_filled_area():
|
||||
area = regionprops(SAMPLE)[0].filled_area
|
||||
assert area == np.sum(SAMPLE)
|
||||
|
||||
SAMPLE_mod = SAMPLE.copy()
|
||||
SAMPLE_mod[7, -3] = 0
|
||||
area = regionprops(SAMPLE_mod)[0].filled_area
|
||||
assert area == np.sum(SAMPLE)
|
||||
|
||||
|
||||
def test_filled_image():
|
||||
img = regionprops(SAMPLE)[0].filled_image
|
||||
assert_array_equal(img, SAMPLE)
|
||||
|
||||
|
||||
def test_major_axis_length():
|
||||
length = regionprops(SAMPLE)[0].major_axis_length
|
||||
# MATLAB has different interpretation of ellipse than found in literature,
|
||||
# here implemented as found in literature
|
||||
assert_almost_equal(length, 16.7924234999)
|
||||
|
||||
|
||||
def test_max_intensity():
|
||||
intensity = regionprops(SAMPLE, intensity_image=INTENSITY_SAMPLE
|
||||
)[0].max_intensity
|
||||
assert_almost_equal(intensity, 2)
|
||||
|
||||
|
||||
def test_mean_intensity():
|
||||
intensity = regionprops(SAMPLE, intensity_image=INTENSITY_SAMPLE
|
||||
)[0].mean_intensity
|
||||
assert_almost_equal(intensity, 1.02777777777777)
|
||||
|
||||
|
||||
def test_min_intensity():
|
||||
intensity = regionprops(SAMPLE, intensity_image=INTENSITY_SAMPLE
|
||||
)[0].min_intensity
|
||||
assert_almost_equal(intensity, 1)
|
||||
|
||||
|
||||
def test_minor_axis_length():
|
||||
length = regionprops(SAMPLE)[0].minor_axis_length
|
||||
# MATLAB has different interpretation of ellipse than found in literature,
|
||||
# here implemented as found in literature
|
||||
assert_almost_equal(length, 9.739302807263)
|
||||
|
||||
|
||||
def test_moments():
|
||||
m = regionprops(SAMPLE)[0].moments
|
||||
# determined with OpenCV
|
||||
assert_almost_equal(m[0, 0], 72.0)
|
||||
assert_almost_equal(m[0, 1], 680.0)
|
||||
assert_almost_equal(m[0, 2], 7682.0)
|
||||
assert_almost_equal(m[0, 3], 95588.0)
|
||||
assert_almost_equal(m[1, 0], 408.0)
|
||||
assert_almost_equal(m[1, 1], 3766.0)
|
||||
assert_almost_equal(m[1, 2], 43882.0)
|
||||
assert_almost_equal(m[2, 0], 2748.0)
|
||||
assert_almost_equal(m[2, 1], 24836.0)
|
||||
assert_almost_equal(m[3, 0], 19776.0)
|
||||
|
||||
|
||||
def test_moments_normalized():
|
||||
nu = regionprops(SAMPLE)[0].moments_normalized
|
||||
|
||||
# determined with OpenCV
|
||||
assert_almost_equal(nu[0, 2], 0.24301268861454037)
|
||||
assert_almost_equal(nu[0, 3], -0.017278118992041805)
|
||||
assert_almost_equal(nu[1, 1], -0.016846707818929982)
|
||||
assert_almost_equal(nu[1, 2], 0.045473992910668816)
|
||||
assert_almost_equal(nu[2, 0], 0.08410493827160502)
|
||||
assert_almost_equal(nu[2, 1], -0.002899800614433943)
|
||||
|
||||
|
||||
def test_orientation():
|
||||
orient = regionprops(SAMPLE)[0].orientation
|
||||
# determined with MATLAB
|
||||
assert_almost_equal(orient, -1.4663278802756865)
|
||||
# test diagonal regions
|
||||
diag = np.eye(10, dtype=int)
|
||||
orient_diag = regionprops(diag)[0].orientation
|
||||
assert_almost_equal(orient_diag, -math.pi / 4)
|
||||
orient_diag = regionprops(np.flipud(diag))[0].orientation
|
||||
assert_almost_equal(orient_diag, math.pi / 4)
|
||||
orient_diag = regionprops(np.fliplr(diag))[0].orientation
|
||||
assert_almost_equal(orient_diag, math.pi / 4)
|
||||
orient_diag = regionprops(np.fliplr(np.flipud(diag)))[0].orientation
|
||||
assert_almost_equal(orient_diag, -math.pi / 4)
|
||||
|
||||
|
||||
def test_perimeter():
|
||||
per = regionprops(SAMPLE)[0].perimeter
|
||||
assert_almost_equal(per, 55.2487373415)
|
||||
|
||||
per = perimeter(SAMPLE.astype('double'), neighbourhood=8)
|
||||
assert_almost_equal(per, 46.8284271247)
|
||||
|
||||
|
||||
def test_solidity():
|
||||
solidity = regionprops(SAMPLE)[0].solidity
|
||||
# determined with MATLAB
|
||||
assert_almost_equal(solidity, 0.580645161290323)
|
||||
|
||||
|
||||
def test_weighted_moments_central():
|
||||
wmu = regionprops(SAMPLE, intensity_image=INTENSITY_SAMPLE
|
||||
)[0].weighted_moments_central
|
||||
ref = np.array(
|
||||
[[7.4000000000e+01, 3.7303493627e-14, 1.2602837838e+03,
|
||||
-7.6561796932e+02],
|
||||
[-2.1316282073e-13, -8.7837837838e+01, 2.1571526662e+03,
|
||||
-4.2385971907e+03],
|
||||
[4.7837837838e+02, -1.4801314828e+02, 6.6989799420e+03,
|
||||
-9.9501164076e+03],
|
||||
[-7.5943608473e+02, -1.2714707125e+03, 1.5304076361e+04,
|
||||
-3.3156729271e+04]])
|
||||
|
||||
np.set_printoptions(precision=10)
|
||||
assert_array_almost_equal(wmu, ref)
|
||||
|
||||
|
||||
def test_weighted_centroid():
|
||||
centroid = regionprops(SAMPLE, intensity_image=INTENSITY_SAMPLE
|
||||
)[0].weighted_centroid
|
||||
assert_array_almost_equal(centroid, (5.540540540540, 9.445945945945))
|
||||
|
||||
|
||||
def test_weighted_moments_hu():
|
||||
whu = regionprops(SAMPLE, intensity_image=INTENSITY_SAMPLE
|
||||
)[0].weighted_moments_hu
|
||||
ref = np.array([
|
||||
3.1750587329e-01,
|
||||
2.1417517159e-02,
|
||||
2.3609322038e-02,
|
||||
1.2565683360e-03,
|
||||
8.3014209421e-07,
|
||||
-3.5073773473e-05,
|
||||
-6.7936409056e-06
|
||||
])
|
||||
assert_array_almost_equal(whu, ref)
|
||||
|
||||
|
||||
def test_weighted_moments():
|
||||
wm = regionprops(SAMPLE, intensity_image=INTENSITY_SAMPLE
|
||||
)[0].weighted_moments
|
||||
ref = np.array(
|
||||
[[7.4000000e+01, 6.9900000e+02, 7.8630000e+03, 9.7317000e+04],
|
||||
[4.1000000e+02, 3.7850000e+03, 4.4063000e+04, 5.7256700e+05],
|
||||
[2.7500000e+03, 2.4855000e+04, 2.9347700e+05, 3.9007170e+06],
|
||||
[1.9778000e+04, 1.7500100e+05, 2.0810510e+06, 2.8078871e+07]]
|
||||
)
|
||||
assert_array_almost_equal(wm, ref)
|
||||
|
||||
|
||||
def test_weighted_moments_normalized():
|
||||
wnu = regionprops(SAMPLE, intensity_image=INTENSITY_SAMPLE
|
||||
)[0].weighted_moments_normalized
|
||||
ref = np.array(
|
||||
[[ np.nan, np.nan, 0.2301467830, -0.0162529732],
|
||||
[ np.nan, -0.0160405109, 0.0457932622, -0.0104598869],
|
||||
[ 0.0873590903, -0.0031421072, 0.0165315478, -0.0028544152],
|
||||
[-0.0161217406, -0.0031376984, 0.0043903193, -0.0011057191]]
|
||||
)
|
||||
assert_array_almost_equal(wnu, ref)
|
||||
|
||||
|
||||
def test_label_sequence():
|
||||
a = np.empty((2, 2), dtype=np.int)
|
||||
a[:, :] = 2
|
||||
ps = regionprops(a)
|
||||
assert len(ps) == 1
|
||||
assert ps[0].label == 2
|
||||
|
||||
|
||||
def test_pure_background():
|
||||
a = np.zeros((2, 2), dtype=np.int)
|
||||
ps = regionprops(a)
|
||||
assert len(ps) == 0
|
||||
|
||||
|
||||
def test_invalid():
|
||||
ps = regionprops(SAMPLE)
|
||||
|
||||
def get_intensity_image():
|
||||
ps[0].intensity_image
|
||||
|
||||
with testing.raises(AttributeError):
|
||||
get_intensity_image()
|
||||
|
||||
|
||||
def test_invalid_size():
|
||||
wrong_intensity_sample = np.array([[1], [1]])
|
||||
with testing.raises(ValueError):
|
||||
regionprops(SAMPLE, wrong_intensity_sample)
|
||||
|
||||
|
||||
def test_equals():
|
||||
arr = np.zeros((100, 100), dtype=np.int)
|
||||
arr[0:25, 0:25] = 1
|
||||
arr[50:99, 50:99] = 2
|
||||
|
||||
regions = regionprops(arr)
|
||||
r1 = regions[0]
|
||||
|
||||
regions = regionprops(arr)
|
||||
r2 = regions[0]
|
||||
r3 = regions[1]
|
||||
|
||||
assert_equal(r1 == r2, True, "Same regionprops are not equal")
|
||||
assert_equal(r1 != r3, True, "Different regionprops are equal")
|
||||
|
||||
|
||||
def test_iterate_all_props():
|
||||
region = regionprops(SAMPLE)[0]
|
||||
p0 = {p: region[p] for p in region}
|
||||
|
||||
region = regionprops(SAMPLE, intensity_image=INTENSITY_SAMPLE)[0]
|
||||
p1 = {p: region[p] for p in region}
|
||||
|
||||
assert len(p0) < len(p1)
|
||||
|
||||
|
||||
def test_cache():
|
||||
SAMPLE_mod = SAMPLE.copy()
|
||||
region = regionprops(SAMPLE_mod)[0]
|
||||
f0 = region.filled_image
|
||||
region._label_image[:10] = 1
|
||||
f1 = region.filled_image
|
||||
|
||||
# Changed underlying image, but cache keeps result the same
|
||||
assert_array_equal(f0, f1)
|
||||
|
||||
# Now invalidate cache
|
||||
region._cache_active = False
|
||||
f1 = region.filled_image
|
||||
|
||||
assert np.any(f0 != f1)
|
||||
|
||||
|
||||
def test_docstrings_and_props():
|
||||
def foo():
|
||||
"""foo"""
|
||||
|
||||
has_docstrings = bool(foo.__doc__)
|
||||
|
||||
region = regionprops(SAMPLE)[0]
|
||||
|
||||
docs = _parse_docs()
|
||||
props = [m for m in dir(region) if not m.startswith('_')]
|
||||
|
||||
nr_docs_parsed = len(docs)
|
||||
nr_props = len(props)
|
||||
if has_docstrings:
|
||||
assert_equal(nr_docs_parsed, nr_props)
|
||||
ds = docs['weighted_moments_normalized']
|
||||
assert 'iteration' not in ds
|
||||
assert len(ds.split('\n')) > 3
|
||||
else:
|
||||
assert_equal(nr_docs_parsed, 0)
|
||||
|
||||
|
||||
def test_props_to_dict():
|
||||
regions = regionprops(SAMPLE)
|
||||
out = _props_to_dict(regions)
|
||||
assert out == {'label': array([1]),
|
||||
'bbox-0': array([0]), 'bbox-1': array([0]),
|
||||
'bbox-2': array([10]), 'bbox-3': array([18])}
|
||||
|
||||
regions = regionprops(SAMPLE)
|
||||
out = _props_to_dict(regions, properties=('label', 'area', 'bbox'),
|
||||
separator='+')
|
||||
assert out == {'label': array([1]), 'area': array([72]),
|
||||
'bbox+0': array([0]), 'bbox+1': array([0]),
|
||||
'bbox+2': array([10]), 'bbox+3': array([18])}
|
||||
|
||||
|
||||
def test_regionprops_table():
|
||||
out = regionprops_table(SAMPLE)
|
||||
assert out == {'label': array([1]),
|
||||
'bbox-0': array([0]), 'bbox-1': array([0]),
|
||||
'bbox-2': array([10]), 'bbox-3': array([18])}
|
||||
|
||||
out = regionprops_table(SAMPLE, properties=('label', 'area', 'bbox'),
|
||||
separator='+')
|
||||
assert out == {'label': array([1]), 'area': array([72]),
|
||||
'bbox+0': array([0]), 'bbox+1': array([0]),
|
||||
'bbox+2': array([10]), 'bbox+3': array([18])}
|
||||
|
||||
out = regionprops_table(np.zeros((2, 2), dtype=int),
|
||||
properties=('label', 'area', 'bbox'),
|
||||
separator='+')
|
||||
assert len(out) == 6
|
||||
assert len(out['label']) == 0
|
||||
assert len(out['area']) == 0
|
||||
assert len(out['bbox+0']) == 0
|
||||
assert len(out['bbox+1']) == 0
|
||||
assert len(out['bbox+2']) == 0
|
||||
assert len(out['bbox+3']) == 0
|
||||
|
||||
|
||||
def test_props_dict_complete():
|
||||
region = regionprops(SAMPLE)[0]
|
||||
properties = [s for s in dir(region) if not s.startswith('_')]
|
||||
assert set(properties) == set(PROPS.values())
|
||||
|
||||
|
||||
def test_column_dtypes_complete():
|
||||
assert set(COL_DTYPES.keys()).union(OBJECT_COLUMNS) == set(PROPS.values())
|
||||
|
||||
|
||||
def test_column_dtypes_correct():
|
||||
msg = 'mismatch with expected type,'
|
||||
region = regionprops(SAMPLE, intensity_image=INTENSITY_SAMPLE)[0]
|
||||
for col in COL_DTYPES:
|
||||
r = region[col]
|
||||
|
||||
if col in OBJECT_COLUMNS:
|
||||
assert COL_DTYPES[col] == object
|
||||
continue
|
||||
|
||||
t = type(np.ravel(r)[0])
|
||||
|
||||
if np.issubdtype(t, np.floating):
|
||||
assert COL_DTYPES[col] == float, (
|
||||
f'{col} dtype {t} {msg} {COL_DTYPES[col]}'
|
||||
)
|
||||
elif np.issubdtype(t, np.integer):
|
||||
assert COL_DTYPES[col] == int, (
|
||||
f'{col} dtype {t} {msg} {COL_DTYPES[col]}'
|
||||
)
|
||||
else:
|
||||
assert False, (
|
||||
f'{col} dtype {t} {msg} {COL_DTYPES[col]}'
|
||||
)
|
||||
|
||||
|
||||
def test_deprecated_coords_argument():
|
||||
with expected_warnings(['coordinates keyword argument']):
|
||||
region = regionprops(SAMPLE, coordinates='rc')
|
||||
with testing.raises(ValueError):
|
||||
region = regionprops(SAMPLE, coordinates='xy')
|
|
@ -0,0 +1,84 @@
|
|||
import numpy as np
|
||||
|
||||
import skimage.data
|
||||
from skimage.measure import compare_nrmse, compare_psnr, compare_mse
|
||||
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import assert_equal, assert_almost_equal
|
||||
from skimage._shared._warnings import expected_warnings
|
||||
|
||||
|
||||
np.random.seed(5)
|
||||
cam = skimage.data.camera()
|
||||
sigma = 20.0
|
||||
cam_noisy = np.clip(cam + sigma * np.random.randn(*cam.shape), 0, 255)
|
||||
cam_noisy = cam_noisy.astype(cam.dtype)
|
||||
|
||||
|
||||
def test_PSNR_vs_IPOL():
|
||||
# Tests vs. imdiff result from the following IPOL article and code:
|
||||
# https://www.ipol.im/pub/art/2011/g_lmii/
|
||||
p_IPOL = 22.4497
|
||||
with expected_warnings(['DEPRECATED']):
|
||||
p = compare_psnr(cam, cam_noisy)
|
||||
assert_almost_equal(p, p_IPOL, decimal=4)
|
||||
|
||||
|
||||
def test_PSNR_float():
|
||||
with expected_warnings(['DEPRECATED']):
|
||||
p_uint8 = compare_psnr(cam, cam_noisy)
|
||||
p_float64 = compare_psnr(cam / 255., cam_noisy / 255.,
|
||||
data_range=1)
|
||||
assert_almost_equal(p_uint8, p_float64, decimal=5)
|
||||
|
||||
# mixed precision inputs
|
||||
with expected_warnings(['DEPRECATED']):
|
||||
p_mixed = compare_psnr(cam / 255., np.float32(cam_noisy / 255.),
|
||||
data_range=1)
|
||||
assert_almost_equal(p_mixed, p_float64, decimal=5)
|
||||
|
||||
# mismatched dtype results in a warning if data_range is unspecified
|
||||
with expected_warnings(['Inputs have mismatched dtype', 'DEPRECATED']):
|
||||
p_mixed = compare_psnr(cam / 255., np.float32(cam_noisy / 255.))
|
||||
assert_almost_equal(p_mixed, p_float64, decimal=5)
|
||||
|
||||
|
||||
def test_PSNR_errors():
|
||||
with expected_warnings(['DEPRECATED']):
|
||||
# shape mismatch
|
||||
with testing.raises(ValueError):
|
||||
compare_psnr(cam, cam[:-1, :])
|
||||
|
||||
|
||||
def test_NRMSE():
|
||||
x = np.ones(4)
|
||||
y = np.asarray([0., 2., 2., 2.])
|
||||
with expected_warnings(['DEPRECATED']):
|
||||
assert_equal(compare_nrmse(y, x, 'mean'), 1 / np.mean(y))
|
||||
assert_equal(compare_nrmse(y, x, 'Euclidean'), 1 / np.sqrt(3))
|
||||
assert_equal(compare_nrmse(y, x, 'min-max'), 1 / (y.max() - y.min()))
|
||||
|
||||
# mixed precision inputs are allowed
|
||||
assert_almost_equal(compare_nrmse(y, np.float32(x), 'min-max'),
|
||||
1 / (y.max() - y.min()))
|
||||
|
||||
|
||||
def test_NRMSE_no_int_overflow():
|
||||
camf = cam.astype(np.float32)
|
||||
cam_noisyf = cam_noisy.astype(np.float32)
|
||||
with expected_warnings(['DEPRECATED']):
|
||||
assert_almost_equal(compare_mse(cam, cam_noisy),
|
||||
compare_mse(camf, cam_noisyf))
|
||||
assert_almost_equal(compare_nrmse(cam, cam_noisy),
|
||||
compare_nrmse(camf, cam_noisyf))
|
||||
|
||||
|
||||
def test_NRMSE_errors():
|
||||
x = np.ones(4)
|
||||
with expected_warnings(['DEPRECATED']):
|
||||
# shape mismatch
|
||||
with testing.raises(ValueError):
|
||||
compare_nrmse(x[:-1], x)
|
||||
# invalid normalization name
|
||||
with testing.raises(ValueError):
|
||||
compare_nrmse(x, x, norm_type='foo')
|
|
@ -0,0 +1,240 @@
|
|||
import os
|
||||
import numpy as np
|
||||
|
||||
from skimage import data, data_dir
|
||||
from skimage.metrics import structural_similarity
|
||||
|
||||
from skimage._shared import testing
|
||||
from skimage._shared._warnings import expected_warnings
|
||||
from skimage._shared.testing import (assert_equal, assert_almost_equal,
|
||||
assert_array_almost_equal, fetch)
|
||||
|
||||
np.random.seed(5)
|
||||
cam = data.camera()
|
||||
sigma = 20.0
|
||||
cam_noisy = np.clip(cam + sigma * np.random.randn(*cam.shape), 0, 255)
|
||||
cam_noisy = cam_noisy.astype(cam.dtype)
|
||||
|
||||
np.random.seed(1234)
|
||||
|
||||
|
||||
def test_structural_similarity_patch_range():
|
||||
N = 51
|
||||
X = (np.random.rand(N, N) * 255).astype(np.uint8)
|
||||
Y = (np.random.rand(N, N) * 255).astype(np.uint8)
|
||||
|
||||
assert(structural_similarity(X, Y, win_size=N) < 0.1)
|
||||
assert_equal(structural_similarity(X, X, win_size=N), 1)
|
||||
|
||||
|
||||
def test_structural_similarity_image():
|
||||
N = 100
|
||||
X = (np.random.rand(N, N) * 255).astype(np.uint8)
|
||||
Y = (np.random.rand(N, N) * 255).astype(np.uint8)
|
||||
|
||||
S0 = structural_similarity(X, X, win_size=3)
|
||||
assert_equal(S0, 1)
|
||||
|
||||
S1 = structural_similarity(X, Y, win_size=3)
|
||||
assert(S1 < 0.3)
|
||||
|
||||
S2 = structural_similarity(X, Y, win_size=11, gaussian_weights=True)
|
||||
assert(S2 < 0.3)
|
||||
|
||||
mssim0, S3 = structural_similarity(X, Y, full=True)
|
||||
assert_equal(S3.shape, X.shape)
|
||||
mssim = structural_similarity(X, Y)
|
||||
assert_equal(mssim0, mssim)
|
||||
|
||||
# ssim of image with itself should be 1.0
|
||||
assert_equal(structural_similarity(X, X), 1.0)
|
||||
|
||||
|
||||
# Because we are forcing a random seed state, it is probably good to test
|
||||
# against a few seeds in case on seed gives a particularly bad example
|
||||
@testing.parametrize('seed', [1, 2, 3, 5, 8, 13])
|
||||
def test_structural_similarity_grad(seed):
|
||||
N = 30
|
||||
# NOTE: This test is known to randomly fail on some systems (Mac OS X 10.6)
|
||||
# And when testing tests in parallel. Therefore, we choose a few
|
||||
# seeds that are known to work.
|
||||
# The likely cause of this failure is that we are setting a hard
|
||||
# threshold on the value of the gradient. Often the computed gradient
|
||||
# is only slightly larger than what was measured.
|
||||
# X = np.random.rand(N, N) * 255
|
||||
# Y = np.random.rand(N, N) * 255
|
||||
rnd = np.random.RandomState(seed)
|
||||
X = rnd.rand(N, N) * 255
|
||||
Y = rnd.rand(N, N) * 255
|
||||
|
||||
f = structural_similarity(X, Y, data_range=255)
|
||||
g = structural_similarity(X, Y, data_range=255, gradient=True)
|
||||
|
||||
assert f < 0.05
|
||||
|
||||
assert g[0] < 0.05
|
||||
assert np.all(g[1] < 0.05)
|
||||
|
||||
mssim, grad, s = structural_similarity(X, Y, data_range=255,
|
||||
gradient=True, full=True)
|
||||
assert np.all(grad < 0.05)
|
||||
|
||||
|
||||
def test_structural_similarity_dtype():
|
||||
N = 30
|
||||
X = np.random.rand(N, N)
|
||||
Y = np.random.rand(N, N)
|
||||
|
||||
S1 = structural_similarity(X, Y)
|
||||
|
||||
X = (X * 255).astype(np.uint8)
|
||||
Y = (X * 255).astype(np.uint8)
|
||||
|
||||
S2 = structural_similarity(X, Y)
|
||||
|
||||
assert S1 < 0.1
|
||||
assert S2 < 0.1
|
||||
|
||||
|
||||
def test_structural_similarity_multichannel():
|
||||
N = 100
|
||||
X = (np.random.rand(N, N) * 255).astype(np.uint8)
|
||||
Y = (np.random.rand(N, N) * 255).astype(np.uint8)
|
||||
|
||||
S1 = structural_similarity(X, Y, win_size=3)
|
||||
|
||||
# replicate across three channels. should get identical value
|
||||
Xc = np.tile(X[..., np.newaxis], (1, 1, 3))
|
||||
Yc = np.tile(Y[..., np.newaxis], (1, 1, 3))
|
||||
S2 = structural_similarity(Xc, Yc, multichannel=True, win_size=3)
|
||||
assert_almost_equal(S1, S2)
|
||||
|
||||
# full case should return an image as well
|
||||
m, S3 = structural_similarity(Xc, Yc, multichannel=True, full=True)
|
||||
assert_equal(S3.shape, Xc.shape)
|
||||
|
||||
# gradient case
|
||||
m, grad = structural_similarity(Xc, Yc, multichannel=True, gradient=True)
|
||||
assert_equal(grad.shape, Xc.shape)
|
||||
|
||||
# full and gradient case
|
||||
m, grad, S3 = structural_similarity(Xc, Yc,
|
||||
multichannel=True,
|
||||
full=True,
|
||||
gradient=True)
|
||||
assert_equal(grad.shape, Xc.shape)
|
||||
assert_equal(S3.shape, Xc.shape)
|
||||
|
||||
# fail if win_size exceeds any non-channel dimension
|
||||
with testing.raises(ValueError):
|
||||
structural_similarity(Xc, Yc, win_size=7, multichannel=False)
|
||||
|
||||
|
||||
def test_structural_similarity_nD():
|
||||
# test 1D through 4D on small random arrays
|
||||
N = 10
|
||||
for ndim in range(1, 5):
|
||||
xsize = [N, ] * 5
|
||||
X = (np.random.rand(*xsize) * 255).astype(np.uint8)
|
||||
Y = (np.random.rand(*xsize) * 255).astype(np.uint8)
|
||||
|
||||
mssim = structural_similarity(X, Y, win_size=3)
|
||||
assert mssim < 0.05
|
||||
|
||||
|
||||
def test_structural_similarity_multichannel_chelsea():
|
||||
# color image example
|
||||
Xc = data.chelsea()
|
||||
sigma = 15.0
|
||||
Yc = np.clip(Xc + sigma * np.random.randn(*Xc.shape), 0, 255)
|
||||
Yc = Yc.astype(Xc.dtype)
|
||||
|
||||
# multichannel result should be mean of the individual channel results
|
||||
mssim = structural_similarity(Xc, Yc, multichannel=True)
|
||||
mssim_sep = [structural_similarity(Yc[..., c], Xc[..., c])
|
||||
for c in range(Xc.shape[-1])]
|
||||
assert_almost_equal(mssim, np.mean(mssim_sep))
|
||||
|
||||
# ssim of image with itself should be 1.0
|
||||
assert_equal(structural_similarity(Xc, Xc, multichannel=True), 1.0)
|
||||
|
||||
|
||||
def test_gaussian_mssim_vs_IPOL():
|
||||
# Tests vs. imdiff result from the following IPOL article and code:
|
||||
# https://www.ipol.im/pub/art/2011/g_lmii/
|
||||
mssim_IPOL = 0.327309966087341
|
||||
mssim = structural_similarity(cam, cam_noisy, gaussian_weights=True,
|
||||
use_sample_covariance=False)
|
||||
assert_almost_equal(mssim, mssim_IPOL, decimal=3)
|
||||
|
||||
|
||||
def test_gaussian_mssim_vs_author_ref():
|
||||
"""
|
||||
test vs. result from original author's Matlab implementation available at
|
||||
https://ece.uwaterloo.ca/~z70wang/research/ssim/
|
||||
|
||||
Matlab test code:
|
||||
img1 = imread('camera.png')
|
||||
img2 = imread('camera_noisy.png')
|
||||
mssim = ssim_index(img1, img2)
|
||||
"""
|
||||
mssim_matlab = 0.327314295673357
|
||||
mssim = structural_similarity(cam, cam_noisy, gaussian_weights=True,
|
||||
use_sample_covariance=False)
|
||||
assert_almost_equal(mssim, mssim_matlab, decimal=10)
|
||||
|
||||
|
||||
def test_gaussian_mssim_and_gradient_vs_Matlab():
|
||||
# comparison to Matlab implementation of N. Avanaki:
|
||||
# https://ece.uwaterloo.ca/~nnikvand/Coderep/SHINE%20TOOLBOX/SHINEtoolbox/
|
||||
# Note: final line of ssim_sens.m was modified to discard image borders
|
||||
|
||||
ref = np.load(fetch('data/mssim_matlab_output.npz'))
|
||||
grad_matlab = ref['grad_matlab']
|
||||
mssim_matlab = float(ref['mssim_matlab'])
|
||||
|
||||
mssim, grad = structural_similarity(cam, cam_noisy, gaussian_weights=True,
|
||||
gradient=True,
|
||||
use_sample_covariance=False)
|
||||
|
||||
assert_almost_equal(mssim, mssim_matlab, decimal=3)
|
||||
|
||||
# check almost equal aside from object borders
|
||||
assert_array_almost_equal(grad_matlab[5:-5], grad[5:-5])
|
||||
|
||||
|
||||
def test_mssim_vs_legacy():
|
||||
# check that ssim with default options matches skimage 0.11 result
|
||||
mssim_skimage_0pt11 = 0.34192589699605191
|
||||
mssim = structural_similarity(cam, cam_noisy)
|
||||
assert_almost_equal(mssim, mssim_skimage_0pt11)
|
||||
|
||||
|
||||
def test_mssim_mixed_dtype():
|
||||
mssim = structural_similarity(cam, cam_noisy)
|
||||
with expected_warnings(['Inputs have mismatched dtype']):
|
||||
mssim_mixed = structural_similarity(cam, cam_noisy.astype(np.float32))
|
||||
assert_almost_equal(mssim, mssim_mixed)
|
||||
|
||||
# no warning when user supplies data_range
|
||||
mssim_mixed = structural_similarity(cam, cam_noisy.astype(np.float32),
|
||||
data_range=255)
|
||||
assert_almost_equal(mssim, mssim_mixed)
|
||||
|
||||
|
||||
def test_invalid_input():
|
||||
# size mismatch
|
||||
X = np.zeros((9, 9), dtype=np.double)
|
||||
Y = np.zeros((8, 8), dtype=np.double)
|
||||
with testing.raises(ValueError):
|
||||
structural_similarity(X, Y)
|
||||
# win_size exceeds image extent
|
||||
with testing.raises(ValueError):
|
||||
structural_similarity(X, X, win_size=X.shape[0] + 1)
|
||||
# some kwarg inputs must be non-negative
|
||||
with testing.raises(ValueError):
|
||||
structural_similarity(X, X, K1=-0.1)
|
||||
with testing.raises(ValueError):
|
||||
structural_similarity(X, X, K2=-0.1)
|
||||
with testing.raises(ValueError):
|
||||
structural_similarity(X, X, sigma=-1.0)
|
Loading…
Add table
Add a link
Reference in a new issue