scikit-image

Форк
0
973 строки · 32.9 Кб
1
import numpy as np
2

3
from .._shared._geometry import polygon_clip
4
from .._shared.version_requirements import require
5
from .._shared.compat import NP_COPY_IF_NEEDED
6
from ._draw import (
7
    _coords_inside_image,
8
    _line,
9
    _line_aa,
10
    _polygon,
11
    _ellipse_perimeter,
12
    _circle_perimeter,
13
    _circle_perimeter_aa,
14
    _bezier_curve,
15
)
16

17

18
def _ellipse_in_shape(shape, center, radii, rotation=0.0):
19
    """Generate coordinates of points within ellipse bounded by shape.
20

21
    Parameters
22
    ----------
23
    shape :  iterable of ints
24
        Shape of the input image.  Must be at least length 2. Only the first
25
        two values are used to determine the extent of the input image.
26
    center : iterable of floats
27
        (row, column) position of center inside the given shape.
28
    radii : iterable of floats
29
        Size of two half axes (for row and column)
30
    rotation : float, optional
31
        Rotation of the ellipse defined by the above, in radians
32
        in range (-PI, PI), in contra clockwise direction,
33
        with respect to the column-axis.
34

35
    Returns
36
    -------
37
    rows : iterable of ints
38
        Row coordinates representing values within the ellipse.
39
    cols : iterable of ints
40
        Corresponding column coordinates representing values within the ellipse.
41
    """
42
    r_lim, c_lim = np.ogrid[0 : float(shape[0]), 0 : float(shape[1])]
43
    r_org, c_org = center
44
    r_rad, c_rad = radii
45
    rotation %= np.pi
46
    sin_alpha, cos_alpha = np.sin(rotation), np.cos(rotation)
47
    r, c = (r_lim - r_org), (c_lim - c_org)
48
    distances = ((r * cos_alpha + c * sin_alpha) / r_rad) ** 2 + (
49
        (r * sin_alpha - c * cos_alpha) / c_rad
50
    ) ** 2
51
    return np.nonzero(distances < 1)
52

53

54
def ellipse(r, c, r_radius, c_radius, shape=None, rotation=0.0):
55
    """Generate coordinates of pixels within ellipse.
56

57
    Parameters
58
    ----------
59
    r, c : double
60
        Centre coordinate of ellipse.
61
    r_radius, c_radius : double
62
        Minor and major semi-axes. ``(r/r_radius)**2 + (c/c_radius)**2 = 1``.
63
    shape : tuple, optional
64
        Image shape which is used to determine the maximum extent of output pixel
65
        coordinates. This is useful for ellipses which exceed the image size.
66
        By default the full extent of the ellipse are used. Must be at least
67
        length 2. Only the first two values are used to determine the extent.
68
    rotation : float, optional (default 0.)
69
        Set the ellipse rotation (rotation) in range (-PI, PI)
70
        in contra clock wise direction, so PI/2 degree means swap ellipse axis
71

72
    Returns
73
    -------
74
    rr, cc : ndarray of int
75
        Pixel coordinates of ellipse.
76
        May be used to directly index into an array, e.g.
77
        ``img[rr, cc] = 1``.
78

79
    Examples
80
    --------
81
    >>> from skimage.draw import ellipse
82
    >>> img = np.zeros((10, 12), dtype=np.uint8)
83
    >>> rr, cc = ellipse(5, 6, 3, 5, rotation=np.deg2rad(30))
84
    >>> img[rr, cc] = 1
85
    >>> img
86
    array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
87
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
88
           [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0],
89
           [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0],
90
           [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
91
           [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
92
           [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
93
           [0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
94
           [0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0],
95
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
96

97
    Notes
98
    -----
99
    The ellipse equation::
100

101
        ((x * cos(alpha) + y * sin(alpha)) / x_radius) ** 2 +
102
        ((x * sin(alpha) - y * cos(alpha)) / y_radius) ** 2 = 1
103

104

105
    Note that the positions of `ellipse` without specified `shape` can have
106
    also, negative values, as this is correct on the plane. On the other hand
107
    using these ellipse positions for an image afterwards may lead to appearing
108
    on the other side of image, because ``image[-1, -1] = image[end-1, end-1]``
109

110
    >>> rr, cc = ellipse(1, 2, 3, 6)
111
    >>> img = np.zeros((6, 12), dtype=np.uint8)
112
    >>> img[rr, cc] = 1
113
    >>> img
114
    array([[1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1],
115
           [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1],
116
           [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1],
117
           [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1],
118
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
119
           [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1]], dtype=uint8)
120
    """
121

122
    center = np.array([r, c])
123
    radii = np.array([r_radius, c_radius])
