rm CondaPkg environment
This commit is contained in:
@@ -1,29 +0,0 @@
|
||||
from .draw import (ellipse, set_color, polygon_perimeter,
|
||||
line, line_aa, polygon, ellipse_perimeter,
|
||||
circle_perimeter, circle_perimeter_aa,
|
||||
disk, bezier_curve, rectangle, rectangle_perimeter)
|
||||
from .draw3d import ellipsoid, ellipsoid_stats
|
||||
from ._draw import _bezier_segment
|
||||
from ._random_shapes import random_shapes
|
||||
from ._polygon2mask import polygon2mask
|
||||
|
||||
from .draw_nd import line_nd
|
||||
|
||||
__all__ = ['line',
|
||||
'line_aa',
|
||||
'line_nd',
|
||||
'bezier_curve',
|
||||
'polygon',
|
||||
'polygon_perimeter',
|
||||
'ellipse',
|
||||
'ellipse_perimeter',
|
||||
'ellipsoid',
|
||||
'ellipsoid_stats',
|
||||
'circle_perimeter',
|
||||
'circle_perimeter_aa',
|
||||
'disk',
|
||||
'set_color',
|
||||
'random_shapes',
|
||||
'rectangle',
|
||||
'rectangle_perimeter',
|
||||
'polygon2mask']
|
||||
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.
@@ -1,41 +0,0 @@
|
||||
import numpy as np
|
||||
|
||||
from . import draw
|
||||
|
||||
|
||||
def polygon2mask(image_shape, polygon):
|
||||
"""Compute a mask from polygon.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image_shape : tuple of size 2.
|
||||
The shape of the mask.
|
||||
polygon : array_like.
|
||||
The polygon coordinates of shape (N, 2) where N is
|
||||
the number of points.
|
||||
|
||||
Returns
|
||||
-------
|
||||
mask : 2-D ndarray of type 'bool'.
|
||||
The mask that corresponds to the input polygon.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This function does not do any border checking, so that all
|
||||
the vertices need to be within the given shape.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> image_shape = (128, 128)
|
||||
>>> polygon = np.array([[60, 100], [100, 40], [40, 40]])
|
||||
>>> mask = polygon2mask(image_shape, polygon)
|
||||
>>> mask.shape
|
||||
(128, 128)
|
||||
"""
|
||||
polygon = np.asarray(polygon)
|
||||
vertex_row_coords, vertex_col_coords = polygon.T
|
||||
fill_row_coords, fill_col_coords = draw.polygon(
|
||||
vertex_row_coords, vertex_col_coords, image_shape)
|
||||
mask = np.zeros(image_shape, dtype=bool)
|
||||
mask[fill_row_coords, fill_col_coords] = True
|
||||
return mask
|
||||
@@ -1,450 +0,0 @@
|
||||
import math
|
||||
import numpy as np
|
||||
|
||||
from . import (polygon as draw_polygon, disk as draw_disk,
|
||||
ellipse as draw_ellipse)
|
||||
from .._shared.utils import warn
|
||||
|
||||
|
||||
def _generate_rectangle_mask(point, image, shape, random):
|
||||
"""Generate a mask for a filled rectangle shape.
|
||||
|
||||
The height and width of the rectangle are generated randomly.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
point : tuple
|
||||
The row and column of the top left corner of the rectangle.
|
||||
image : tuple
|
||||
The height, width and depth of the image into which the shape
|
||||
is placed.
|
||||
shape : tuple
|
||||
The minimum and maximum size of the shape to fit.
|
||||
random : `numpy.random.Generator`
|
||||
|
||||
The random state to use for random sampling.
|
||||
|
||||
Raises
|
||||
------
|
||||
ArithmeticError
|
||||
When a shape cannot be fit into the image with the given starting
|
||||
coordinates. This usually means the image dimensions are too small or
|
||||
shape dimensions too large.
|
||||
|
||||
Returns
|
||||
-------
|
||||
label : tuple
|
||||
A (category, ((r0, r1), (c0, c1))) tuple specifying the category and
|
||||
bounding box coordinates of the shape.
|
||||
indices : 2-D array
|
||||
A mask of indices that the shape fills.
|
||||
|
||||
"""
|
||||
available_width = min(image[1] - point[1], shape[1]) - shape[0]
|
||||
available_height = min(image[0] - point[0], shape[1]) - shape[0]
|
||||
|
||||
# Pick random widths and heights.
|
||||
r = shape[0] + random.integers(max(1, available_height)) - 1
|
||||
c = shape[0] + random.integers(max(1, available_width)) - 1
|
||||
rectangle = draw_polygon([
|
||||
point[0],
|
||||
point[0] + r,
|
||||
point[0] + r,
|
||||
point[0],
|
||||
], [
|
||||
point[1],
|
||||
point[1],
|
||||
point[1] + c,
|
||||
point[1] + c,
|
||||
])
|
||||
label = ('rectangle', ((point[0], point[0] + r + 1),
|
||||
(point[1], point[1] + c + 1)))
|
||||
|
||||
return rectangle, label
|
||||
|
||||
|
||||
def _generate_circle_mask(point, image, shape, random):
|
||||
"""Generate a mask for a filled circle shape.
|
||||
|
||||
The radius of the circle is generated randomly.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
point : tuple
|
||||
The row and column of the top left corner of the rectangle.
|
||||
image : tuple
|
||||
The height, width and depth of the image into which the shape is placed.
|
||||
shape : tuple
|
||||
The minimum and maximum size and color of the shape to fit.
|
||||
random : `numpy.random.Generator`
|
||||
The random state to use for random sampling.
|
||||
|
||||
Raises
|
||||
------
|
||||
ArithmeticError
|
||||
When a shape cannot be fit into the image with the given starting
|
||||
coordinates. This usually means the image dimensions are too small or
|
||||
shape dimensions too large.
|
||||
|
||||
Returns
|
||||
-------
|
||||
label : tuple
|
||||
A (category, ((r0, r1), (c0, c1))) tuple specifying the category and
|
||||
bounding box coordinates of the shape.
|
||||
indices : 2-D array
|
||||
A mask of indices that the shape fills.
|
||||
"""
|
||||
if shape[0] == 1 or shape[1] == 1:
|
||||
raise ValueError('size must be > 1 for circles')
|
||||
min_radius = shape[0] // 2.0
|
||||
max_radius = shape[1] // 2.0
|
||||
left = point[1]
|
||||
right = image[1] - point[1]
|
||||
top = point[0]
|
||||
bottom = image[0] - point[0]
|
||||
available_radius = min(left, right, top,
|
||||
bottom, max_radius) - min_radius
|
||||
if available_radius < 0:
|
||||
raise ArithmeticError('cannot fit shape to image')
|
||||
radius = int(min_radius + random.integers(max(1, available_radius)))
|
||||
# TODO: think about how to deprecate this
|
||||
# while draw_circle was deprecated in favor of draw_disk
|
||||
# switching to a label of 'disk' here
|
||||
# would be a breaking change for downstream libraries
|
||||
# See discussion on naming convention here
|
||||
# https://github.com/scikit-image/scikit-image/pull/4428
|
||||
disk = draw_disk((point[0], point[1]), radius)
|
||||
# Until a deprecation path is decided, always return `'circle'`
|
||||
label = ('circle', ((point[0] - radius + 1, point[0] + radius),
|
||||
(point[1] - radius + 1, point[1] + radius)))
|
||||
|
||||
return disk, label
|
||||
|
||||
|
||||
def _generate_triangle_mask(point, image, shape, random):
|
||||
"""Generate a mask for a filled equilateral triangle shape.
|
||||
|
||||
The length of the sides of the triangle is generated randomly.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
point : tuple
|
||||
The row and column of the top left corner of a up-pointing triangle.
|
||||
image : tuple
|
||||
The height, width and depth of the image into which the shape
|
||||
is placed.
|
||||
shape : tuple
|
||||
The minimum and maximum size and color of the shape to fit.
|
||||
random : `numpy.random.Generator`
|
||||
The random state to use for random sampling.
|
||||
|
||||
Raises
|
||||
------
|
||||
ArithmeticError
|
||||
When a shape cannot be fit into the image with the given starting
|
||||
coordinates. This usually means the image dimensions are too small or
|
||||
shape dimensions too large.
|
||||
|
||||
Returns
|
||||
-------
|
||||
label : tuple
|
||||
A (category, ((r0, r1), (c0, c1))) tuple specifying the category and
|
||||
bounding box coordinates of the shape.
|
||||
indices : 2-D array
|
||||
A mask of indices that the shape fills.
|
||||
|
||||
"""
|
||||
if shape[0] == 1 or shape[1] == 1:
|
||||
raise ValueError('dimension must be > 1 for triangles')
|
||||
available_side = min(image[1] - point[1], point[0],
|
||||
shape[1]) - shape[0]
|
||||
side = shape[0] + random.integers(max(1, available_side)) - 1
|
||||
triangle_height = int(np.ceil(np.sqrt(3 / 4.0) * side))
|
||||
triangle = draw_polygon([
|
||||
point[0],
|
||||
point[0] - triangle_height,
|
||||
point[0],
|
||||
], [
|
||||
point[1],
|
||||
point[1] + side // 2,
|
||||
point[1] + side,
|
||||
])
|
||||
label = ('triangle', ((point[0] - triangle_height, point[0] + 1),
|
||||
(point[1], point[1] + side + 1)))
|
||||
|
||||
return triangle, label
|
||||
|
||||
|
||||
def _generate_ellipse_mask(point, image, shape, random):
|
||||
"""Generate a mask for a filled ellipse shape.
|
||||
|
||||
The rotation, major and minor semi-axes of the ellipse are generated
|
||||
randomly.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
point : tuple
|
||||
The row and column of the top left corner of the rectangle.
|
||||
image : tuple
|
||||
The height, width and depth of the image into which the shape is
|
||||
placed.
|
||||
shape : tuple
|
||||
The minimum and maximum size and color of the shape to fit.
|
||||
random : `numpy.random.Generator`
|
||||
The random state to use for random sampling.
|
||||
|
||||
Raises
|
||||
------
|
||||
ArithmeticError
|
||||
When a shape cannot be fit into the image with the given starting
|
||||
coordinates. This usually means the image dimensions are too small or
|
||||
shape dimensions too large.
|
||||
|
||||
Returns
|
||||
-------
|
||||
label : tuple
|
||||
A (category, ((r0, r1), (c0, c1))) tuple specifying the category and
|
||||
bounding box coordinates of the shape.
|
||||
indices : 2-D array
|
||||
A mask of indices that the shape fills.
|
||||
"""
|
||||
if shape[0] == 1 or shape[1] == 1:
|
||||
raise ValueError('size must be > 1 for ellipses')
|
||||
min_radius = shape[0] / 2.0
|
||||
max_radius = shape[1] / 2.0
|
||||
left = point[1]
|
||||
right = image[1] - point[1]
|
||||
top = point[0]
|
||||
bottom = image[0] - point[0]
|
||||
available_radius = min(left, right, top, bottom, max_radius)
|
||||
if available_radius < min_radius:
|
||||
raise ArithmeticError('cannot fit shape to image')
|
||||
# NOTE: very conservative because we could take into account the fact that
|
||||
# we have 2 different radii, but this is a good first approximation.
|
||||
# Also, we can afford to have a uniform sampling because the ellipse will
|
||||
# be rotated.
|
||||
r_radius = random.uniform(min_radius, available_radius + 1)
|
||||
c_radius = random.uniform(min_radius, available_radius + 1)
|
||||
rotation = random.uniform(-np.pi, np.pi)
|
||||
ellipse = draw_ellipse(
|
||||
point[0],
|
||||
point[1],
|
||||
r_radius,
|
||||
c_radius,
|
||||
shape=image[:2],
|
||||
rotation=rotation,
|
||||
)
|
||||
max_radius = math.ceil(max(r_radius, c_radius))
|
||||
min_x = np.min(ellipse[0])
|
||||
max_x = np.max(ellipse[0]) + 1
|
||||
min_y = np.min(ellipse[1])
|
||||
max_y = np.max(ellipse[1]) + 1
|
||||
label = ('ellipse', ((min_x, max_x), (min_y, max_y)))
|
||||
|
||||
return ellipse, label
|
||||
|
||||
|
||||
# Allows lookup by key as well as random selection.
|
||||
SHAPE_GENERATORS = dict(
|
||||
rectangle=_generate_rectangle_mask,
|
||||
circle=_generate_circle_mask,
|
||||
triangle=_generate_triangle_mask,
|
||||
ellipse=_generate_ellipse_mask)
|
||||
SHAPE_CHOICES = list(SHAPE_GENERATORS.values())
|
||||
|
||||
|
||||
def _generate_random_colors(num_colors, num_channels, intensity_range, random):
|
||||
"""Generate an array of random colors.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
num_colors : int
|
||||
Number of colors to generate.
|
||||
num_channels : int
|
||||
Number of elements representing color.
|
||||
intensity_range : {tuple of tuples of ints, tuple of ints}, optional
|
||||
The range of values to sample pixel values from. For grayscale images
|
||||
the format is (min, max). For multichannel - ((min, max),) if the
|
||||
ranges are equal across the channels, and
|
||||
((min_0, max_0), ... (min_N, max_N)) if they differ.
|
||||
random : `numpy.random.Generator`
|
||||
The random state to use for random sampling.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
When the `intensity_range` is not in the interval (0, 255).
|
||||
|
||||
Returns
|
||||
-------
|
||||
colors : array
|
||||
An array of shape (num_colors, num_channels), where the values for
|
||||
each channel are drawn from the corresponding `intensity_range`.
|
||||
|
||||
"""
|
||||
if num_channels == 1:
|
||||
intensity_range = (intensity_range, )
|
||||
elif len(intensity_range) == 1:
|
||||
intensity_range = intensity_range * num_channels
|
||||
colors = [random.integers(r[0], r[1] + 1, size=num_colors)
|
||||
for r in intensity_range]
|
||||
return np.transpose(colors)
|
||||
|
||||
|
||||
def random_shapes(image_shape,
|
||||
max_shapes,
|
||||
min_shapes=1,
|
||||
min_size=2,
|
||||
max_size=None,
|
||||
num_channels=3,
|
||||
shape=None,
|
||||
intensity_range=None,
|
||||
allow_overlap=False,
|
||||
num_trials=100,
|
||||
random_seed=None,
|
||||
*,
|
||||
channel_axis=-1):
|
||||
"""Generate an image with random shapes, labeled with bounding boxes.
|
||||
|
||||
The image is populated with random shapes with random sizes, random
|
||||
locations, and random colors, with or without overlap.
|
||||
|
||||
Shapes have random (row, col) starting coordinates and random sizes bounded
|
||||
by `min_size` and `max_size`. It can occur that a randomly generated shape
|
||||
will not fit the image at all. In that case, the algorithm will try again
|
||||
with new starting coordinates a certain number of times. However, it also
|
||||
means that some shapes may be skipped altogether. In that case, this
|
||||
function will generate fewer shapes than requested.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image_shape : tuple
|
||||
The number of rows and columns of the image to generate.
|
||||
max_shapes : int
|
||||
The maximum number of shapes to (attempt to) fit into the shape.
|
||||
min_shapes : int, optional
|
||||
The minimum number of shapes to (attempt to) fit into the shape.
|
||||
min_size : int, optional
|
||||
The minimum dimension of each shape to fit into the image.
|
||||
max_size : int, optional
|
||||
The maximum dimension of each shape to fit into the image.
|
||||
num_channels : int, optional
|
||||
Number of channels in the generated image. If 1, generate monochrome
|
||||
images, else color images with multiple channels. Ignored if
|
||||
``multichannel`` is set to False.
|
||||
shape : {rectangle, circle, triangle, ellipse, None} str, optional
|
||||
The name of the shape to generate or `None` to pick random ones.
|
||||
intensity_range : {tuple of tuples of uint8, tuple of uint8}, optional
|
||||
The range of values to sample pixel values from. For grayscale
|
||||
images the format is (min, max). For multichannel - ((min, max),)
|
||||
if the ranges are equal across the channels, and
|
||||
((min_0, max_0), ... (min_N, max_N)) if they differ. As the
|
||||
function supports generation of uint8 arrays only, the maximum
|
||||
range is (0, 255). If None, set to (0, 254) for each channel
|
||||
reserving color of intensity = 255 for background.
|
||||
allow_overlap : bool, optional
|
||||
If `True`, allow shapes to overlap.
|
||||
num_trials : int, optional
|
||||
How often to attempt to fit a shape into the image before skipping it.
|
||||
random_seed : {None, int, `numpy.random.Generator`}, optional
|
||||
If `random_seed` is None the `numpy.random.Generator` singleton is
|
||||
used.
|
||||
If `random_seed` is an int, a new ``Generator`` instance is used,
|
||||
seeded with `random_seed`.
|
||||
If `random_seed` is already a ``Generator`` instance then that instance
|
||||
is used.
|
||||
channel_axis : int or None, optional
|
||||
If None, the image is assumed to be a grayscale (single channel) image.
|
||||
Otherwise, this parameter indicates which axis of the array corresponds
|
||||
to channels.
|
||||
|
||||
.. versionadded:: 0.19
|
||||
``channel_axis`` was added in 0.19.
|
||||
|
||||
Returns
|
||||
-------
|
||||
image : uint8 array
|
||||
An image with the fitted shapes.
|
||||
labels : list
|
||||
A list of labels, one per shape in the image. Each label is a
|
||||
(category, ((r0, r1), (c0, c1))) tuple specifying the category and
|
||||
bounding box coordinates of the shape.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import skimage.draw
|
||||
>>> image, labels = skimage.draw.random_shapes((32, 32), max_shapes=3)
|
||||
>>> image # doctest: +SKIP
|
||||
array([
|
||||
[[255, 255, 255],
|
||||
[255, 255, 255],
|
||||
[255, 255, 255],
|
||||
...,
|
||||
[255, 255, 255],
|
||||
[255, 255, 255],
|
||||
[255, 255, 255]]], dtype=uint8)
|
||||
>>> labels # doctest: +SKIP
|
||||
[('circle', ((22, 18), (25, 21))),
|
||||
('triangle', ((5, 6), (13, 13)))]
|
||||
"""
|
||||
if min_size > image_shape[0] or min_size > image_shape[1]:
|
||||
raise ValueError('Minimum dimension must be less than ncols and nrows')
|
||||
max_size = max_size or max(image_shape[0], image_shape[1])
|
||||
|
||||
if channel_axis is None:
|
||||
num_channels = 1
|
||||
|
||||
if intensity_range is None:
|
||||
intensity_range = (0, 254) if num_channels == 1 else ((0, 254), )
|
||||
else:
|
||||
tmp = (intensity_range, ) if num_channels == 1 else intensity_range
|
||||
for intensity_pair in tmp:
|
||||
for intensity in intensity_pair:
|
||||
if not (0 <= intensity <= 255):
|
||||
msg = 'Intensity range must lie within (0, 255) interval'
|
||||
raise ValueError(msg)
|
||||
|
||||
random = np.random.default_rng(random_seed)
|
||||
user_shape = shape
|
||||
image_shape = (image_shape[0], image_shape[1], num_channels)
|
||||
image = np.full(image_shape, 255, dtype=np.uint8)
|
||||
filled = np.zeros(image_shape, dtype=bool)
|
||||
labels = []
|
||||
|
||||
num_shapes = random.integers(min_shapes, max_shapes + 1)
|
||||
colors = _generate_random_colors(num_shapes, num_channels,
|
||||
intensity_range, random)
|
||||
shape = (min_size, max_size)
|
||||
for shape_idx in range(num_shapes):
|
||||
if user_shape is None:
|
||||
shape_generator = random.choice(SHAPE_CHOICES)
|
||||
else:
|
||||
shape_generator = SHAPE_GENERATORS[user_shape]
|
||||
for _ in range(num_trials):
|
||||
# Pick start coordinates.
|
||||
column = random.integers(max(1, image_shape[1] - min_size))
|
||||
row = random.integers(max(1, image_shape[0] - min_size))
|
||||
point = (row, column)
|
||||
try:
|
||||
indices, label = shape_generator(point, image_shape, shape,
|
||||
random)
|
||||
except ArithmeticError:
|
||||
# Couldn't fit the shape, skip it.
|
||||
indices = []
|
||||
continue
|
||||
# Check if there is an overlap where the mask is nonzero.
|
||||
if allow_overlap or not filled[indices].any():
|
||||
image[indices] = colors[shape_idx]
|
||||
filled[indices] = True
|
||||
labels.append(label)
|
||||
break
|
||||
else:
|
||||
warn('Could not fit any shapes to image, '
|
||||
'consider reducing the minimum dimension')
|
||||
|
||||
if channel_axis is None:
|
||||
image = np.squeeze(image, axis=2)
|
||||
else:
|
||||
image = np.moveaxis(image, -1, channel_axis)
|
||||
|
||||
return image, labels
|
||||
934
.CondaPkg/env/Lib/site-packages/skimage/draw/draw.py
vendored
934
.CondaPkg/env/Lib/site-packages/skimage/draw/draw.py
vendored
@@ -1,934 +0,0 @@
|
||||
import numpy as np
|
||||
|
||||
from .._shared._geometry import polygon_clip
|
||||
from .._shared.version_requirements import require
|
||||
from ._draw import (_coords_inside_image, _line, _line_aa,
|
||||
_polygon, _ellipse_perimeter,
|
||||
_circle_perimeter, _circle_perimeter_aa,
|
||||
_bezier_curve)
|
||||
|
||||
|
||||
def _ellipse_in_shape(shape, center, radii, rotation=0.):
|
||||
"""Generate coordinates of points within ellipse bounded by shape.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
shape : iterable of ints
|
||||
Shape of the input image. Must be at least length 2. Only the first
|
||||
two values are used to determine the extent of the input image.
|
||||
center : iterable of floats
|
||||
(row, column) position of center inside the given shape.
|
||||
radii : iterable of floats
|
||||
Size of two half axes (for row and column)
|
||||
rotation : float, optional
|
||||
Rotation of the ellipse defined by the above, in radians
|
||||
in range (-PI, PI), in contra clockwise direction,
|
||||
with respect to the column-axis.
|
||||
|
||||
Returns
|
||||
-------
|
||||
rows : iterable of ints
|
||||
Row coordinates representing values within the ellipse.
|
||||
cols : iterable of ints
|
||||
Corresponding column coordinates representing values within the ellipse.
|
||||
"""
|
||||
r_lim, c_lim = np.ogrid[0:float(shape[0]), 0:float(shape[1])]
|
||||
r_org, c_org = center
|
||||
r_rad, c_rad = radii
|
||||
rotation %= np.pi
|
||||
sin_alpha, cos_alpha = np.sin(rotation), np.cos(rotation)
|
||||
r, c = (r_lim - r_org), (c_lim - c_org)
|
||||
distances = ((r * cos_alpha + c * sin_alpha) / r_rad) ** 2 \
|
||||
+ ((r * sin_alpha - c * cos_alpha) / c_rad) ** 2
|
||||
return np.nonzero(distances < 1)
|
||||
|
||||
|
||||
def ellipse(r, c, r_radius, c_radius, shape=None, rotation=0.):
|
||||
"""Generate coordinates of pixels within ellipse.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
r, c : double
|
||||
Centre coordinate of ellipse.
|
||||
r_radius, c_radius : double
|
||||
Minor and major semi-axes. ``(r/r_radius)**2 + (c/c_radius)**2 = 1``.
|
||||
shape : tuple, optional
|
||||
Image shape which is used to determine the maximum extent of output pixel
|
||||
coordinates. This is useful for ellipses which exceed the image size.
|
||||
By default the full extent of the ellipse are used. Must be at least
|
||||
length 2. Only the first two values are used to determine the extent.
|
||||
rotation : float, optional (default 0.)
|
||||
Set the ellipse rotation (rotation) in range (-PI, PI)
|
||||
in contra clock wise direction, so PI/2 degree means swap ellipse axis
|
||||
|
||||
Returns
|
||||
-------
|
||||
rr, cc : ndarray of int
|
||||
Pixel coordinates of ellipse.
|
||||
May be used to directly index into an array, e.g.
|
||||
``img[rr, cc] = 1``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.draw import ellipse
|
||||
>>> img = np.zeros((10, 12), dtype=np.uint8)
|
||||
>>> rr, cc = ellipse(5, 6, 3, 5, rotation=np.deg2rad(30))
|
||||
>>> img[rr, cc] = 1
|
||||
>>> img
|
||||
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, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
||||
|
||||
Notes
|
||||
-----
|
||||
The ellipse equation::
|
||||
|
||||
((x * cos(alpha) + y * sin(alpha)) / x_radius) ** 2 +
|
||||
((x * sin(alpha) - y * cos(alpha)) / y_radius) ** 2 = 1
|
||||
|
||||
|
||||
Note that the positions of `ellipse` without specified `shape` can have
|
||||
also, negative values, as this is correct on the plane. On the other hand
|
||||
using these ellipse positions for an image afterwards may lead to appearing
|
||||
on the other side of image, because ``image[-1, -1] = image[end-1, end-1]``
|
||||
|
||||
>>> rr, cc = ellipse(1, 2, 3, 6)
|
||||
>>> img = np.zeros((6, 12), dtype=np.uint8)
|
||||
>>> img[rr, cc] = 1
|
||||
>>> img
|
||||
array([[1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1]], dtype=uint8)
|
||||
"""
|
||||
|
||||
center = np.array([r, c])
|
||||
radii = np.array([r_radius, c_radius])
|
||||
# allow just rotation with in range +/- 180 degree
|
||||
rotation %= np.pi
|
||||
|
||||
# compute rotated radii by given rotation
|
||||
r_radius_rot = abs(r_radius * np.cos(rotation)) \
|
||||
+ c_radius * np.sin(rotation)
|
||||
c_radius_rot = r_radius * np.sin(rotation) \
|
||||
+ abs(c_radius * np.cos(rotation))
|
||||
# The upper_left and lower_right corners of the smallest rectangle
|
||||
# containing the ellipse.
|
||||
radii_rot = np.array([r_radius_rot, c_radius_rot])
|
||||
upper_left = np.ceil(center - radii_rot).astype(int)
|
||||
lower_right = np.floor(center + radii_rot).astype(int)
|
||||
|
||||
if shape is not None:
|
||||
# Constrain upper_left and lower_right by shape boundary.
|
||||
upper_left = np.maximum(upper_left, np.array([0, 0]))
|
||||
lower_right = np.minimum(lower_right, np.array(shape[:2]) - 1)
|
||||
|
||||
shifted_center = center - upper_left
|
||||
bounding_shape = lower_right - upper_left + 1
|
||||
|
||||
rr, cc = _ellipse_in_shape(bounding_shape, shifted_center, radii, rotation)
|
||||
rr.flags.writeable = True
|
||||
cc.flags.writeable = True
|
||||
rr += upper_left[0]
|
||||
cc += upper_left[1]
|
||||
return rr, cc
|
||||
|
||||
|
||||
def disk(center, radius, *, shape=None):
|
||||
"""Generate coordinates of pixels within circle.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
center : tuple
|
||||
Center coordinate of disk.
|
||||
radius : double
|
||||
Radius of disk.
|
||||
shape : tuple, optional
|
||||
Image shape as a tuple of size 2. Determines the maximum
|
||||
extent of output pixel coordinates. This is useful for disks that
|
||||
exceed the image size. If None, the full extent of the disk is used.
|
||||
The shape might result in negative coordinates and wraparound
|
||||
behaviour.
|
||||
|
||||
Returns
|
||||
-------
|
||||
rr, cc : ndarray of int
|
||||
Pixel coordinates of disk.
|
||||
May be used to directly index into an array, e.g.
|
||||
``img[rr, cc] = 1``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> from skimage.draw import disk
|
||||
>>> shape = (4, 4)
|
||||
>>> img = np.zeros(shape, dtype=np.uint8)
|
||||
>>> rr, cc = disk((0, 0), 2, shape=shape)
|
||||
>>> img[rr, cc] = 1
|
||||
>>> img
|
||||
array([[1, 1, 0, 0],
|
||||
[1, 1, 0, 0],
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 0, 0]], dtype=uint8)
|
||||
>>> img = np.zeros(shape, dtype=np.uint8)
|
||||
>>> # Negative coordinates in rr and cc perform a wraparound
|
||||
>>> rr, cc = disk((0, 0), 2, shape=None)
|
||||
>>> img[rr, cc] = 1
|
||||
>>> img
|
||||
array([[1, 1, 0, 1],
|
||||
[1, 1, 0, 1],
|
||||
[0, 0, 0, 0],
|
||||
[1, 1, 0, 1]], dtype=uint8)
|
||||
>>> img = np.zeros((10, 10), dtype=np.uint8)
|
||||
>>> rr, cc = disk((4, 4), 5)
|
||||
>>> img[rr, cc] = 1
|
||||
>>> img
|
||||
array([[0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
||||
"""
|
||||
r, c = center
|
||||
return ellipse(r, c, radius, radius, shape)
|
||||
|
||||
|
||||
@require("matplotlib", ">=3.3")
|
||||
def polygon_perimeter(r, c, shape=None, clip=False):
|
||||
"""Generate polygon perimeter coordinates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
r : (N,) ndarray
|
||||
Row coordinates of vertices of polygon.
|
||||
c : (N,) ndarray
|
||||
Column coordinates of vertices of polygon.
|
||||
shape : tuple, optional
|
||||
Image shape which is used to determine maximum extents of output pixel
|
||||
coordinates. This is useful for polygons that exceed the image size.
|
||||
If None, the full extents of the polygon is used. Must be at least
|
||||
length 2. Only the first two values are used to determine the extent of
|
||||
the input image.
|
||||
clip : bool, optional
|
||||
Whether to clip the polygon to the provided shape. If this is set
|
||||
to True, the drawn figure will always be a closed polygon with all
|
||||
edges visible.
|
||||
|
||||
Returns
|
||||
-------
|
||||
rr, cc : ndarray of int
|
||||
Pixel coordinates of polygon.
|
||||
May be used to directly index into an array, e.g.
|
||||
``img[rr, cc] = 1``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.draw import polygon_perimeter
|
||||
>>> img = np.zeros((10, 10), dtype=np.uint8)
|
||||
>>> rr, cc = polygon_perimeter([5, -1, 5, 10],
|
||||
... [-1, 5, 11, 5],
|
||||
... shape=img.shape, clip=True)
|
||||
>>> img[rr, cc] = 1
|
||||
>>> img
|
||||
array([[0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0, 0, 1, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[0, 1, 1, 0, 0, 0, 0, 0, 0, 1],
|
||||
[0, 0, 0, 1, 0, 0, 0, 1, 1, 0],
|
||||
[0, 0, 0, 0, 1, 1, 1, 0, 0, 0]], dtype=uint8)
|
||||
|
||||
"""
|
||||
if clip:
|
||||
if shape is None:
|
||||
raise ValueError("Must specify clipping shape")
|
||||
clip_box = np.array([0, 0, shape[0] - 1, shape[1] - 1])
|
||||
else:
|
||||
clip_box = np.array([np.min(r), np.min(c),
|
||||
np.max(r), np.max(c)])
|
||||
|
||||
# Do the clipping irrespective of whether clip is set. This
|
||||
# ensures that the returned polygon is closed and is an array.
|
||||
r, c = polygon_clip(r, c, *clip_box)
|
||||
|
||||
r = np.round(r).astype(int)
|
||||
c = np.round(c).astype(int)
|
||||
|
||||
# Construct line segments
|
||||
rr, cc = [], []
|
||||
for i in range(len(r) - 1):
|
||||
line_r, line_c = line(r[i], c[i], r[i + 1], c[i + 1])
|
||||
rr.extend(line_r)
|
||||
cc.extend(line_c)
|
||||
|
||||
rr = np.asarray(rr)
|
||||
cc = np.asarray(cc)
|
||||
|
||||
if shape is None:
|
||||
return rr, cc
|
||||
else:
|
||||
return _coords_inside_image(rr, cc, shape)
|
||||
|
||||
|
||||
def set_color(image, coords, color, alpha=1):
|
||||
"""Set pixel color in the image at the given coordinates.
|
||||
|
||||
Note that this function modifies the color of the image in-place.
|
||||
Coordinates that exceed the shape of the image will be ignored.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : (M, N, D) ndarray
|
||||
Image
|
||||
coords : tuple of ((P,) ndarray, (P,) ndarray)
|
||||
Row and column coordinates of pixels to be colored.
|
||||
color : (D,) ndarray
|
||||
Color to be assigned to coordinates in the image.
|
||||
alpha : scalar or (N,) ndarray
|
||||
Alpha values used to blend color with image. 0 is transparent,
|
||||
1 is opaque.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.draw import line, set_color
|
||||
>>> img = np.zeros((10, 10), dtype=np.uint8)
|
||||
>>> rr, cc = line(1, 1, 20, 20)
|
||||
>>> set_color(img, (rr, cc), 1)
|
||||
>>> img
|
||||
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1]], dtype=uint8)
|
||||
|
||||
"""
|
||||
rr, cc = coords
|
||||
|
||||
if image.ndim == 2:
|
||||
image = image[..., np.newaxis]
|
||||
|
||||
color = np.array(color, ndmin=1, copy=False)
|
||||
|
||||
if image.shape[-1] != color.shape[-1]:
|
||||
raise ValueError(f'Color shape ({color.shape[0]}) must match last '
|
||||
'image dimension ({image.shape[-1]}).')
|
||||
|
||||
if np.isscalar(alpha):
|
||||
# Can be replaced by ``full_like`` when numpy 1.8 becomes
|
||||
# minimum dependency
|
||||
alpha = np.ones_like(rr) * alpha
|
||||
|
||||
rr, cc, alpha = _coords_inside_image(rr, cc, image.shape, val=alpha)
|
||||
|
||||
alpha = alpha[..., np.newaxis]
|
||||
|
||||
color = color * alpha
|
||||
vals = image[rr, cc] * (1 - alpha)
|
||||
|
||||
image[rr, cc] = vals + color
|
||||
|
||||
|
||||
def line(r0, c0, r1, c1):
|
||||
"""Generate line pixel coordinates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
r0, c0 : int
|
||||
Starting position (row, column).
|
||||
r1, c1 : int
|
||||
End position (row, column).
|
||||
|
||||
Returns
|
||||
-------
|
||||
rr, cc : (N,) ndarray of int
|
||||
Indices of pixels that belong to the line.
|
||||
May be used to directly index into an array, e.g.
|
||||
``img[rr, cc] = 1``.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Anti-aliased line generator is available with `line_aa`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.draw import line
|
||||
>>> img = np.zeros((10, 10), dtype=np.uint8)
|
||||
>>> rr, cc = line(1, 1, 8, 8)
|
||||
>>> img[rr, cc] = 1
|
||||
>>> img
|
||||
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
||||
"""
|
||||
return _line(r0, c0, r1, c1)
|
||||
|
||||
|
||||
def line_aa(r0, c0, r1, c1):
|
||||
"""Generate anti-aliased line pixel coordinates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
r0, c0 : int
|
||||
Starting position (row, column).
|
||||
r1, c1 : int
|
||||
End position (row, column).
|
||||
|
||||
Returns
|
||||
-------
|
||||
rr, cc, val : (N,) ndarray (int, int, float)
|
||||
Indices of pixels (`rr`, `cc`) and intensity values (`val`).
|
||||
``img[rr, cc] = val``.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] A Rasterizing Algorithm for Drawing Curves, A. Zingl, 2012
|
||||
http://members.chello.at/easyfilter/Bresenham.pdf
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.draw import line_aa
|
||||
>>> img = np.zeros((10, 10), dtype=np.uint8)
|
||||
>>> rr, cc, val = line_aa(1, 1, 8, 8)
|
||||
>>> img[rr, cc] = val * 255
|
||||
>>> img
|
||||
array([[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[ 0, 255, 74, 0, 0, 0, 0, 0, 0, 0],
|
||||
[ 0, 74, 255, 74, 0, 0, 0, 0, 0, 0],
|
||||
[ 0, 0, 74, 255, 74, 0, 0, 0, 0, 0],
|
||||
[ 0, 0, 0, 74, 255, 74, 0, 0, 0, 0],
|
||||
[ 0, 0, 0, 0, 74, 255, 74, 0, 0, 0],
|
||||
[ 0, 0, 0, 0, 0, 74, 255, 74, 0, 0],
|
||||
[ 0, 0, 0, 0, 0, 0, 74, 255, 74, 0],
|
||||
[ 0, 0, 0, 0, 0, 0, 0, 74, 255, 0],
|
||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
||||
"""
|
||||
return _line_aa(r0, c0, r1, c1)
|
||||
|
||||
|
||||
def polygon(r, c, shape=None):
|
||||
"""Generate coordinates of pixels within polygon.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
r : (N,) ndarray
|
||||
Row coordinates of vertices of polygon.
|
||||
c : (N,) ndarray
|
||||
Column coordinates of vertices of polygon.
|
||||
shape : tuple, optional
|
||||
Image shape which is used to determine the maximum extent of output
|
||||
pixel coordinates. This is useful for polygons that exceed the image
|
||||
size. If None, the full extent of the polygon is used. Must be at
|
||||
least length 2. Only the first two values are used to determine the
|
||||
extent of the input image.
|
||||
|
||||
Returns
|
||||
-------
|
||||
rr, cc : ndarray of int
|
||||
Pixel coordinates of polygon.
|
||||
May be used to directly index into an array, e.g.
|
||||
``img[rr, cc] = 1``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.draw import polygon
|
||||
>>> img = np.zeros((10, 10), dtype=np.uint8)
|
||||
>>> r = np.array([1, 2, 8])
|
||||
>>> c = np.array([1, 7, 4])
|
||||
>>> rr, cc = polygon(r, c)
|
||||
>>> img[rr, cc] = 1
|
||||
>>> img
|
||||
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 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]], dtype=uint8)
|
||||
|
||||
"""
|
||||
return _polygon(r, c, shape)
|
||||
|
||||
|
||||
def circle_perimeter(r, c, radius, method='bresenham', shape=None):
|
||||
"""Generate circle perimeter coordinates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
r, c : int
|
||||
Centre coordinate of circle.
|
||||
radius : int
|
||||
Radius of circle.
|
||||
method : {'bresenham', 'andres'}, optional
|
||||
bresenham : Bresenham method (default)
|
||||
andres : Andres method
|
||||
shape : tuple, optional
|
||||
Image shape which is used to determine the maximum extent of output
|
||||
pixel coordinates. This is useful for circles that exceed the image
|
||||
size. If None, the full extent of the circle is used. Must be at least
|
||||
length 2. Only the first two values are used to determine the extent of
|
||||
the input image.
|
||||
|
||||
Returns
|
||||
-------
|
||||
rr, cc : (N,) ndarray of int
|
||||
Bresenham and Andres' method:
|
||||
Indices of pixels that belong to the circle perimeter.
|
||||
May be used to directly index into an array, e.g.
|
||||
``img[rr, cc] = 1``.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Andres method presents the advantage that concentric
|
||||
circles create a disc whereas Bresenham can make holes. There
|
||||
is also less distortions when Andres circles are rotated.
|
||||
Bresenham method is also known as midpoint circle algorithm.
|
||||
Anti-aliased circle generator is available with `circle_perimeter_aa`.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] J.E. Bresenham, "Algorithm for computer control of a digital
|
||||
plotter", IBM Systems journal, 4 (1965) 25-30.
|
||||
.. [2] E. Andres, "Discrete circles, rings and spheres", Computers &
|
||||
Graphics, 18 (1994) 695-706.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.draw import circle_perimeter
|
||||
>>> img = np.zeros((10, 10), dtype=np.uint8)
|
||||
>>> rr, cc = circle_perimeter(4, 4, 3)
|
||||
>>> img[rr, cc] = 1
|
||||
>>> img
|
||||
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
||||
"""
|
||||
return _circle_perimeter(r, c, radius, method, shape)
|
||||
|
||||
|
||||
def circle_perimeter_aa(r, c, radius, shape=None):
|
||||
"""Generate anti-aliased circle perimeter coordinates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
r, c : int
|
||||
Centre coordinate of circle.
|
||||
radius : int
|
||||
Radius of circle.
|
||||
shape : tuple, optional
|
||||
Image shape which is used to determine the maximum extent of output
|
||||
pixel coordinates. This is useful for circles that exceed the image
|
||||
size. If None, the full extent of the circle is used. Must be at least
|
||||
length 2. Only the first two values are used to determine the extent of
|
||||
the input image.
|
||||
|
||||
Returns
|
||||
-------
|
||||
rr, cc, val : (N,) ndarray (int, int, float)
|
||||
Indices of pixels (`rr`, `cc`) and intensity values (`val`).
|
||||
``img[rr, cc] = val``.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Wu's method draws anti-aliased circle. This implementation doesn't use
|
||||
lookup table optimization.
|
||||
|
||||
Use the function ``draw.set_color`` to apply ``circle_perimeter_aa``
|
||||
results to color images.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] X. Wu, "An efficient antialiasing technique", In ACM SIGGRAPH
|
||||
Computer Graphics, 25 (1991) 143-152.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.draw import circle_perimeter_aa
|
||||
>>> img = np.zeros((10, 10), dtype=np.uint8)
|
||||
>>> rr, cc, val = circle_perimeter_aa(4, 4, 3)
|
||||
>>> img[rr, cc] = val * 255
|
||||
>>> img
|
||||
array([[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[ 0, 0, 60, 211, 255, 211, 60, 0, 0, 0],
|
||||
[ 0, 60, 194, 43, 0, 43, 194, 60, 0, 0],
|
||||
[ 0, 211, 43, 0, 0, 0, 43, 211, 0, 0],
|
||||
[ 0, 255, 0, 0, 0, 0, 0, 255, 0, 0],
|
||||
[ 0, 211, 43, 0, 0, 0, 43, 211, 0, 0],
|
||||
[ 0, 60, 194, 43, 0, 43, 194, 60, 0, 0],
|
||||
[ 0, 0, 60, 211, 255, 211, 60, 0, 0, 0],
|
||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
||||
|
||||
>>> from skimage import data, draw
|
||||
>>> image = data.chelsea()
|
||||
>>> rr, cc, val = draw.circle_perimeter_aa(r=100, c=100, radius=75)
|
||||
>>> draw.set_color(image, (rr, cc), [1, 0, 0], alpha=val)
|
||||
"""
|
||||
return _circle_perimeter_aa(r, c, radius, shape)
|
||||
|
||||
|
||||
def ellipse_perimeter(r, c, r_radius, c_radius, orientation=0, shape=None):
|
||||
"""Generate ellipse perimeter coordinates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
r, c : int
|
||||
Centre coordinate of ellipse.
|
||||
r_radius, c_radius : int
|
||||
Minor and major semi-axes. ``(r/r_radius)**2 + (c/c_radius)**2 = 1``.
|
||||
orientation : double, optional
|
||||
Major axis orientation in clockwise direction as radians.
|
||||
shape : tuple, optional
|
||||
Image shape which is used to determine the maximum extent of output
|
||||
pixel coordinates. This is useful for ellipses that exceed the image
|
||||
size. If None, the full extent of the ellipse is used. Must be at
|
||||
least length 2. Only the first two values are used to determine the
|
||||
extent of the input image.
|
||||
|
||||
Returns
|
||||
-------
|
||||
rr, cc : (N,) ndarray of int
|
||||
Indices of pixels that belong to the ellipse perimeter.
|
||||
May be used to directly index into an array, e.g.
|
||||
``img[rr, cc] = 1``.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] A Rasterizing Algorithm for Drawing Curves, A. Zingl, 2012
|
||||
http://members.chello.at/easyfilter/Bresenham.pdf
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.draw import ellipse_perimeter
|
||||
>>> img = np.zeros((10, 10), dtype=np.uint8)
|
||||
>>> rr, cc = ellipse_perimeter(5, 5, 3, 4)
|
||||
>>> img[rr, cc] = 1
|
||||
>>> img
|
||||
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0, 0, 1, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[0, 0, 1, 0, 0, 0, 0, 0, 1, 0],
|
||||
[0, 0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
||||
|
||||
|
||||
Note that the positions of `ellipse` without specified `shape` can have
|
||||
also, negative values, as this is correct on the plane. On the other hand
|
||||
using these ellipse positions for an image afterwards may lead to appearing
|
||||
on the other side of image, because ``image[-1, -1] = image[end-1, end-1]``
|
||||
|
||||
>>> rr, cc = ellipse_perimeter(2, 3, 4, 5)
|
||||
>>> img = np.zeros((9, 12), dtype=np.uint8)
|
||||
>>> img[rr, cc] = 1
|
||||
>>> img
|
||||
array([[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
|
||||
[1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]], dtype=uint8)
|
||||
"""
|
||||
return _ellipse_perimeter(r, c, r_radius, c_radius, orientation, shape)
|
||||
|
||||
|
||||
def bezier_curve(r0, c0, r1, c1, r2, c2, weight, shape=None):
|
||||
"""Generate Bezier curve coordinates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
r0, c0 : int
|
||||
Coordinates of the first control point.
|
||||
r1, c1 : int
|
||||
Coordinates of the middle control point.
|
||||
r2, c2 : int
|
||||
Coordinates of the last control point.
|
||||
weight : double
|
||||
Middle control point weight, it describes the line tension.
|
||||
shape : tuple, optional
|
||||
Image shape which is used to determine the maximum extent of output
|
||||
pixel coordinates. This is useful for curves that exceed the image
|
||||
size. If None, the full extent of the curve is used.
|
||||
|
||||
Returns
|
||||
-------
|
||||
rr, cc : (N,) ndarray of int
|
||||
Indices of pixels that belong to the Bezier curve.
|
||||
May be used to directly index into an array, e.g.
|
||||
``img[rr, cc] = 1``.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The algorithm is the rational quadratic algorithm presented in
|
||||
reference [1]_.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] A Rasterizing Algorithm for Drawing Curves, A. Zingl, 2012
|
||||
http://members.chello.at/easyfilter/Bresenham.pdf
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> from skimage.draw import bezier_curve
|
||||
>>> img = np.zeros((10, 10), dtype=np.uint8)
|
||||
>>> rr, cc = bezier_curve(1, 5, 5, -2, 8, 8, 2)
|
||||
>>> img[rr, cc] = 1
|
||||
>>> img
|
||||
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 1, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
||||
"""
|
||||
return _bezier_curve(r0, c0, r1, c1, r2, c2, weight, shape)
|
||||
|
||||
|
||||
def rectangle(start, end=None, extent=None, shape=None):
|
||||
"""Generate coordinates of pixels within a rectangle.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
start : tuple
|
||||
Origin point of the rectangle, e.g., ``([plane,] row, column)``.
|
||||
end : tuple
|
||||
End point of the rectangle ``([plane,] row, column)``.
|
||||
For a 2D matrix, the slice defined by the rectangle is
|
||||
``[start:(end+1)]``.
|
||||
Either `end` or `extent` must be specified.
|
||||
extent : tuple
|
||||
The extent (size) of the drawn rectangle. E.g.,
|
||||
``([num_planes,] num_rows, num_cols)``.
|
||||
Either `end` or `extent` must be specified.
|
||||
A negative extent is valid, and will result in a rectangle
|
||||
going along the opposite direction. If extent is negative, the
|
||||
`start` point is not included.
|
||||
shape : tuple, optional
|
||||
Image shape used to determine the maximum bounds of the output
|
||||
coordinates. This is useful for clipping rectangles that exceed
|
||||
the image size. By default, no clipping is done.
|
||||
|
||||
Returns
|
||||
-------
|
||||
coords : array of int, shape (Ndim, Npoints)
|
||||
The coordinates of all pixels in the rectangle.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This function can be applied to N-dimensional images, by passing `start` and
|
||||
`end` or `extent` as tuples of length N.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> from skimage.draw import rectangle
|
||||
>>> img = np.zeros((5, 5), dtype=np.uint8)
|
||||
>>> start = (1, 1)
|
||||
>>> extent = (3, 3)
|
||||
>>> rr, cc = rectangle(start, extent=extent, shape=img.shape)
|
||||
>>> img[rr, cc] = 1
|
||||
>>> img
|
||||
array([[0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0]], dtype=uint8)
|
||||
|
||||
|
||||
>>> img = np.zeros((5, 5), dtype=np.uint8)
|
||||
>>> start = (0, 1)
|
||||
>>> end = (3, 3)
|
||||
>>> rr, cc = rectangle(start, end=end, shape=img.shape)
|
||||
>>> img[rr, cc] = 1
|
||||
>>> img
|
||||
array([[0, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0]], dtype=uint8)
|
||||
|
||||
>>> import numpy as np
|
||||
>>> from skimage.draw import rectangle
|
||||
>>> img = np.zeros((6, 6), dtype=np.uint8)
|
||||
>>> start = (3, 3)
|
||||
>>>
|
||||
>>> rr, cc = rectangle(start, extent=(2, 2))
|
||||
>>> img[rr, cc] = 1
|
||||
>>> rr, cc = rectangle(start, extent=(-2, 2))
|
||||
>>> img[rr, cc] = 2
|
||||
>>> rr, cc = rectangle(start, extent=(-2, -2))
|
||||
>>> img[rr, cc] = 3
|
||||
>>> rr, cc = rectangle(start, extent=(2, -2))
|
||||
>>> img[rr, cc] = 4
|
||||
>>> print(img)
|
||||
[[0 0 0 0 0 0]
|
||||
[0 3 3 2 2 0]
|
||||
[0 3 3 2 2 0]
|
||||
[0 4 4 1 1 0]
|
||||
[0 4 4 1 1 0]
|
||||
[0 0 0 0 0 0]]
|
||||
|
||||
"""
|
||||
tl, br = _rectangle_slice(start=start, end=end, extent=extent)
|
||||
|
||||
if shape is not None:
|
||||
n_dim = len(start)
|
||||
br = np.minimum(shape[0:n_dim], br)
|
||||
tl = np.maximum(np.zeros_like(shape[0:n_dim]), tl)
|
||||
coords = np.meshgrid(*[np.arange(st, en) for st, en in zip(tuple(tl),
|
||||
tuple(br))])
|
||||
return coords
|
||||
|
||||
|
||||
@require("matplotlib", ">=3.3")
|
||||
def rectangle_perimeter(start, end=None, extent=None, shape=None, clip=False):
|
||||
"""Generate coordinates of pixels that are exactly around a rectangle.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
start : tuple
|
||||
Origin point of the inner rectangle, e.g., ``(row, column)``.
|
||||
end : tuple
|
||||
End point of the inner rectangle ``(row, column)``.
|
||||
For a 2D matrix, the slice defined by inner the rectangle is
|
||||
``[start:(end+1)]``.
|
||||
Either `end` or `extent` must be specified.
|
||||
extent : tuple
|
||||
The extent (size) of the inner rectangle. E.g.,
|
||||
``(num_rows, num_cols)``.
|
||||
Either `end` or `extent` must be specified.
|
||||
Negative extents are permitted. See `rectangle` to better
|
||||
understand how they behave.
|
||||
shape : tuple, optional
|
||||
Image shape used to determine the maximum bounds of the output
|
||||
coordinates. This is useful for clipping perimeters that exceed
|
||||
the image size. By default, no clipping is done. Must be at least
|
||||
length 2. Only the first two values are used to determine the extent of
|
||||
the input image.
|
||||
clip : bool, optional
|
||||
Whether to clip the perimeter to the provided shape. If this is set
|
||||
to True, the drawn figure will always be a closed polygon with all
|
||||
edges visible.
|
||||
|
||||
Returns
|
||||
-------
|
||||
coords : array of int, shape (2, Npoints)
|
||||
The coordinates of all pixels in the rectangle.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> from skimage.draw import rectangle_perimeter
|
||||
>>> img = np.zeros((5, 6), dtype=np.uint8)
|
||||
>>> start = (2, 3)
|
||||
>>> end = (3, 4)
|
||||
>>> rr, cc = rectangle_perimeter(start, end=end, shape=img.shape)
|
||||
>>> img[rr, cc] = 1
|
||||
>>> img
|
||||
array([[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1],
|
||||
[0, 0, 1, 0, 0, 1],
|
||||
[0, 0, 1, 0, 0, 1],
|
||||
[0, 0, 1, 1, 1, 1]], dtype=uint8)
|
||||
|
||||
>>> img = np.zeros((5, 5), dtype=np.uint8)
|
||||
>>> r, c = rectangle_perimeter(start, (10, 10), shape=img.shape, clip=True)
|
||||
>>> img[r, c] = 1
|
||||
>>> img
|
||||
array([[0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1],
|
||||
[0, 0, 1, 0, 1],
|
||||
[0, 0, 1, 0, 1],
|
||||
[0, 0, 1, 1, 1]], dtype=uint8)
|
||||
|
||||
"""
|
||||
top_left, bottom_right = _rectangle_slice(start=start,
|
||||
end=end,
|
||||
extent=extent)
|
||||
|
||||
top_left -= 1
|
||||
r = [top_left[0], top_left[0], bottom_right[0], bottom_right[0],
|
||||
top_left[0]]
|
||||
c = [top_left[1], bottom_right[1], bottom_right[1], top_left[1],
|
||||
top_left[1]]
|
||||
return polygon_perimeter(r, c, shape=shape, clip=clip)
|
||||
|
||||
|
||||
def _rectangle_slice(start, end=None, extent=None):
|
||||
"""Return the slice ``(top_left, bottom_right)`` of the rectangle.
|
||||
|
||||
Returns
|
||||
=======
|
||||
(top_left, bottomm_right)
|
||||
The slice you would need to select the region in the rectangle defined
|
||||
by the parameters.
|
||||
Select it like:
|
||||
|
||||
``rect[top_left[0]:bottom_right[0], top_left[1]:bottom_right[1]]``
|
||||
"""
|
||||
if end is None and extent is None:
|
||||
raise ValueError("Either `end` or `extent` must be given.")
|
||||
if end is not None and extent is not None:
|
||||
raise ValueError("Cannot provide both `end` and `extent`.")
|
||||
|
||||
if extent is not None:
|
||||
end = np.asarray(start) + np.asarray(extent)
|
||||
top_left = np.minimum(start, end)
|
||||
bottom_right = np.maximum(start, end)
|
||||
|
||||
top_left = np.round(top_left).astype(int)
|
||||
bottom_right = np.round(bottom_right).astype(int)
|
||||
|
||||
if extent is None:
|
||||
bottom_right += 1
|
||||
|
||||
return (top_left, bottom_right)
|
||||
@@ -1,114 +0,0 @@
|
||||
import numpy as np
|
||||
from scipy.special import (ellipkinc as ellip_F, ellipeinc as ellip_E)
|
||||
|
||||
|
||||
def ellipsoid(a, b, c, spacing=(1., 1., 1.), levelset=False):
|
||||
"""
|
||||
Generates ellipsoid with semimajor axes aligned with grid dimensions
|
||||
on grid with specified `spacing`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
a : float
|
||||
Length of semimajor axis aligned with x-axis.
|
||||
b : float
|
||||
Length of semimajor axis aligned with y-axis.
|
||||
c : float
|
||||
Length of semimajor axis aligned with z-axis.
|
||||
spacing : tuple of floats, length 3
|
||||
Spacing in (x, y, z) spatial dimensions.
|
||||
levelset : bool
|
||||
If True, returns the level set for this ellipsoid (signed level
|
||||
set about zero, with positive denoting interior) as np.float64.
|
||||
False returns a binarized version of said level set.
|
||||
|
||||
Returns
|
||||
-------
|
||||
ellip : (N, M, P) array
|
||||
Ellipsoid centered in a correctly sized array for given `spacing`.
|
||||
Boolean dtype unless `levelset=True`, in which case a float array is
|
||||
returned with the level set above 0.0 representing the ellipsoid.
|
||||
|
||||
"""
|
||||
if (a <= 0) or (b <= 0) or (c <= 0):
|
||||
raise ValueError('Parameters a, b, and c must all be > 0')
|
||||
|
||||
offset = np.r_[1, 1, 1] * np.r_[spacing]
|
||||
|
||||
# Calculate limits, and ensure output volume is odd & symmetric
|
||||
low = np.ceil(- np.r_[a, b, c] - offset)
|
||||
high = np.floor(np.r_[a, b, c] + offset + 1)
|
||||
|
||||
for dim in range(3):
|
||||
if (high[dim] - low[dim]) % 2 == 0:
|
||||
low[dim] -= 1
|
||||
num = np.arange(low[dim], high[dim], spacing[dim])
|
||||
if 0 not in num:
|
||||
low[dim] -= np.max(num[num < 0])
|
||||
|
||||
# Generate (anisotropic) spatial grid
|
||||
x, y, z = np.mgrid[low[0]:high[0]:spacing[0],
|
||||
low[1]:high[1]:spacing[1],
|
||||
low[2]:high[2]:spacing[2]]
|
||||
|
||||
if not levelset:
|
||||
arr = ((x / float(a)) ** 2 +
|
||||
(y / float(b)) ** 2 +
|
||||
(z / float(c)) ** 2) <= 1
|
||||
else:
|
||||
arr = ((x / float(a)) ** 2 +
|
||||
(y / float(b)) ** 2 +
|
||||
(z / float(c)) ** 2) - 1
|
||||
|
||||
return arr
|
||||
|
||||
|
||||
def ellipsoid_stats(a, b, c):
|
||||
"""
|
||||
Calculates analytical surface area and volume for ellipsoid with
|
||||
semimajor axes aligned with grid dimensions of specified `spacing`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
a : float
|
||||
Length of semimajor axis aligned with x-axis.
|
||||
b : float
|
||||
Length of semimajor axis aligned with y-axis.
|
||||
c : float
|
||||
Length of semimajor axis aligned with z-axis.
|
||||
|
||||
Returns
|
||||
-------
|
||||
vol : float
|
||||
Calculated volume of ellipsoid.
|
||||
surf : float
|
||||
Calculated surface area of ellipsoid.
|
||||
|
||||
"""
|
||||
if (a <= 0) or (b <= 0) or (c <= 0):
|
||||
raise ValueError('Parameters a, b, and c must all be > 0')
|
||||
|
||||
# Calculate volume & surface area
|
||||
# Surface calculation requires a >= b >= c and a != c.
|
||||
abc = [a, b, c]
|
||||
abc.sort(reverse=True)
|
||||
a = abc[0]
|
||||
b = abc[1]
|
||||
c = abc[2]
|
||||
|
||||
# Volume
|
||||
vol = 4 / 3. * np.pi * a * b * c
|
||||
|
||||
# Analytical ellipsoid surface area
|
||||
phi = np.arcsin((1. - (c ** 2 / (a ** 2.))) ** 0.5)
|
||||
d = float((a ** 2 - c ** 2) ** 0.5)
|
||||
m = (a ** 2 * (b ** 2 - c ** 2) /
|
||||
float(b ** 2 * (a ** 2 - c ** 2)))
|
||||
F = ellip_F(phi, m)
|
||||
E = ellip_E(phi, m)
|
||||
|
||||
surf = 2 * np.pi * (c ** 2 +
|
||||
b * c ** 2 / d * F +
|
||||
b * d * E)
|
||||
|
||||
return vol, surf
|
||||
@@ -1,110 +0,0 @@
|
||||
import numpy as np
|
||||
|
||||
|
||||
def _round_safe(coords):
|
||||
"""Round coords while ensuring successive values are less than 1 apart.
|
||||
|
||||
When rounding coordinates for `line_nd`, we want coordinates that are less
|
||||
than 1 apart (always the case, by design) to remain less than one apart.
|
||||
However, NumPy rounds values to the nearest *even* integer, so:
|
||||
|
||||
>>> np.round([0.5, 1.5, 2.5, 3.5, 4.5])
|
||||
array([0., 2., 2., 4., 4.])
|
||||
|
||||
So, for our application, we detect whether the above case occurs, and use
|
||||
``np.floor`` if so. It is sufficient to detect that the first coordinate
|
||||
falls on 0.5 and that the second coordinate is 1.0 apart, since we assume
|
||||
by construction that the inter-point distance is less than or equal to 1
|
||||
and that all successive points are equidistant.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
coords : 1D array of float
|
||||
The coordinates array. We assume that all successive values are
|
||||
equidistant (``np.all(np.diff(coords) = coords[1] - coords[0])``)
|
||||
and that this distance is no more than 1
|
||||
(``np.abs(coords[1] - coords[0]) <= 1``).
|
||||
|
||||
Returns
|
||||
-------
|
||||
rounded : 1D array of int
|
||||
The array correctly rounded for an indexing operation, such that no
|
||||
successive indices will be more than 1 apart.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> coords0 = np.array([0.5, 1.25, 2., 2.75, 3.5])
|
||||
>>> _round_safe(coords0)
|
||||
array([0, 1, 2, 3, 4])
|
||||
>>> coords1 = np.arange(0.5, 8, 1)
|
||||
>>> coords1
|
||||
array([0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5])
|
||||
>>> _round_safe(coords1)
|
||||
array([0, 1, 2, 3, 4, 5, 6, 7])
|
||||
"""
|
||||
if (len(coords) > 1
|
||||
and coords[0] % 1 == 0.5
|
||||
and coords[1] - coords[0] == 1):
|
||||
_round_function = np.floor
|
||||
else:
|
||||
_round_function = np.round
|
||||
return _round_function(coords).astype(int)
|
||||
|
||||
|
||||
def line_nd(start, stop, *, endpoint=False, integer=True):
|
||||
"""Draw a single-pixel thick line in n dimensions.
|
||||
|
||||
The line produced will be ndim-connected. That is, two subsequent
|
||||
pixels in the line will be either direct or diagonal neighbors in
|
||||
n dimensions.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
start : array-like, shape (N,)
|
||||
The start coordinates of the line.
|
||||
stop : array-like, shape (N,)
|
||||
The end coordinates of the line.
|
||||
endpoint : bool, optional
|
||||
Whether to include the endpoint in the returned line. Defaults
|
||||
to False, which allows for easy drawing of multi-point paths.
|
||||
integer : bool, optional
|
||||
Whether to round the coordinates to integer. If True (default),
|
||||
the returned coordinates can be used to directly index into an
|
||||
array. `False` could be used for e.g. vector drawing.
|
||||
|
||||
Returns
|
||||
-------
|
||||
coords : tuple of arrays
|
||||
The coordinates of points on the line.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> lin = line_nd((1, 1), (5, 2.5), endpoint=False)
|
||||
>>> lin
|
||||
(array([1, 2, 3, 4]), array([1, 1, 2, 2]))
|
||||
>>> im = np.zeros((6, 5), dtype=int)
|
||||
>>> im[lin] = 1
|
||||
>>> im
|
||||
array([[0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0]])
|
||||
>>> line_nd([2, 1, 1], [5, 5, 2.5], endpoint=True)
|
||||
(array([2, 3, 4, 4, 5]), array([1, 2, 3, 4, 5]), array([1, 1, 2, 2, 2]))
|
||||
"""
|
||||
start = np.asarray(start)
|
||||
stop = np.asarray(stop)
|
||||
npoints = int(np.ceil(np.max(np.abs(stop - start))))
|
||||
if endpoint:
|
||||
npoints += 1
|
||||
|
||||
coords = np.linspace(start, stop, num=npoints, endpoint=endpoint).T
|
||||
if integer:
|
||||
for dim in range(len(start)):
|
||||
coords[dim, :] = _round_safe(coords[dim, :])
|
||||
|
||||
coords = coords.astype(int)
|
||||
|
||||
return tuple(coords)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -1,174 +0,0 @@
|
||||
import numpy as np
|
||||
from skimage._shared.testing import assert_array_equal, assert_allclose
|
||||
|
||||
from skimage.draw import ellipsoid, ellipsoid_stats, rectangle
|
||||
from skimage._shared import testing
|
||||
|
||||
|
||||
def test_ellipsoid_sign_parameters1():
|
||||
with testing.raises(ValueError):
|
||||
ellipsoid(-1, 2, 2)
|
||||
|
||||
|
||||
def test_ellipsoid_sign_parameters2():
|
||||
with testing.raises(ValueError):
|
||||
ellipsoid(0, 2, 2)
|
||||
|
||||
|
||||
def test_ellipsoid_sign_parameters3():
|
||||
with testing.raises(ValueError):
|
||||
ellipsoid(-3, -2, 2)
|
||||
|
||||
|
||||
def test_ellipsoid_bool():
|
||||
test = ellipsoid(2, 2, 2)[1:-1, 1:-1, 1:-1]
|
||||
test_anisotropic = ellipsoid(2, 2, 4, spacing=(1., 1., 2.))
|
||||
test_anisotropic = test_anisotropic[1:-1, 1:-1, 1:-1]
|
||||
|
||||
expected = np.array([[[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, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0]],
|
||||
|
||||
[[0, 0, 1, 0, 0],
|
||||
[0, 1, 1, 1, 0],
|
||||
[1, 1, 1, 1, 1],
|
||||
[0, 1, 1, 1, 0],
|
||||
[0, 0, 1, 0, 0]],
|
||||
|
||||
[[0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 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]]])
|
||||
|
||||
assert_array_equal(test, expected.astype(bool))
|
||||
assert_array_equal(test_anisotropic, expected.astype(bool))
|
||||
|
||||
|
||||
def test_ellipsoid_levelset():
|
||||
test = ellipsoid(2, 2, 2, levelset=True)[1:-1, 1:-1, 1:-1]
|
||||
test_anisotropic = ellipsoid(2, 2, 4, spacing=(1., 1., 2.),
|
||||
levelset=True)
|
||||
test_anisotropic = test_anisotropic[1:-1, 1:-1, 1:-1]
|
||||
|
||||
expected = np.array([[[ 2. , 1.25, 1. , 1.25, 2. ],
|
||||
[ 1.25, 0.5 , 0.25, 0.5 , 1.25],
|
||||
[ 1. , 0.25, 0. , 0.25, 1. ],
|
||||
[ 1.25, 0.5 , 0.25, 0.5 , 1.25],
|
||||
[ 2. , 1.25, 1. , 1.25, 2. ]],
|
||||
|
||||
[[ 1.25, 0.5 , 0.25, 0.5 , 1.25],
|
||||
[ 0.5 , -0.25, -0.5 , -0.25, 0.5 ],
|
||||
[ 0.25, -0.5 , -0.75, -0.5 , 0.25],
|
||||
[ 0.5 , -0.25, -0.5 , -0.25, 0.5 ],
|
||||
[ 1.25, 0.5 , 0.25, 0.5 , 1.25]],
|
||||
|
||||
[[ 1. , 0.25, 0. , 0.25, 1. ],
|
||||
[ 0.25, -0.5 , -0.75, -0.5 , 0.25],
|
||||
[ 0. , -0.75, -1. , -0.75, 0. ],
|
||||
[ 0.25, -0.5 , -0.75, -0.5 , 0.25],
|
||||
[ 1. , 0.25, 0. , 0.25, 1. ]],
|
||||
|
||||
[[ 1.25, 0.5 , 0.25, 0.5 , 1.25],
|
||||
[ 0.5 , -0.25, -0.5 , -0.25, 0.5 ],
|
||||
[ 0.25, -0.5 , -0.75, -0.5 , 0.25],
|
||||
[ 0.5 , -0.25, -0.5 , -0.25, 0.5 ],
|
||||
[ 1.25, 0.5 , 0.25, 0.5 , 1.25]],
|
||||
|
||||
[[ 2. , 1.25, 1. , 1.25, 2. ],
|
||||
[ 1.25, 0.5 , 0.25, 0.5 , 1.25],
|
||||
[ 1. , 0.25, 0. , 0.25, 1. ],
|
||||
[ 1.25, 0.5 , 0.25, 0.5 , 1.25],
|
||||
[ 2. , 1.25, 1. , 1.25, 2. ]]])
|
||||
|
||||
assert_allclose(test, expected)
|
||||
assert_allclose(test_anisotropic, expected)
|
||||
|
||||
|
||||
def test_ellipsoid_stats():
|
||||
# Test comparison values generated by Wolfram Alpha
|
||||
vol, surf = ellipsoid_stats(6, 10, 16)
|
||||
assert_allclose(1280 * np.pi, vol, atol=1e-4)
|
||||
assert_allclose(1383.28, surf, atol=1e-2)
|
||||
|
||||
# Test when a <= b <= c does not hold
|
||||
vol, surf = ellipsoid_stats(16, 6, 10)
|
||||
assert_allclose(1280 * np.pi, vol, atol=1e-4)
|
||||
assert_allclose(1383.28, surf, atol=1e-2)
|
||||
|
||||
# Larger test to ensure reliability over broad range
|
||||
vol, surf = ellipsoid_stats(17, 27, 169)
|
||||
assert_allclose(103428 * np.pi, vol, atol=1e-4)
|
||||
assert_allclose(37426.3, surf, atol=1e-1)
|
||||
|
||||
|
||||
def test_rect_3d_extent():
|
||||
expected = np.array([[[0, 0, 1, 1, 1],
|
||||
[0, 0, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0]],
|
||||
[[0, 0, 1, 1, 1],
|
||||
[0, 0, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0]],
|
||||
[[0, 0, 1, 1, 1],
|
||||
[0, 0, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0]],
|
||||
[[0, 0, 1, 1, 1],
|
||||
[0, 0, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0]]], dtype=np.uint8)
|
||||
img = np.zeros((4, 5, 5), dtype=np.uint8)
|
||||
start = (0, 0, 2)
|
||||
extent = (5, 2, 3)
|
||||
pp, rr, cc = rectangle(start, extent=extent, shape=img.shape)
|
||||
img[pp, rr, cc] = 1
|
||||
assert_array_equal(img, expected)
|
||||
|
||||
|
||||
def test_rect_3d_end():
|
||||
expected = np.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]],
|
||||
[[0, 0, 1, 1, 0],
|
||||
[0, 0, 1, 1, 0],
|
||||
[0, 0, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0]],
|
||||
[[0, 0, 1, 1, 0],
|
||||
[0, 0, 1, 1, 0],
|
||||
[0, 0, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0]],
|
||||
[[0, 0, 1, 1, 0],
|
||||
[0, 0, 1, 1, 0],
|
||||
[0, 0, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0]]], dtype=np.uint8)
|
||||
img = np.zeros((4, 5, 5), dtype=np.uint8)
|
||||
start = (1, 0, 2)
|
||||
end = (3, 2, 3)
|
||||
pp, rr, cc = rectangle(start, end=end, shape=img.shape)
|
||||
img[pp, rr, cc] = 1
|
||||
assert_array_equal(img, expected)
|
||||
@@ -1,18 +0,0 @@
|
||||
from skimage.draw import line_nd
|
||||
from skimage._shared.testing import assert_equal
|
||||
|
||||
|
||||
def test_empty_line():
|
||||
coords = line_nd((1, 1, 1), (1, 1, 1))
|
||||
assert len(coords) == 3
|
||||
assert all(len(c) == 0 for c in coords)
|
||||
|
||||
|
||||
def test_zero_line():
|
||||
coords = line_nd((-1, -1), (2, 2))
|
||||
assert_equal(coords, [[-1, 0, 1], [-1, 0, 1]])
|
||||
|
||||
|
||||
def test_no_round():
|
||||
coords = line_nd((0.5, 0), (2.5, 0), integer=False, endpoint=True)
|
||||
assert_equal(coords, [[0.5, 1.5, 2.5], [0, 0, 0]])
|
||||
@@ -1,14 +0,0 @@
|
||||
import numpy as np
|
||||
|
||||
from skimage import draw
|
||||
|
||||
|
||||
image_shape = (512, 512)
|
||||
polygon = np.array([[80, 111, 146, 234, 407, 300, 187, 45],
|
||||
[465, 438, 499, 380, 450, 287, 210, 167]]).T
|
||||
|
||||
|
||||
def test_polygon2mask():
|
||||
mask = draw.polygon2mask(image_shape, polygon)
|
||||
assert mask.shape == image_shape
|
||||
assert mask.sum() == 57653
|
||||
@@ -1,195 +0,0 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from skimage.draw import random_shapes
|
||||
|
||||
from skimage._shared import testing
|
||||
from skimage._shared._warnings import expected_warnings
|
||||
|
||||
|
||||
def test_generates_color_images_with_correct_shape():
|
||||
image, _ = random_shapes((128, 128), max_shapes=10)
|
||||
assert image.shape == (128, 128, 3)
|
||||
|
||||
|
||||
def test_generates_gray_images_with_correct_shape():
|
||||
image, _ = random_shapes(
|
||||
(4567, 123), min_shapes=3, max_shapes=20, channel_axis=None)
|
||||
assert image.shape == (4567, 123)
|
||||
|
||||
|
||||
def test_generates_gray_images_with_correct_shape_deprecated_multichannel():
|
||||
image, _ = random_shapes(
|
||||
(4567, 123), min_shapes=3, max_shapes=20, channel_axis=None)
|
||||
assert image.shape == (4567, 123)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('channel_axis', [None, 0, 1, 2])
|
||||
def test_generated_shape_for_channel_axis(channel_axis):
|
||||
shape = (128, 64)
|
||||
num_channels = 5
|
||||
|
||||
image, _ = random_shapes(
|
||||
shape, num_channels=num_channels, min_shapes=3, max_shapes=10,
|
||||
channel_axis=channel_axis)
|
||||
|
||||
if channel_axis is None:
|
||||
expected_shape = shape
|
||||
else:
|
||||
expected_shape = tuple(np.insert(shape, channel_axis, num_channels))
|
||||
|
||||
assert image.shape == expected_shape
|
||||
|
||||
|
||||
def test_generates_correct_bounding_boxes_for_rectangles():
|
||||
image, labels = random_shapes(
|
||||
(128, 128),
|
||||
max_shapes=1,
|
||||
shape='rectangle',
|
||||
random_seed=42)
|
||||
assert len(labels) == 1
|
||||
label, bbox = labels[0]
|
||||
assert label == 'rectangle', label
|
||||
|
||||
crop = image[bbox[0][0]:bbox[0][1], bbox[1][0]:bbox[1][1]]
|
||||
|
||||
# The crop is filled.
|
||||
assert (crop >= 0).all() and (crop < 255).all()
|
||||
|
||||
# The crop is complete.
|
||||
image[bbox[0][0]:bbox[0][1], bbox[1][0]:bbox[1][1]] = 255
|
||||
assert (image == 255).all()
|
||||
|
||||
|
||||
def test_generates_correct_bounding_boxes_for_triangles():
|
||||
image, labels = random_shapes(
|
||||
(128, 128),
|
||||
max_shapes=1,
|
||||
shape='triangle',
|
||||
random_seed=42)
|
||||
assert len(labels) == 1
|
||||
label, bbox = labels[0]
|
||||
assert label == 'triangle', label
|
||||
|
||||
crop = image[bbox[0][0]:bbox[0][1], bbox[1][0]:bbox[1][1]]
|
||||
|
||||
# The crop is filled.
|
||||
assert (crop >= 0).any() and (crop < 255).any()
|
||||
|
||||
# The crop is complete.
|
||||
image[bbox[0][0]:bbox[0][1], bbox[1][0]:bbox[1][1]] = 255
|
||||
assert (image == 255).all()
|
||||
|
||||
|
||||
def test_generates_correct_bounding_boxes_for_circles():
|
||||
image, labels = random_shapes(
|
||||
(43, 44),
|
||||
max_shapes=1,
|
||||
min_size=20,
|
||||
max_size=20,
|
||||
shape='circle',
|
||||
random_seed=42)
|
||||
assert len(labels) == 1
|
||||
label, bbox = labels[0]
|
||||
assert label == 'circle', label
|
||||
|
||||
crop = image[bbox[0][0]:bbox[0][1], bbox[1][0]:bbox[1][1]]
|
||||
|
||||
# The crop is filled.
|
||||
assert (crop >= 0).any() and (crop < 255).any()
|
||||
|
||||
# The crop is complete.
|
||||
image[bbox[0][0]:bbox[0][1], bbox[1][0]:bbox[1][1]] = 255
|
||||
assert (image == 255).all()
|
||||
|
||||
|
||||
def test_generates_correct_bounding_boxes_for_ellipses():
|
||||
image, labels = random_shapes(
|
||||
(43, 44),
|
||||
max_shapes=1,
|
||||
min_size=20,
|
||||
max_size=20,
|
||||
shape='ellipse',
|
||||
random_seed=42)
|
||||
assert len(labels) == 1
|
||||
label, bbox = labels[0]
|
||||
assert label == 'ellipse', label
|
||||
|
||||
crop = image[bbox[0][0]:bbox[0][1], bbox[1][0]:bbox[1][1]]
|
||||
|
||||
# The crop is filled.
|
||||
assert (crop >= 0).any() and (crop < 255).any()
|
||||
|
||||
# The crop is complete.
|
||||
image[bbox[0][0]:bbox[0][1], bbox[1][0]:bbox[1][1]] = 255
|
||||
assert (image == 255).all()
|
||||
|
||||
|
||||
def test_generate_circle_throws_when_size_too_small():
|
||||
with testing.raises(ValueError):
|
||||
random_shapes(
|
||||
(64, 128), max_shapes=1, min_size=1, max_size=1, shape='circle')
|
||||
|
||||
|
||||
def test_generate_ellipse_throws_when_size_too_small():
|
||||
with testing.raises(ValueError):
|
||||
random_shapes(
|
||||
(64, 128), max_shapes=1, min_size=1, max_size=1, shape='ellipse')
|
||||
|
||||
|
||||
def test_generate_triangle_throws_when_size_too_small():
|
||||
with testing.raises(ValueError):
|
||||
random_shapes(
|
||||
(128, 64), max_shapes=1, min_size=1, max_size=1, shape='triangle')
|
||||
|
||||
|
||||
def test_can_generate_one_by_one_rectangle():
|
||||
image, labels = random_shapes(
|
||||
(50, 128),
|
||||
max_shapes=1,
|
||||
min_size=1,
|
||||
max_size=1,
|
||||
shape='rectangle')
|
||||
assert len(labels) == 1
|
||||
_, bbox = labels[0]
|
||||
crop = image[bbox[0][0]:bbox[0][1], bbox[1][0]:bbox[1][1]]
|
||||
|
||||
# rgb
|
||||
assert (np.shape(crop) == (1, 1, 3) and np.any(crop >= 1)
|
||||
and np.any(crop < 255))
|
||||
|
||||
|
||||
def test_throws_when_intensity_range_out_of_range():
|
||||
with testing.raises(ValueError):
|
||||
random_shapes((1000, 1234), max_shapes=1, channel_axis=None,
|
||||
intensity_range=(0, 256))
|
||||
with testing.raises(ValueError):
|
||||
random_shapes((2, 2), max_shapes=1,
|
||||
intensity_range=((-1, 255),))
|
||||
|
||||
|
||||
def test_returns_empty_labels_and_white_image_when_cannot_fit_shape():
|
||||
# The circle will never fit this.
|
||||
with expected_warnings(['Could not fit']):
|
||||
image, labels = random_shapes(
|
||||
(10000, 10000), max_shapes=1, min_size=10000, shape='circle')
|
||||
assert len(labels) == 0
|
||||
assert (image == 255).all()
|
||||
|
||||
|
||||
def test_random_shapes_is_reproducible_with_seed():
|
||||
random_seed = 42
|
||||
labels = []
|
||||
for _ in range(5):
|
||||
_, label = random_shapes((128, 128), max_shapes=5,
|
||||
random_seed=random_seed)
|
||||
labels.append(label)
|
||||
assert all(other == labels[0] for other in labels[1:])
|
||||
|
||||
|
||||
def test_generates_white_image_when_intensity_range_255():
|
||||
image, labels = random_shapes((128, 128), max_shapes=3,
|
||||
intensity_range=((255, 255),),
|
||||
random_seed=42)
|
||||
assert len(labels) > 0
|
||||
assert (image == 255).all()
|
||||
Reference in New Issue
Block a user