summaryrefslogtreecommitdiff
path: root/silx/image/shapes.pyx
diff options
context:
space:
mode:
Diffstat (limited to 'silx/image/shapes.pyx')
-rw-r--r--silx/image/shapes.pyx319
1 files changed, 0 insertions, 319 deletions
diff --git a/silx/image/shapes.pyx b/silx/image/shapes.pyx
deleted file mode 100644
index 4fb2e33..0000000
--- a/silx/image/shapes.pyx
+++ /dev/null
@@ -1,319 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2015-2017 European Synchrotron Radiation Facility
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ############################################################################*/
-"""This module provides functions making masks on an image.
-
-- :func:`circle_fill` function generates coordinates of a circle in an image.
-- :func:`draw_line` function generates coordinates of a line in an image.
-- :func:`polygon_fill_mask` function generates a mask from a set of points
- defining a polygon.
-
-The :class:`Polygon` class provides checking if a point is inside a polygon.
-
-The whole module uses the (row, col) (i.e., (y, x))) convention
-for 2D coordinates.
-"""
-
-__authors__ = ["Jérôme Kieffer", "T. Vincent"]
-__license__ = "MIT"
-__date__ = "15/02/2019"
-__status__ = "dev"
-
-
-cimport cython
-import numpy
-from libc.math cimport ceil, fabs
-
-
-cdef class Polygon(object):
- """Define a polygon that provides inside check and mask generation.
-
- :param vertices: corners of the polygon
- :type vertices: Nx2 array of floats of (row, col)
- """
-
- cdef float[:,:] vertices
- cdef int nvert
-
- def __init__(self, vertices):
- self.vertices = numpy.ascontiguousarray(vertices, dtype=numpy.float32)
- self.nvert = self.vertices.shape[0]
-
- def is_inside(self, row, col):
- """Check if (row, col) is inside or outside the polygon
-
- :param float row:
- :param float col:
- :return: True if position is inside polygon, False otherwise
- """
- return self.c_is_inside(row, col)
-
- @cython.cdivision(True)
- @cython.wraparound(False)
- @cython.boundscheck(False)
- cdef bint c_is_inside(self, float row, float col) nogil:
- """Check if (row, col) is inside or outside the polygon
-
- Pure C_Cython class implementation.
-
- :param float row:
- :param float col:
- :return: True if position is inside polygon, False otherwise
- """
- cdef int index, is_inside
- cdef float pt1x, pt1y, pt2x, pt2y, xinters
- is_inside = 0
-
- pt1x = self.vertices[self.nvert-1, 1]
- pt1y = self.vertices[self.nvert-1, 0]
- for index in range(self.nvert):
- pt2x = self.vertices[index, 1]
- pt2y = self.vertices[index, 0]
-
- if (((pt1y <= row and row < pt2y) or
- (pt2y <= row and row < pt1y)) and
- # Extra (optional) condition to avoid some computation
- (col <= pt1x or col <= pt2x)):
- xinters = (row - pt1y) * (pt2x - pt1x) / (pt2y - pt1y) + pt1x
- is_inside ^= col < xinters
- pt1x, pt1y = pt2x, pt2y
- return is_inside
-
- @cython.cdivision(True)
- @cython.wraparound(False)
- @cython.boundscheck(False)
- def make_mask(self, int height, int width):
- """Create a mask array representing the filled polygon
-
- :param int height: Height of the mask array
- :param int width: Width of the mask array
- :return: 2D array (height, width)
- """
- cdef unsigned char[:, :] mask = numpy.zeros((height, width),
- dtype=numpy.uint8)
- cdef int row_min, row_max, col_min, col_max # mask subpart to update
- cdef int row, col, index # Loop indixes
- cdef float pt1x, pt1y, pt2x, pt2y # segment end points
- cdef int xinters, is_inside, current
-
- row_min = max(int(min(self.vertices[:, 0])), 0)
- row_max = min(int(max(self.vertices[:, 0])) + 1, height)
-
- # Can be replaced by prange(row_min, row_max, nogil=True)
- with nogil:
- for row in range(row_min, row_max):
- # For each line of the image, mark intersection of all segments
- # in the line and then run a xor scan to fill inner parts
- # Adapted from http://alienryderflex.com/polygon_fill/
- pt1x = self.vertices[self.nvert-1, 1]
- pt1y = self.vertices[self.nvert-1, 0]
- col_min = width - 1
- col_max = 0
- is_inside = 0 # Init with whether first col is inside or not
-
- for index in range(self.nvert):
- pt2x = self.vertices[index, 1]
- pt2y = self.vertices[index, 0]
-
- if ((pt1y <= row and row < pt2y) or
- (pt2y <= row and row < pt1y)):
- # Intersection casted to int so that ]x, x+1] => x
- xinters = (<int>ceil(pt1x + (row - pt1y) *
- (pt2x - pt1x) / (pt2y - pt1y))) - 1
-
- # Update column range to patch
- if xinters < col_min:
- col_min = xinters
- if xinters > col_max:
- col_max = xinters
-
- if xinters < 0:
- # Add an intersection to init value of xor scan
- is_inside ^= 1
- elif xinters < width:
- # Mark intersection in mask
- mask[row, xinters] ^= 1
- # else: do not consider intersection on the right
-
- pt1x, pt1y = pt2x, pt2y
-
- if col_min < col_max:
- # Clip column range to mask
- if col_min < 0:
- col_min = 0
- if col_max > width - 1:
- col_max = width - 1
-
- # xor exclusive scan
- for col in range(col_min, col_max + 1):
- current = mask[row, col]
- mask[row, col] = is_inside
- is_inside = current ^ is_inside
-
- # Ensures the result is exported as numpy array and not memory view.
- return numpy.asarray(mask)
-
-
-def polygon_fill_mask(vertices, shape):
- """Return a mask of boolean, True for pixels inside a polygon.
-
- :param vertices: Strip of segments end points (row, column) or (y, x)
- :type vertices: numpy.ndarray like container of dimension Nx2
- :param shape: size of the mask as (height, width)
- :type shape: 2-tuple of int
- :return: Mask corresponding to the polygon
- :rtype: numpy.ndarray of dimension shape
- """
- return Polygon(vertices).make_mask(shape[0], shape[1])
-
-
-@cython.wraparound(False)
-@cython.boundscheck(False)
-def draw_line(int row0, int col0, int row1, int col1, int width=1):
- """Line includes both end points.
- Width is handled by drawing parallel lines, so junctions of lines belonging
- to different octant with width > 1 will not look nice.
-
- Using Bresenham line algorithm:
- Bresenham, J. E.
- Algorithm for computer control of a digital plotter.
- IBM Systems Journal. Vol 4 No 1. 1965. pp 25-30
-
- :param int row0: Start point row
- :param int col0: Start point col
- :param int row1: End point row
- :param int col1: End point col
- :param int width: Thickness of the line in pixels (default 1)
- Width must be at least 1.
- :return: Array coordinates of points inside the line (might be negative)
- :rtype: 2-tuple of numpy.ndarray (rows, cols)
- """
- cdef int drow, dcol, invert_coords
- cdef int db, da, delta, b, a, step_a, step_b
- cdef int index, offset # Loop indices
-
- # Store coordinates of points of the line
- cdef int[:, :] b_coords
- cdef int[:, :] a_coords
-
- dcol = abs(col1 - col0)
- drow = abs(row1 - row0)
- invert_coords = dcol < drow
-
- if dcol == 0 and drow == 0:
- return (numpy.array((row0,), dtype=numpy.int32),
- numpy.array((col0,), dtype=numpy.int32))
-
- if width < 1:
- width = 1
-
- # Set a and b according to segment octant
- if not invert_coords:
- da = dcol
- db = drow
- step_a = 1 if col1 > col0 else -1
- step_b = 1 if row1 > row0 else -1
- a = col0
- b = row0
-
- else:
- da = drow
- db = dcol
- step_a = 1 if row1 > row0 else -1
- step_b = 1 if col1 > col0 else -1
- a = row0
- b = col0
-
- b_coords = numpy.empty((da + 1, width), dtype=numpy.int32)
- a_coords = numpy.empty((da + 1, width), dtype=numpy.int32)
-
- with nogil:
- b -= (width - 1) // 2
- delta = 2 * db - da
- for index in range(da + 1):
- for offset in range(width):
- b_coords[index, offset] = b + offset
- a_coords[index, offset] = a
-
- if delta >= 0: # M2: Move by step_a + step_b
- b += step_b
- delta -= 2 * da
- # else M1: Move by step_a
-
- a += step_a
- delta += 2 * db
-
- if not invert_coords:
- return (numpy.asarray(b_coords).reshape(-1),
- numpy.asarray(a_coords).reshape(-1))
- else:
- return (numpy.asarray(a_coords).reshape(-1),
- numpy.asarray(b_coords).reshape(-1))
-
-
-def circle_fill(int crow, int ccol, float radius):
- """Generates coordinate of image points lying in a disk.
-
- :param int crow: Row of the center of the disk
- :param int ccol: Column of the center of the disk
- :param float radius: Radius of the disk
- :return: Array coordinates of points inside the disk (might be negative)
- :rtype: 2-tuple of numpy.ndarray (rows, cols)
- """
- cdef int i_radius, len_coords
-
- radius = fabs(radius)
- i_radius = <int>radius
-
- coords = numpy.arange(-i_radius, ceil(radius) + 1,
- dtype=numpy.float32) ** 2
- len_coords = len(coords)
- # rows, cols = where(row**2 + col**2 < radius**2)
- rows, cols = numpy.where(coords.reshape(1, len_coords) +
- coords.reshape(len_coords, 1) < radius ** 2)
- return rows + crow - i_radius, cols + ccol - i_radius
-
-
-def ellipse_fill(int crow, int ccol, float radius_r, float radius_c):
- """Generates coordinate of image points lying in a ellipse.
-
- :param int crow: Row of the center of the ellipse
- :param int ccol: Column of the center of the ellipse
- :param float radius_r: Radius of the ellipse in the row
- :param float radius_c: Radius of the ellipse in the column
- :return: Array coordinates of points inside the ellipse (might be negative)
- :rtype: 2-tuple of numpy.ndarray (rows, cols)
- """
- cdef int i_radius_r
- cdef int i_radius_c
-
- i_radius_r = <int>fabs(radius_r)
- i_radius_c = <int>fabs(radius_c)
-
- x_coords = numpy.arange(-i_radius_r, ceil(radius_r) + 1, dtype=numpy.float32).reshape(-1, 1)
- y_coords = numpy.arange(-i_radius_c, ceil(radius_c) + 1, dtype=numpy.float32).reshape(1, -1)
-
- # rows, cols = where(x**2 + col**2 < 1)
- rows, cols = numpy.where(x_coords**2 / radius_r**2 + y_coords**2 / radius_c**2 < 1.0)
- return rows + crow - i_radius_r, cols + ccol - i_radius_c