124
    # allow just rotation with in range +/- 180 degree
125
    rotation %= np.pi
126

127
    # compute rotated radii by given rotation
128
    r_radius_rot = abs(r_radius * np.cos(rotation)) + c_radius * np.sin(rotation)
129
    c_radius_rot = r_radius * np.sin(rotation) + abs(c_radius * np.cos(rotation))
130
    # The upper_left and lower_right corners of the smallest rectangle
131
    # containing the ellipse.
132
    radii_rot = np.array([r_radius_rot, c_radius_rot])
133
    upper_left = np.ceil(center - radii_rot).astype(int)
134
    lower_right = np.floor(center + radii_rot).astype(int)
135

136
    if shape is not None:
137
        # Constrain upper_left and lower_right by shape boundary.
138
        upper_left = np.maximum(upper_left, np.array([0, 0]))
139
        lower_right = np.minimum(lower_right, np.array(shape[:2]) - 1)
140

141
    shifted_center = center - upper_left
142
    bounding_shape = lower_right - upper_left + 1
143

144
    rr, cc = _ellipse_in_shape(bounding_shape, shifted_center, radii, rotation)
145
    rr.flags.writeable = True
146
    cc.flags.writeable = True
147
    rr += upper_left[0]
148
    cc += upper_left[1]
149
    return rr, cc
150

151

152
def disk(center, radius, *, shape=None):
153
    """Generate coordinates of pixels within circle.
154

155
    Parameters
156
    ----------
157
    center : tuple
158
        Center coordinate of disk.
159
    radius : double
160
        Radius of disk.
161
    shape : tuple, optional
162
        Image shape as a tuple of size 2. Determines the maximum
163
        extent of output pixel coordinates. This is useful for disks that
164
        exceed the image size. If None, the full extent of the disk is used.
165
        The  shape might result in negative coordinates and wraparound
166
        behaviour.
167

168
    Returns
169
    -------
170
    rr, cc : ndarray of int
171
        Pixel coordinates of disk.
172
        May be used to directly index into an array, e.g.
173
        ``img[rr, cc] = 1``.
174

175
    Examples
176
    --------
177
    >>> import numpy as np
178
    >>> from skimage.draw import disk
179
    >>> shape = (4, 4)
180
    >>> img = np.zeros(shape, dtype=np.uint8)
181
    >>> rr, cc = disk((0, 0), 2, shape=shape)
182
    >>> img[rr, cc] = 1
183
    >>> img
184
    array([[1, 1, 0, 0],
185
           [1, 1, 0, 0],
186
           [0, 0, 0, 0],
187
           [0, 0, 0, 0]], dtype=uint8)
188
    >>> img = np.zeros(shape, dtype=np.uint8)
189
    >>> # Negative coordinates in rr and cc perform a wraparound
190
    >>> rr, cc = disk((0, 0), 2, shape=None)
191
    >>> img[rr, cc] = 1
192
    >>> img
193
    array([[1, 1, 0, 1],
194
           [1, 1, 0, 1],
195
           [0, 0, 0, 0],
196
           [1, 1, 0, 1]], dtype=uint8)
197
    >>> img = np.zeros((10, 10), dtype=np.uint8)
198
    >>> rr, cc = disk((4, 4), 5)
199
    >>> img[rr, cc] = 1
200
    >>> img
201
    array([[0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
202
           [0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
203
           [1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
204
           [1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
205
           [1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
206
           [1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
207
           [1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
208
           [0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
209
           [0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
210
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
211
    """
212
    r, c = center
213
    return ellipse(r, c, radius, radius, shape)
214

215

216
@require("matplotlib", ">=3.3")
217
def polygon_perimeter(r, c, shape=None, clip=False):
218
    """Generate polygon perimeter coordinates.
219

220
    Parameters
221
    ----------
222
    r : (N,) ndarray
223
        Row coordinates of vertices of polygon.
224
    c : (N,) ndarray
225
        Column coordinates of vertices of polygon.
226
    shape : tuple, optional
227
        Image shape which is used to determine maximum extents of output pixel
228
        coordinates. This is useful for polygons that exceed the image size.
229
        If None, the full extents of the polygon is used.  Must be at least
230
        length 2. Only the first two values are used to determine the extent of
231
        the input image.
232
    clip : bool, optional
233
        Whether to clip the polygon to the provided shape.  If this is set
234
        to True, the drawn figure will always be a closed polygon with all
235
        edges visible.
236

237
    Returns
238
    -------
239
    rr, cc : ndarray of int
240
        Pixel coordinates of polygon.
241
        May be used to directly index into an array, e.g.
242
        ``img[rr, cc] = 1``.
243

244
    Examples
245
    --------
246
    .. testsetup::
247
        >>> import pytest; _ = pytest.importorskip('matplotlib')
248

249
    >>> from skimage.draw import polygon_perimeter
250
    >>> img = np.zeros((10, 10), dtype=np.uint8)
251
    >>> rr, cc = polygon_perimeter([5, -1, 5, 10],
252
    ...                            [-1, 5, 11, 5],
253
    ...                            shape=img.shape, clip=True)
254
    >>> img[rr, cc] = 1
255
    >>> img
256
    array([[0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
257
           [0, 0, 0, 1, 0, 0, 0, 1, 0, 0],
258
           [0, 0, 1, 0, 0, 0, 0, 0, 1, 0],
259
           [0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
260
           [1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
261
           [1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
262
           [1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
263
           [0, 1, 1, 0, 0, 0, 0, 0, 0, 1],
264
           [0, 0, 0, 1, 0, 0, 0, 1, 1, 0],
265
           [0, 0, 0, 0, 1, 1, 1, 0, 0, 0]], dtype=uint8)
266

267
    """
