Add manhattanization functionality
This commit is contained in:
parent
3d52566016
commit
ffbaf8f4c4
@ -118,6 +118,25 @@ class Pattern:
|
|||||||
subpat.pattern.polygonize(poly_num_points, poly_max_arclen)
|
subpat.pattern.polygonize(poly_num_points, poly_max_arclen)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def manhattanize(self,
|
||||||
|
grid_x: numpy.ndarray,
|
||||||
|
grid_y: numpy.ndarray
|
||||||
|
) -> 'Pattern':
|
||||||
|
"""
|
||||||
|
Calls .polygonize() and .flatten on the pattern, then calls .manhattanize() on all the
|
||||||
|
resulting shapes, replacing them with the returned Manhattan polygons.
|
||||||
|
|
||||||
|
:param grid_x: List of allowed x-coordinates for the Manhattanized polygon edges.
|
||||||
|
:param grid_y: List of allowed y-coordinates for the Manhattanized polygon edges.
|
||||||
|
:return: self
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.polygonize().flatten()
|
||||||
|
old_shapes = self.shapes
|
||||||
|
self.shapes = list(itertools.chain.from_iterable(
|
||||||
|
(shape.manhattanize(grid_x, grid_y) for shape in old_shapes)))
|
||||||
|
return self
|
||||||
|
|
||||||
def subpatternize(self,
|
def subpatternize(self,
|
||||||
recursive: bool=True,
|
recursive: bool=True,
|
||||||
norm_value: int=1e6,
|
norm_value: int=1e6,
|
||||||
|
@ -176,3 +176,94 @@ class Shape(metaclass=ABCMeta):
|
|||||||
self.translate(+pivot)
|
self.translate(+pivot)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def manhattanize(self, grid_x: numpy.ndarray, grid_y: numpy.ndarray) -> List['Polygon']:
|
||||||
|
"""
|
||||||
|
Returns a list of polygons with grid-aligned ("Manhattan") edges approximating the shape.
|
||||||
|
|
||||||
|
This function works by
|
||||||
|
1) Converting the shape to polygons using .to_polygons()
|
||||||
|
2) Accurately rasterizing each polygon on a grid,
|
||||||
|
where the edges of each grid cell correspond to the allowed coordinates
|
||||||
|
3) Thresholding the (anti-aliased) rasterized image
|
||||||
|
4) Finding the contours which outline the filled areas in the thresholded image
|
||||||
|
This process results in a fairly accurate Manhattan representation of the shape. Possible
|
||||||
|
caveats include:
|
||||||
|
a) If high accuracy is important, perform any polygonization and clipping operations
|
||||||
|
prior to calling this function. This allows you to specify any arguments you may
|
||||||
|
need for .to_polygons(), and also avoids calling .manhattanize() multiple times for
|
||||||
|
the same grid location (which causes inaccuracies in the final representation).
|
||||||
|
b) If the shape is very large or the grid very fine, memory requirements can be reduced
|
||||||
|
by breaking the shape apart into multiple, smaller shapes.
|
||||||
|
c) Inaccuracies in edge shape can result from Manhattanization of edges which are
|
||||||
|
equidistant from allowed edge location.
|
||||||
|
|
||||||
|
Implementation notes:
|
||||||
|
i) Rasterization is performed using float_raster, giving a high-precision anti-aliased
|
||||||
|
rasterized image.
|
||||||
|
ii) To find the exact polygon edges, the thresholded rasterized image is supersampled
|
||||||
|
prior to calling skimage.measure.find_contours(), which uses marching squares
|
||||||
|
to find the contours. This is done because find_contours() performs interpolation,
|
||||||
|
which has to be undone in order to regain the axis-aligned contours. A targetted
|
||||||
|
rewrite of find_contours() for this specific application, or use of a different
|
||||||
|
boundary tracing method could remove this requirement, but for now this seems to
|
||||||
|
be the most performant approach.
|
||||||
|
|
||||||
|
:param grid_x: List of allowed x-coordinates for the Manhattanized polygon edges.
|
||||||
|
:param grid_y: List of allowed y-coordinates for the Manhattanized polygon edges.
|
||||||
|
:return: List of Polygon objects with grid-aligned edges.
|
||||||
|
"""
|
||||||
|
from . import Polygon
|
||||||
|
import skimage.measure
|
||||||
|
import float_raster
|
||||||
|
|
||||||
|
grid_x = numpy.unique(grid_x)
|
||||||
|
grid_y = numpy.unique(grid_y)
|
||||||
|
|
||||||
|
polygon_contours = []
|
||||||
|
for polygon in self.to_polygons():
|
||||||
|
mins, maxs = polygon.get_bounds()
|
||||||
|
keep_x = numpy.logical_and(grid_x > mins[0], grid_x < maxs[0])
|
||||||
|
keep_y = numpy.logical_and(grid_y > mins[1], grid_y < maxs[1])
|
||||||
|
for k in (keep_x, keep_y):
|
||||||
|
for s in (1, 2):
|
||||||
|
k[s:] += k[:-s]
|
||||||
|
k[:-s] += k[s:]
|
||||||
|
k = k > 0
|
||||||
|
|
||||||
|
gx = grid_x[keep_x]
|
||||||
|
gy = grid_y[keep_y]
|
||||||
|
|
||||||
|
if len(gx) == 0 or len(gy) == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
offset = (numpy.where(keep_x)[0][0],
|
||||||
|
numpy.where(keep_y)[0][0])
|
||||||
|
|
||||||
|
rastered = float_raster.raster((polygon.vertices + polygon.offset).T, gx, gy)
|
||||||
|
binary_rastered = (rastered >= 0.5)
|
||||||
|
supersampled = binary_rastered.repeat(2, axis=0).repeat(2, axis=1)
|
||||||
|
|
||||||
|
from matplotlib import pyplot
|
||||||
|
pyplot.pcolormesh(binary_rastered)
|
||||||
|
pyplot.colorbar()
|
||||||
|
pyplot.show()
|
||||||
|
|
||||||
|
contours = skimage.measure.find_contours(supersampled, 0.5)
|
||||||
|
polygon_contours.append((offset, contours))
|
||||||
|
|
||||||
|
manhattan_polygons = []
|
||||||
|
for offset_i, contours in polygon_contours:
|
||||||
|
for contour in contours:
|
||||||
|
# /2 deals with supersampling
|
||||||
|
# +.5 deals with the fact that our 0-edge becomes -.5 in the super-sampled contour output
|
||||||
|
snapped_contour = numpy.round((contour + .5) / 2).astype(int)
|
||||||
|
vertices = numpy.hstack((grid_x[snapped_contour[:, None, 0] + offset_i[0]],
|
||||||
|
grid_y[snapped_contour[:, None, 1] + offset_i[1]]))
|
||||||
|
|
||||||
|
manhattan_polygons.append(Polygon(
|
||||||
|
vertices=vertices,
|
||||||
|
layer=self.layer,
|
||||||
|
dose=self.dose))
|
||||||
|
|
||||||
|
return manhattan_polygons
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user