Source code for src.imagedata.apps.Siemens.draw_antialiased

"""Library to draw an antialiased line.
  http://stackoverflow.com/questions/3122049/drawing-an-anti-aliased-line-with-thepython-imaging-library
  https://en.wikipedia.org/wiki/Xiaolin_Wu%27s_line_algorithm

  https://yellowsplash.wordpress.com/2009/10/23/fast-antialiased-circles-and-ellipses-from-xiaolin-wus-concepts/
  https://stackoverflow.com/questions/37589165/drawing-an-antialiased-circle-as-described-by-xaolin-wu#37714284
"""

import math
import numpy as np
from matplotlib.path import Path


[docs] def draw_polygon_mask(canvas, points, colour, threshold, fill=True): """Make a 2D mask [ny,nx] on canvas Optionally, fill the polygon with the same colour. Args: canvas: numpy array points: polygon points (list of tuples (x,y)) (float) colour: colour to draw (mask index) (int) threshold: only pixels with an alpha > threshold will be drawn (float, 0.0-1.0) fill: whether to fill the circle (boolean) """ # Flag the voxels that will be in the mask mask = np.zeros_like(canvas, dtype=bool) # Colour the voxels on the polygon with the True colour pn = points[len(points) - 1] # Close the polygon for p in points: x1, y1 = pn x2, y2 = p draw_line_mask(mask, x1, y1, x2, y2, True, threshold) pn = p # canvas.save("/tmp/polygon.png", "PNG") if fill: # Colour the voxels inside the polygon with the True colour inside = point_in_polygon(mask, points) mask = np.logical_or(mask, inside) # Set voxels in the mask to the given colour canvas[mask] = colour
[docs] def plot(canvas, x, y, steep, colour, alpha, threshold): """Draws an anti-aliased pixel on a line.""" if steep: x, y = y, x # if x < canvas.shape[1] and y < canvas.shape[0] and x >= 0 and y >= 0: if 0 <= x < canvas.shape[1] and 0 <= y < canvas.shape[0]: x = int(x) y = int(y) if alpha >= threshold: canvas[y, x] = colour
[docs] def iround(x): """Rounds x to the nearest integer.""" return ipart(x + 0.5)
[docs] def ipart(x): """Floors x.""" return math.floor(x)
[docs] def fpart(x): """Returns the fractional part of x.""" return x - math.floor(x)
[docs] def rfpart(x): """Returns the 1 minus the fractional part of x.""" return 1 - fpart(x)
[docs] def draw_line_mask(canvas, x1, y1, x2, y2, colour, threshold): """Draw line mask on NumPy array. Apply the Xialon Wu anti-aliasing algorithm for drawing line. Draw points only when the alpha blending is above a set threshold. Only given colour value is drawn. Intended usage is as a mask index. Args: canvas: 2D numpy array (np.ndarray) x1 (float): line end points y1 (float): line end points x2 (float): line end points y2 (float): line end points colour (int): colour to draw 'mask index' threshold (float): only pixels with an alpha > threshold will be drawn, 0.0-1.0 """ dx = x2 - x1 # if not dx: # # Vertical line # draw_line((x1, y1, x2, y2), fill=col, width=1) # return dy = y2 - y1 steep = abs(dx) < abs(dy) if steep: x1, y1 = y1, x1 x2, y2 = y2, x2 dx, dy = dy, dx if x2 < x1: x1, x2 = x2, x1 y1, y2 = y2, y1 try: gradient = float(dy) / float(dx) except ZeroDivisionError: gradient = 1.0 # Handle first endpoint xend = round(x1) yend = y1 + gradient * (xend - x1) xgap = rfpart(x1 + 0.5) xpxl1 = xend # this will be used in the main loop ypxl1 = ipart(yend) plot(canvas, xpxl1, ypxl1, steep, colour, rfpart(yend) * xgap, threshold) plot(canvas, xpxl1, ypxl1 + 1, steep, colour, fpart(yend) * xgap, threshold) intery = yend + gradient # first y-intersection for the main loop # handle second endpoint xend = round(x2) yend = y2 + gradient * (xend - x2) xgap = fpart(x2 + 0.5) xpxl2 = xend # this will be used in the main loop ypxl2 = ipart(yend) plot(canvas, xpxl2, ypxl2, steep, colour, rfpart(yend) * xgap, threshold) plot(canvas, xpxl2, ypxl2 + 1, steep, colour, fpart(yend) * xgap, threshold) # main loop for x in range(int(xpxl1 + 1), int(xpxl2)): plot(canvas, x, ipart(intery), steep, colour, rfpart(intery), threshold) plot(canvas, x, ipart(intery) + 1, steep, colour, fpart(intery), threshold) intery = intery + gradient
[docs] def point_in_polygon(canvas, polygon): ny, nx = canvas.shape # Create vertex coordinates for each grid cell... # (<0,0> is at the top left of the grid in this system) x, y = np.meshgrid(np.arange(nx), np.arange(ny)) x, y = x.flatten(), y.flatten() points = np.vstack((x, y)).T path = Path(polygon) grid = path.contains_points(points) grid = grid.reshape((ny, nx)) return grid
[docs] def draw_circle_mask(canvas, center_x, center_y, outer_radius, colour, threshold, fill=True): """Draw circle mask on NumPy array. Apply algorithm for drawing anti-aliased circle. Draw points only when the alpha blending is above a set threshold. Only given _colour value is drawn. Intended usage is as a mask index. Optionally, fill the circle with the same _colour. Reference: https://stackoverflow.com/questions/37589165/drawing-an-antialiased-circle-as-described-by-xaolin-wu#37714284 Args: canvas: numpy array center_x, center_y: center of circle in array coordinates (int) outer_radius: radius of circle in array dimension (float) colour: _colour to draw (mask index) (int) threshold: only pixels with an alpha > threshold will be drawn (float, 0.0-1.0) fill: whether to fill the circle (boolean) """ def _draw_8point(_canvas, _cx, _cy, _i, _j, _colour): """Draws 8 points, one on each octant.""" # Square symmetry local_coord = [(_i * (-1) ** (k % 2), _j * (-1) ** (k // 2)) for k in range(4)] # Diagonal symmetry local_coord += [(j_, i_) for i_, j_ in local_coord] for i_, j_ in local_coord: # print("_draw_8point", _cy + j_, _cx + i_) _canvas[_cy + j_, _cx + i_] = _colour i = 0 j = outer_radius last_fade_amount = 0 # fade_amount = 0 max_opaque = 1.0 while i < j: height = math.sqrt(max(outer_radius * outer_radius - i * i, 0)) fade_amount = max_opaque * (math.ceil(height) - height) if fade_amount < last_fade_amount: # Opaqueness reset so drop down a row. j -= 1 last_fade_amount = fade_amount # We're fading out the current _j row, and fading in the next one down. if max_opaque - fade_amount > threshold: _draw_8point(canvas, center_x, center_y, i, j, colour) if fade_amount > threshold: _draw_8point(canvas, center_x, center_y, i, j - 1, colour) i += 1 if fill: boundary_fill4(canvas, center_x, center_y, colour, colour)
[docs] def draw_ellipse_mask(canvas, center_x, center_y, outer_radius, colour, threshold, fill=True): """Draw ellipse mask on NumPy array. Apply algorithm for drawing anti-aliased ellipse. Draw points only when the alpha blending is above a set threshold. Only given _colour value is drawn. Intended usage is as a mask index. Optionally, fill the ellipse with the same _colour. Reference: https://yellowsplash.wordpress.com/2009/10/23/fast-antialiased-circles-and-ellipses-from-xiaolin-wus-concepts/ https://stackoverflow.com/questions/37589165/drawing-an-antialiased-circle-as-described-by-xaolin-wu#37714284 Args: canvas: numpy array center_x, center_y: center of ellipse in array coordinates (int) outer_radius: radius of circle in array dimension (float) colour: colour to draw (mask index) (int) threshold: only pixels with an alpha > threshold will be drawn (float, 0.0-1.0) fill: whether to fill the circle (boolean) """ def _draw_4point(_canvas, _cx, _cy, x, y, _colour): # Draw the 8 symmetries print("_draw_8point", _cy, _cx, y, x) print("_draw_8point", _cy + y, _cx - x) print("_draw_8point", _cy + y, _cx + x) print("_draw_8point", _cy - y, _cx - x) print("_draw_8point", _cy - y, _cx + x) _canvas[_cy + y, _cx - x] = _colour _canvas[_cy + y, _cx + x] = _colour _canvas[_cy - y, _cx - x] = _colour _canvas[_cy - y, _cx + x] = _colour i = 0 j = outer_radius last_fade_amount = 0 # fade_amount = 0 max_opaque = 1.0 while i < j: height = math.sqrt(max(outer_radius * outer_radius - i * i, 0)) fade_amount = max_opaque * (math.ceil(height) - height) if fade_amount < last_fade_amount: # Opaqueness reset so drop down a row. j -= 1 last_fade_amount = fade_amount # We're fading out the current j row, and fading in the next one down. if max_opaque - fade_amount > threshold: _draw_4point(canvas, center_x, center_y, i, j, colour) if fade_amount > threshold: _draw_4point(canvas, center_x, center_y, i, j - 1, colour) i += 1 if fill: boundary_fill4(canvas, center_x, center_y, colour, colour)
[docs] def flood_fill4(canvas, start_x, start_y, old_value, fill_value): x, y = start_x, start_y if 0 <= x < canvas.shape[1] and 0 <= y < canvas.shape[0]: if canvas[y, x] == old_value: canvas[y, x] = fill_value # Attempt to propagate in each of four directions flood_fill4(canvas, x, y - 1, old_value, fill_value) flood_fill4(canvas, x, y + 1, old_value, fill_value) flood_fill4(canvas, x - 1, y, old_value, fill_value) flood_fill4(canvas, x + 1, y, old_value, fill_value)
[docs] def boundary_fill4(canvas, start_x, start_y, boundary_value, fill_value): x, y = start_x, start_y if 0 <= x < canvas.shape[1] and 0 <= y < canvas.shape[0]: if canvas[y, x] != boundary_value and canvas[y, x] != fill_value: canvas[y, x] = fill_value boundary_fill4(canvas, x, y - 1, boundary_value, fill_value) boundary_fill4(canvas, x, y + 1, boundary_value, fill_value) boundary_fill4(canvas, x - 1, y, boundary_value, fill_value) boundary_fill4(canvas, x + 1, y, boundary_value, fill_value)