268
    if clip:
269
        if shape is None:
270
            raise ValueError("Must specify clipping shape")
271
        clip_box = np.array([0, 0, shape[0] - 1, shape[1] - 1])
272
    else:
273
        clip_box = np.array([np.min(r), np.min(c), np.max(r), np.max(c)])
274

275
    # Do the clipping irrespective of whether clip is set.  This
276
    # ensures that the returned polygon is closed and is an array.
277
    r, c = polygon_clip(r, c, *clip_box)
278

279
    r = np.round(r).astype(int)
280
    c = np.round(c).astype(int)
281

282
    # Construct line segments
283
    rr, cc = [], []
284
    for i in range(len(r) - 1):
285
        line_r, line_c = line(r[i], c[i], r[i + 1], c[i + 1])
286
        rr.extend(line_r)
287
        cc.extend(line_c)
288

289
    rr = np.asarray(rr)
290
    cc = np.asarray(cc)
291

292
    if shape is None:
293
        return rr, cc
294
    else:
295
        return _coords_inside_image(rr, cc, shape)
296

297

298
def set_color(image, coords, color, alpha=1):
299
    """Set pixel color in the image at the given coordinates.
300

301
    Note that this function modifies the color of the image in-place.
302
    Coordinates that exceed the shape of the image will be ignored.
303

304
    Parameters
305
    ----------
306
    image : (M, N, C) ndarray
307
        Image
308
    coords : tuple of ((K,) ndarray, (K,) ndarray)
309
        Row and column coordinates of pixels to be colored.
310
    color : (C,) ndarray
311
        Color to be assigned to coordinates in the image.
312
    alpha : scalar or (K,) ndarray
313
        Alpha values used to blend color with image.  0 is transparent,
314
        1 is opaque.
315

316
    Examples
317
    --------
318
    >>> from skimage.draw import line, set_color
319
    >>> img = np.zeros((10, 10), dtype=np.uint8)
320
    >>> rr, cc = line(1, 1, 20, 20)
321
    >>> set_color(img, (rr, cc), 1)
322
    >>> img
323
    array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
324
           [0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
325
           [0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
326
           [0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
327
           [0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
328
           [0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
329
           [0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
330
           [0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
331
           [0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
332
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 1]], dtype=uint8)
333

334
    """
335
    rr, cc = coords
336

337
    if image.ndim == 2:
338
        image = image[..., np.newaxis]
339

340
    color = np.array(color, ndmin=1, copy=NP_COPY_IF_NEEDED)
341

342
    if image.shape[-1] != color.shape[-1]:
343
        raise ValueError(
344
            f'Color shape ({color.shape[0]}) must match last '
345
            'image dimension ({image.shape[-1]}).'
346
        )
347

348
    if np.isscalar(alpha):
349
        # Can be replaced by ``full_like`` when numpy 1.8 becomes
350
        # minimum dependency
351
        alpha = np.ones_like(rr) * alpha
352

353
    rr, cc, alpha = _coords_inside_image(rr, cc, image.shape, val=alpha)
354

355
    alpha = alpha[..., np.newaxis]
356

357
    color = color * alpha
358
    vals = image[rr, cc] * (1 - alpha)
359

360
    image[rr, cc] = vals + color
361

362

