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
31
venv/Lib/site-packages/skimage/draw/__init__.py
Normal file
31
venv/Lib/site-packages/skimage/draw/__init__.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
from .draw import (circle, 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',
|
||||
'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.
BIN
venv/Lib/site-packages/skimage/draw/_draw.cp36-win32.pyd
Normal file
BIN
venv/Lib/site-packages/skimage/draw/_draw.cp36-win32.pyd
Normal file
Binary file not shown.
41
venv/Lib/site-packages/skimage/draw/_polygon2mask.py
Normal file
41
venv/Lib/site-packages/skimage/draw/_polygon2mask.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
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=np.bool)
|
||||
mask[fill_row_coords, fill_col_coords] = True
|
||||
return mask
|
434
venv/Lib/site-packages/skimage/draw/_random_shapes.py
Normal file
434
venv/Lib/site-packages/skimage/draw/_random_shapes.py
Normal file
|
@ -0,0 +1,434 @@
|
|||
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 : np.random.RandomState
|
||||
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])
|
||||
if available_width < shape[0]:
|
||||
raise ArithmeticError('cannot fit shape to image')
|
||||
available_height = min(image[0] - point[0], shape[1])
|
||||
if available_height < shape[0]:
|
||||
raise ArithmeticError('cannot fit shape to image')
|
||||
# Pick random widths and heights.
|
||||
r = random.randint(shape[0], available_height + 1)
|
||||
c = random.randint(shape[0], 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), (point[1], point[1] + c)))
|
||||
|
||||
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 : np.random.RandomState
|
||||
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)
|
||||
if available_radius < min_radius:
|
||||
raise ArithmeticError('cannot fit shape to image')
|
||||
radius = random.randint(min_radius, available_radius + 1)
|
||||
# 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 down-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 : np.random.RandomState
|
||||
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] + 1, shape[1])
|
||||
if available_side < shape[0]:
|
||||
raise ArithmeticError('cannot fit shape to image')
|
||||
side = random.randint(shape[0], 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]),
|
||||
(point[1], point[1] + side)))
|
||||
|
||||
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 : np.random.RandomState
|
||||
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 : np.random.RandomState
|
||||
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.randint(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,
|
||||
multichannel=True,
|
||||
num_channels=3,
|
||||
shape=None,
|
||||
intensity_range=None,
|
||||
allow_overlap=False,
|
||||
num_trials=100,
|
||||
random_seed=None):
|
||||
"""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.
|
||||
multichannel : bool, optional
|
||||
If True, the generated image has ``num_channels`` color channels,
|
||||
otherwise generates grayscale 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 : int, optional
|
||||
Seed to initialize the random number generator.
|
||||
If `None`, a random seed from the operating system is used.
|
||||
|
||||
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 not multichannel:
|
||||
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.RandomState(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.randint(min_shapes, max_shapes + 1)
|
||||
colors = _generate_random_colors(num_shapes, num_channels,
|
||||
intensity_range, random)
|
||||
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]
|
||||
shape = (min_size, max_size)
|
||||
for _ in range(num_trials):
|
||||
# Pick start coordinates.
|
||||
column = random.randint(image_shape[1])
|
||||
row = random.randint(image_shape[0])
|
||||
point = (row, column)
|
||||
try:
|
||||
indices, label = shape_generator(point, image_shape, shape,
|
||||
random)
|
||||
except ArithmeticError:
|
||||
# Couldn't fit the shape, skip it.
|
||||
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 not multichannel:
|
||||
image = np.squeeze(image, axis=2)
|
||||
return image, labels
|
949
venv/Lib/site-packages/skimage/draw/draw.py
Normal file
949
venv/Lib/site-packages/skimage/draw/draw.py
Normal file
|
@ -0,0 +1,949 @@
|
|||
import warnings
|
||||
import numpy as np
|
||||
|
||||
from .._shared._geometry import polygon_clip
|
||||
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 circle(r, c, radius, shape=None):
|
||||
"""Generate coordinates of pixels within circle.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
r, c : double
|
||||
Center coordinate of disk.
|
||||
radius : double
|
||||
Radius of disk.
|
||||
shape : tuple, optional
|
||||
Image shape which is used to determine 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. 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 disk.
|
||||
May be used to directly index into an array, e.g.
|
||||
``img[rr, cc] = 1``.
|
||||
|
||||
Warns
|
||||
-----
|
||||
Deprecated:
|
||||
.. versionadded:: 0.17
|
||||
|
||||
This function is deprecated and will be removed in scikit-image 0.19.
|
||||
Please use the function named ``disk`` instead.
|
||||
"""
|
||||
warnings.warn("circle is deprecated in favor of "
|
||||
"disk."
|
||||
"circle will be removed in version 0.19",
|
||||
FutureWarning, stacklevel=2)
|
||||
return disk((r, c), radius, shape=shape)
|
||||
|
||||
|
||||
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 which is used to determine 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. 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 disk.
|
||||
May be used to directly index into an array, e.g.
|
||||
``img[rr, cc] = 1``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.draw import disk
|
||||
>>> 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)
|
||||
|
||||
|
||||
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('Color shape ({}) must match last '
|
||||
'image dimension ({}).'.format(color.shape[0],
|
||||
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, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 1, 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],
|
||||
[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 oposite 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
|
||||
|
||||
|
||||
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)
|
||||
|
||||
if extent is None:
|
||||
bottom_right += 1
|
||||
|
||||
return (top_left, bottom_right)
|
114
venv/Lib/site-packages/skimage/draw/draw3d.py
Normal file
114
venv/Lib/site-packages/skimage/draw/draw3d.py
Normal file
|
@ -0,0 +1,114 @@
|
|||
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
|
108
venv/Lib/site-packages/skimage/draw/draw_nd.py
Normal file
108
venv/Lib/site-packages/skimage/draw/draw_nd.py
Normal file
|
@ -0,0 +1,108 @@
|
|||
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 neighbours 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 = []
|
||||
for dim in range(len(start)):
|
||||
dimcoords = np.linspace(start[dim], stop[dim], npoints, endpoint)
|
||||
if integer:
|
||||
dimcoords = _round_safe(dimcoords).astype(int)
|
||||
coords.append(dimcoords)
|
||||
return tuple(coords)
|
31
venv/Lib/site-packages/skimage/draw/setup.py
Normal file
31
venv/Lib/site-packages/skimage/draw/setup.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
from skimage._build import cython
|
||||
|
||||
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('draw', parent_package, top_path)
|
||||
|
||||
cython(['_draw.pyx'], working_path=base_path)
|
||||
|
||||
config.add_extension('_draw', sources=['_draw.c'],
|
||||
include_dirs=[get_numpy_include_dirs(), '../_shared'])
|
||||
|
||||
return config
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from numpy.distutils.core import setup
|
||||
setup(maintainer='scikit-image developers',
|
||||
author='scikit-image developers',
|
||||
maintainer_email='scikit-image@python.org',
|
||||
description='Drawing',
|
||||
url='https://github.com/scikit-image/scikit-image',
|
||||
license='SciPy License (BSD Style)',
|
||||
**(configuration(top_path='').todict())
|
||||
)
|
9
venv/Lib/site-packages/skimage/draw/tests/__init__.py
Normal file
9
venv/Lib/site-packages/skimage/draw/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.
1111
venv/Lib/site-packages/skimage/draw/tests/test_draw.py
Normal file
1111
venv/Lib/site-packages/skimage/draw/tests/test_draw.py
Normal file
File diff suppressed because it is too large
Load diff
174
venv/Lib/site-packages/skimage/draw/tests/test_draw3d.py
Normal file
174
venv/Lib/site-packages/skimage/draw/tests/test_draw3d.py
Normal file
|
@ -0,0 +1,174 @@
|
|||
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)
|
18
venv/Lib/site-packages/skimage/draw/tests/test_draw_nd.py
Normal file
18
venv/Lib/site-packages/skimage/draw/tests/test_draw_nd.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
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]])
|
|
@ -0,0 +1,14 @@
|
|||
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() == 57647
|
171
venv/Lib/site-packages/skimage/draw/tests/test_random_shapes.py
Normal file
171
venv/Lib/site-packages/skimage/draw/tests/test_random_shapes.py
Normal file
|
@ -0,0 +1,171 @@
|
|||
import numpy as np
|
||||
|
||||
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, multichannel=False)
|
||||
assert image.shape == (4567, 123)
|
||||
|
||||
|
||||
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, multichannel=False,
|
||||
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()
|
Loading…
Add table
Add a link
Reference in a new issue