363
def line(r0, c0, r1, c1):
364
    """Generate line pixel coordinates.
365

366
    Parameters
367
    ----------
368
    r0, c0 : int
369
        Starting position (row, column).
370
    r1, c1 : int
371
        End position (row, column).
372

373
    Returns
374
    -------
375
    rr, cc : (N,) ndarray of int
376
        Indices of pixels that belong to the line.
377
        May be used to directly index into an array, e.g.
378
        ``img[rr, cc] = 1``.
379

380
    Notes
381
    -----
382
    Anti-aliased line generator is available with `line_aa`.
383

384
    Examples
385
    --------
386
    >>> from skimage.draw import line
387
    >>> img = np.zeros((10, 10), dtype=np.uint8)
388
    >>> rr, cc = line(1, 1, 8, 8)
389
    >>> img[rr, cc] = 1
390
    >>> img
391
    array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
392
           [0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
393
           [0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
394
           [0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
395
           [0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
396
           [0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
397
           [0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
398
           [0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
399
           [0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
400
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
401
    """
402
    return _line(r0, c0, r1, c1)
403

404

405
def line_aa(r0, c0, r1, c1):
406
    """Generate anti-aliased line pixel coordinates.
407

408
    Parameters
409
    ----------
410
    r0, c0 : int
411
        Starting position (row, column).
412
    r1, c1 : int
413
        End position (row, column).
414

415
    Returns
416
    -------
417
    rr, cc, val : (N,) ndarray (int, int, float)
418
        Indices of pixels (`rr`, `cc`) and intensity values (`val`).
419
        ``img[rr, cc] = val``.
420

421
    References
422
    ----------
423
    .. [1] A Rasterizing Algorithm for Drawing Curves, A. Zingl, 2012
424
           http://members.chello.at/easyfilter/Bresenham.pdf
425

426
    Examples
427
    --------
428
    >>> from skimage.draw import line_aa
429
    >>> img = np.zeros((10, 10), dtype=np.uint8)
430
    >>> rr, cc, val = line_aa(1, 1, 8, 8)
431
    >>> img[rr, cc] = val * 255
432
    >>> img
433
    array([[  0,   0,   0,   0,   0,   0,   0,   0,   0,   0],
434
           [  0, 255,  74,   0,   0,   0,   0,   0,   0,   0],
435
           [  0,  74, 255,  74,   0,   0,   0,   0,   0,   0],
436
           [  0,   0,  74, 255,  74,   0,   0,   0,   0,   0],
437
           [  0,   0,   0,  74, 255,  74,   0,   0,   0,   0],
438
           [  0,   0,   0,   0,  74, 255,  74,   0,   0,   0],
439
           [  0,   0,   0,   0,   0,  74, 255,  74,   0,   0],
440
           [  0,   0,   0,   0,   0,   0,  74, 255,  74,   0],
441
           [  0,   0,   0,   0,   0,   0,   0,  74, 255,   0],
442
           [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0]], dtype=uint8)
443
    """
444
    return _line_aa(r0, c0, r1, c1)
445

446

447
def polygon(r, c, shape=None):
448
    """Generate coordinates of pixels inside a polygon.
449

450
    Parameters
451
    ----------
452
    r : (N,) array_like
453
        Row coordinates of the polygon's vertices.
454
    c : (N,) array_like
455
        Column coordinates of the polygon's vertices.
456
    shape : tuple, optional
457
        Image shape which is used to determine the maximum extent of output
458
        pixel coordinates. This is useful for polygons that exceed the image
459
        size. If None, the full extent of the polygon is used.  Must be at
460
        least length 2. Only the first two values are used to determine the
461
        extent of the input image.
462

463
    Returns
464
    -------
465
    rr, cc : ndarray of int
466
        Pixel coordinates of polygon.
467
        May be used to directly index into an array, e.g.
468
        ``img[rr, cc] = 1``.
469

470
    See Also
471
    --------
472
    polygon2mask:
473
        Create a binary mask from a polygon.
474

475
    Notes
476
    -----
477
    This function ensures that `rr` and `cc` don't contain negative values.
478
    Pixels of the polygon that whose coordinates are smaller 0, are not drawn.
479

480
    Examples
481
    --------
482
    >>> import skimage as ski
483
    >>> r = np.array([1, 2, 8])
484
    >>> c = np.array([1, 7, 4])
485
    >>> rr, cc = ski.draw.polygon(r, c)
486
    >>> img = np.zeros((10, 10), dtype=int)
487
    >>> img[rr, cc] = 1
488
    >>> img
489
    array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
490
           [0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
491
           [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
492
           [0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
493
           [0, 0, 0, 1, 1, 1, 1, 0, 0, 0],
494
           [0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
495
           [0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
496
           [0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
497
           [0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
498
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
499

500
    If the image `shape` is defined and vertices / points of the `polygon` are
501
    outside this coordinate space, only a part (or none at all) of the polygon's
502
    pixels is returned. Shifting the polygon's vertices by an offset can be used
503
    to move the polygon around and potentially draw an arbitrary sub-region of
504
    the polygon.
505

506
    >>> offset = (2, -4)
507
    >>> rr, cc = ski.draw.polygon(r - offset[0], c - offset[1], shape=img.shape)
508
    >>> img = np.zeros((10, 10), dtype=int)
509
    >>> img[rr, cc] = 1
510
    >>> img
511
    array([[0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
512
           [0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
513
           [0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
514
           [0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
515
           [0, 0, 0, 0, 0, 0, 0, 0, 1, 1],
516
           [0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
517
           [0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
518
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
519
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
520
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
521
    """
522
    return _polygon(r, c, shape)
523

524

525
def circle_perimeter(r, c, radius, method='bresenham', shape=None):
526
    """Generate circle perimeter coordinates.
527

528
    Parameters
529
    ----------
530
    r, c : int
531
        Centre coordinate of circle.
532
    radius : int
533
        Radius of circle.
534
    method : {'bresenham', 'andres'}, optional
535
        bresenham : Bresenham method (default)
536
        andres : Andres method
537
    shape : tuple, optional
538
        Image shape which is used to determine the maximum extent of output
539
        pixel coordinates. This is useful for circles that exceed the image
540
        size. If None, the full extent of the circle is used.  Must be at least
541
        length 2. Only the first two values are used to determine the extent of
542
        the input image.
543

544
    Returns
545
    -------
546
    rr, cc : (N,) ndarray of int
547
        Bresenham and Andres' method:
548
        Indices of pixels that belong to the circle perimeter.
549
        May be used to directly index into an array, e.g.
550
        ``img[rr, cc] = 1``.
551

552
    Notes
553
    -----
554
    Andres method presents the advantage that concentric
555
    circles create a disc whereas Bresenham can make holes. There
556
    is also less distortions when Andres circles are rotated.
557
    Bresenham method is also known as midpoint circle algorithm.
558
    Anti-aliased circle generator is available with `circle_perimeter_aa`.
559

560
    References
561
    ----------
562
    .. [1] J.E. Bresenham, "Algorithm for computer control of a digital
563
           plotter", IBM Systems journal, 4 (1965) 25-30.
564
    .. [2] E. Andres, "Discrete circles, rings and spheres", Computers &
565
           Graphics, 18 (1994) 695-706.
566

567
    Examples
568
    --------
569
    >>> from skimage.draw import circle_perimeter
570
    >>> img = np.zeros((10, 10), dtype=np.uint8)
571
    >>> rr, cc = circle_perimeter(4, 4, 3)
572
    >>> img[rr, cc] = 1
573
    >>> img
574
    array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
575
           [0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
576
           [0, 0, 1, 0, 0, 0, 1, 0, 0, 0],
577
           [0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
578
           [0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
579
           [0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
580
           [0, 0, 1, 0, 0, 0, 1, 0, 0, 0],
581
           [0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
582
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
583
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
584
    """
585
    return _circle_perimeter(r, c, radius, method, shape)
586

587

588
def circle_perimeter_aa(r, c, radius, shape=None):
589
    """Generate anti-aliased circle perimeter coordinates.
590

591
    Parameters
592
    ----------
593
    r, c : int
594
        Centre coordinate of circle.
595
    radius : int
596
        Radius of circle.
597
    shape : tuple, optional
598
        Image shape which is used to determine the maximum extent of output
599
        pixel coordinates. This is useful for circles that exceed the image
600
        size. If None, the full extent of the circle is used.  Must be at least
601
        length 2. Only the first two values are used to determine the extent of
602
        the input image.
603

604
    Returns
605
    -------
606
    rr, cc, val : (N,) ndarray (int, int, float)
607
        Indices of pixels (`rr`, `cc`) and intensity values (`val`).
608
        ``img[rr, cc] = val``.
609

610
    Notes
611
    -----
612
    Wu's method draws anti-aliased circle. This implementation doesn't use
613
    lookup table optimization.
614

615
    Use the function ``draw.set_color`` to apply ``circle_perimeter_aa``
616
    results to color images.
617

618
    References
619
    ----------
620
    .. [1] X. Wu, "An efficient antialiasing technique", In ACM SIGGRAPH
621
           Computer Graphics, 25 (1991) 143-152.
622

623
    Examples
624
    --------
625
    >>> from skimage.draw import circle_perimeter_aa
626
    >>> img = np.zeros((10, 10), dtype=np.uint8)
627
    >>> rr, cc, val = circle_perimeter_aa(4, 4, 3)
628
    >>> img[rr, cc] = val * 255
629
    >>> img
630
    array([[  0,   0,   0,   0,   0,   0,   0,   0,   0,   0],
631
           [  0,   0,  60, 211, 255, 211,  60,   0,   0,   0],
632
           [  0,  60, 194,  43,   0,  43, 194,  60,   0,   0],
633
           [  0, 211,  43,   0,   0,   0,  43, 211,   0,   0],
634
           [  0, 255,   0,   0,   0,   0,   0, 255,   0,   0],
635
           [  0, 211,  43,   0,   0,   0,  43, 211,   0,   0],
636
           [  0,  60, 194,  43,   0,  43, 194,  60,   0,   0],
637
           [  0,   0,  60, 211, 255, 211,  60,   0,   0,   0],
638
           [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0],
639
           [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0]], dtype=uint8)
640

641
    >>> from skimage import data, draw
642
    >>> image = data.chelsea()
643
    >>> rr, cc, val = draw.circle_perimeter_aa(r=100, c=100, radius=75)
644
    >>> draw.set_color(image, (rr, cc), [1, 0, 0], alpha=val)
645
    """
646
    return _circle_perimeter_aa(r, c, radius, shape)
647

648

649
def ellipse_perimeter(r, c, r_radius, c_radius, orientation=0, shape=None):
650
    """Generate ellipse perimeter coordinates.
651

652
    Parameters
653
    ----------
654
    r, c : int
655
        Centre coordinate of ellipse.
656
    r_radius, c_radius : int
657
        Minor and major semi-axes. ``(r/r_radius)**2 + (c/c_radius)**2 = 1``.
658
    orientation : double, optional
659
        Major axis orientation in clockwise direction as radians.
660
    shape : tuple, optional
661
        Image shape which is used to determine the maximum extent of output
662
        pixel coordinates. This is useful for ellipses that exceed the image
663
        size. If None, the full extent of the ellipse is used.  Must be at
664
        least length 2. Only the first two values are used to determine the
665
        extent of the input image.
666

667
    Returns
668
    -------
669
    rr, cc : (N,) ndarray of int
670
        Indices of pixels that belong to the ellipse perimeter.
671
        May be used to directly index into an array, e.g.
672
        ``img[rr, cc] = 1``.
673

674
    References
675
    ----------
676
    .. [1] A Rasterizing Algorithm for Drawing Curves, A. Zingl, 2012
677
           http://members.chello.at/easyfilter/Bresenham.pdf
678

679
    Examples
680
    --------
681
    >>> from skimage.draw import ellipse_perimeter
682
    >>> img = np.zeros((10, 10), dtype=np.uint8)
683
    >>> rr, cc = ellipse_perimeter(5, 5, 3, 4)
684
    >>> img[rr, cc] = 1
685
    >>> img
686
    array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
687
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
688
           [0, 0, 0, 1, 1, 1, 1, 1, 0, 0],
689
           [0, 0, 1, 0, 0, 0, 0, 0, 1, 0],
690
           [0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
691
           [0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
692
           [0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
693
           [0, 0, 1, 0, 0, 0, 0, 0, 1, 0],
694
           [0, 0, 0, 1, 1, 1, 1, 1, 0, 0],
695
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
696

697

698
    Note that the positions of `ellipse` without specified `shape` can have
699
    also, negative values, as this is correct on the plane. On the other hand
700
    using these ellipse positions for an image afterwards may lead to appearing
701
    on the other side of image, because ``image[-1, -1] = image[end-1, end-1]``
702

703
    >>> rr, cc = ellipse_perimeter(2, 3, 4, 5)
704
    >>> img = np.zeros((9, 12), dtype=np.uint8)
705
    >>> img[rr, cc] = 1
706
    >>> img
707
    array([[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1],
708
           [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0],
709
           [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0],
710
           [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0],
711
           [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1],
712
           [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
713
           [0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
714
           [0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
715
           [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]], dtype=uint8)
716
    """
717
    return _ellipse_perimeter(r, c, r_radius, c_radius, orientation, shape)
718

719

720
def bezier_curve(r0, c0, r1, c1, r2, c2, weight, shape=None):
721
    """Generate Bezier curve coordinates.
722

723
    Parameters
724
    ----------
725
    r0, c0 : int
726
        Coordinates of the first control point.
727
    r1, c1 : int
728
        Coordinates of the middle control point.
729
    r2, c2 : int
730
        Coordinates of the last control point.
731
    weight : double
732
        Middle control point weight, it describes the line tension.
733
    shape : tuple, optional
734
        Image shape which is used to determine the maximum extent of output
735
        pixel coordinates. This is useful for curves that exceed the image
736
        size. If None, the full extent of the curve is used.
737

738
    Returns
739
    -------
740
    rr, cc : (N,) ndarray of int
741
        Indices of pixels that belong to the Bezier curve.
742
        May be used to directly index into an array, e.g.
743
        ``img[rr, cc] = 1``.
744

745
    Notes
746
    -----
747
    The algorithm is the rational quadratic algorithm presented in
748
    reference [1]_.
749

750
    References
751
    ----------
752
    .. [1] A Rasterizing Algorithm for Drawing Curves, A. Zingl, 2012
753
           http://members.chello.at/easyfilter/Bresenham.pdf
754

755
    Examples
756
    --------
757
    >>> import numpy as np
758
    >>> from skimage.draw import bezier_curve
759
    >>> img = np.zeros((10, 10), dtype=np.uint8)
760
    >>> rr, cc = bezier_curve(1, 5, 5, -2, 8, 8, 2)
761
    >>> img[rr, cc] = 1
762
    >>> img
763
    array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
764
           [0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
765
           [0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
766
           [0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
767
           [0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
768
           [0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
769
           [0, 0, 1, 1, 0, 0, 0, 0, 0, 0],
770
           [0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
771
           [0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
772
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
773
    """
774
    return _bezier_curve(r0, c0, r1, c1, r2, c2, weight, shape)
775

776

777
def rectangle(start, end=None, extent=None, shape=None):
778
    """Generate coordinates of pixels within a rectangle.
779

780
    Parameters
781
    ----------
782
    start : tuple
783
        Origin point of the rectangle, e.g., ``([plane,] row, column)``.
784
    end : tuple
785
        End point of the rectangle ``([plane,] row, column)``.
786
        For a 2D matrix, the slice defined by the rectangle is
787
        ``[start:(end+1)]``.
788
        Either `end` or `extent` must be specified.
789
    extent : tuple
790
        The extent (size) of the drawn rectangle.  E.g.,
791
        ``([num_planes,] num_rows, num_cols)``.
792
        Either `end` or `extent` must be specified.
793
        A negative extent is valid, and will result in a rectangle
794
        going along the opposite direction. If extent is negative, the
795
        `start` point is not included.
796
    shape : tuple, optional
797
        Image shape used to determine the maximum bounds of the output
798
        coordinates. This is useful for clipping rectangles that exceed
799
        the image size. By default, no clipping is done.
800

801
    Returns
802
    -------
803
    coords : array of int, shape (Ndim, Npoints)
804
        The coordinates of all pixels in the rectangle.
805

806
    Notes
807
    -----
808
    This function can be applied to N-dimensional images, by passing `start` and
809
    `end` or `extent` as tuples of length N.
810

811
    Examples
812
    --------
813
    >>> import numpy as np
814
    >>> from skimage.draw import rectangle
815
    >>> img = np.zeros((5, 5), dtype=np.uint8)
816
    >>> start = (1, 1)
817
    >>> extent = (3, 3)
818
    >>> rr, cc = rectangle(start, extent=extent, shape=img.shape)
819
    >>> img[rr, cc] = 1
820
    >>> img
821
    array([[0, 0, 0, 0, 0],
822
           [0, 1, 1, 1, 0],
823
           [0, 1, 1, 1, 0],
824
           [0, 1, 1, 1, 0],
825
           [0, 0, 0, 0, 0]], dtype=uint8)
826

827

828
    >>> img = np.zeros((5, 5), dtype=np.uint8)
829
    >>> start = (0, 1)
830
    >>> end = (3, 3)
831
    >>> rr, cc = rectangle(start, end=end, shape=img.shape)
832
    >>> img[rr, cc] = 1
833
    >>> img
834
    array([[0, 1, 1, 1, 0],
835
           [0, 1, 1, 1, 0],
836
           [0, 1, 1, 1, 0],
837
           [0, 1, 1, 1, 0],
838
           [0, 0, 0, 0, 0]], dtype=uint8)
839

840
    >>> import numpy as np
841
    >>> from skimage.draw import rectangle
842
    >>> img = np.zeros((6, 6), dtype=np.uint8)
843
    >>> start = (3, 3)
844
    >>>
845
    >>> rr, cc = rectangle(start, extent=(2, 2))
846
    >>> img[rr, cc] = 1
847
    >>> rr, cc = rectangle(start, extent=(-2, 2))
848
    >>> img[rr, cc] = 2
849
    >>> rr, cc = rectangle(start, extent=(-2, -2))
850
    >>> img[rr, cc] = 3
851
    >>> rr, cc = rectangle(start, extent=(2, -2))
852
    >>> img[rr, cc] = 4
853
    >>> print(img)
854
    [[0 0 0 0 0 0]
855
     [0 3 3 2 2 0]
856
     [0 3 3 2 2 0]
857
     [0 4 4 1 1 0]
858
     [0 4 4 1 1 0]
859
     [0 0 0 0 0 0]]
860

861
    """
862
    tl, br = _rectangle_slice(start=start, end=end, extent=extent)
863

864
    if shape is not None:
865
        n_dim = len(start)
866
        br = np.minimum(shape[0:n_dim], br)
867
        tl = np.maximum(np.zeros_like(shape[0:n_dim]), tl)
868
    coords = np.meshgrid(*[np.arange(st, en) for st, en in zip(tuple(tl), tuple(br))])
869
    return coords
870

871

872
@require("matplotlib", ">=3.3")
873
def rectangle_perimeter(start, end=None, extent=None, shape=None, clip=False):
874
    """Generate coordinates of pixels that are exactly around a rectangle.
875

876
    Parameters
877
    ----------
878
    start : tuple
879
        Origin point of the inner rectangle, e.g., ``(row, column)``.
880
    end : tuple
881
        End point of the inner rectangle ``(row, column)``.
882
        For a 2D matrix, the slice defined by inner the rectangle is
883
        ``[start:(end+1)]``.
884
        Either `end` or `extent` must be specified.
885
    extent : tuple
886
        The extent (size) of the inner rectangle.  E.g.,
887
        ``(num_rows, num_cols)``.
888
        Either `end` or `extent` must be specified.
889
        Negative extents are permitted. See `rectangle` to better
890
        understand how they behave.
891
    shape : tuple, optional
892
        Image shape used to determine the maximum bounds of the output
893
        coordinates. This is useful for clipping perimeters that exceed
894
        the image size. By default, no clipping is done.  Must be at least
895
        length 2. Only the first two values are used to determine the extent of
896
        the input image.
897
    clip : bool, optional
898
        Whether to clip the perimeter to the provided shape. If this is set
899
        to True, the drawn figure will always be a closed polygon with all
900
        edges visible.
901

902
    Returns
903
    -------
904
    coords : array of int, shape (2, Npoints)
905
        The coordinates of all pixels in the rectangle.
906

907
    Examples
908
    --------
909
    .. testsetup::
910
        >>> import pytest; _ = pytest.importorskip('matplotlib')
911

912
    >>> import numpy as np
913
    >>> from skimage.draw import rectangle_perimeter
914
    >>> img = np.zeros((5, 6), dtype=np.uint8)
915
    >>> start = (2, 3)
916
    >>> end = (3, 4)
917
    >>> rr, cc = rectangle_perimeter(start, end=end, shape=img.shape)
918
    >>> img[rr, cc] = 1
919
    >>> img
920
    array([[0, 0, 0, 0, 0, 0],
921
           [0, 0, 1, 1, 1, 1],
922
           [0, 0, 1, 0, 0, 1],
923
           [0, 0, 1, 0, 0, 1],
924
           [0, 0, 1, 1, 1, 1]], dtype=uint8)
925

926
    >>> img = np.zeros((5, 5), dtype=np.uint8)
927
    >>> r, c = rectangle_perimeter(start, (10, 10), shape=img.shape, clip=True)
928
    >>> img[r, c] = 1
929
    >>> img
930
    array([[0, 0, 0, 0, 0],
931
           [0, 0, 1, 1, 1],
932
           [0, 0, 1, 0, 1],
933
           [0, 0, 1, 0, 1],
934
           [0, 0, 1, 1, 1]], dtype=uint8)
935

936
    """
937
    top_left, bottom_right = _rectangle_slice(start=start, end=end, extent=extent)
938

939
    top_left -= 1
940
    r = [top_left[0], top_left[0], bottom_right[0], bottom_right[0], top_left[0]]
941
    c = [top_left[1], bottom_right[1], bottom_right[1], top_left[1], top_left[1]]
942
    return polygon_perimeter(r, c, shape=shape, clip=clip)
943

944

945
def _rectangle_slice(start, end=None, extent=None):
946
    """Return the slice ``(top_left, bottom_right)`` of the rectangle.
947

948
    Returns
949
    -------
950
    (top_left, bottom_right)
951
        The slice you would need to select the region in the rectangle defined
952
        by the parameters.
953
        Select it like:
954

955
        ``rect[top_left[0]:bottom_right[0], top_left[1]:bottom_right[1]]``
956
    """
957
    if end is None and extent is None:
958
        raise ValueError("Either `end` or `extent` must be given.")
959
    if end is not None and extent is not None:
960
        raise ValueError("Cannot provide both `end` and `extent`.")
961

962
    if extent is not None:
963
        end = np.asarray(start) + np.asarray(extent)
964
    top_left = np.minimum(start, end)
965
    bottom_right = np.maximum(start, end)
966

967
    top_left = np.round(top_left).astype(int)
968
    bottom_right = np.round(bottom_right).astype(int)
969

970
    if extent is None:
971
        bottom_right += 1
972

973
    return (top_left, bottom_right)
974

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.