def addEdge(self, points, simplifyEpsilon=0.5, container=True, figClass=fig.Polygon, **attr): """fe.addEdge(points, simplifyEpsilon, ...) Adds and returns exactly one fig.Polygon object representing the given points. You will probably want to use addClippedPoly() instead. If simplifyEpsilon (default: 0.5) is not None, simplifyPolygon is called on the *scaled* polygon (i.e. the default is to simplify the polygon to 0.5 fig units, which are integer anyways).""" if container == True: container = self.f o = self.offset + attr.get('offset', (0, 0)) if self.roi: o -= self.roi.begin() pp = Polygon([(o + point) * self.scale for point in points]) if simplifyEpsilon: pp = simplifyPolygon(pp, simplifyEpsilon) fp = figClass([intPos(v) for v in pp], closed=pp[0] == pp[-1]) for a in attr: if a != "offset": setattr(fp, a, attr[a]) container.append(fp) return fp
def smallestBoundingBox(ch): """Determine rotated bbox from convex hull""" # FIXME: use rotating calipers for O(N) instead of O(N^2)! assert ch.closed() bboxes = [] for seg in polyLineSegments(ch): line = seg.line() norm = line.norm dir = line.dir() dists = [] positions = [] for p in ch: dists.append(numpy.dot(norm, p)) positions.append(numpy.dot(dir, p)) l1 = min(positions) l2 = max(positions) l3 = min(dists) l4 = max(dists) area = (l2 - l1) * (l4 - l3) bboxes.append((area, line, l1, l2, l3, l4)) bboxes.sort() _, line, l1, l2, l3, l4 = bboxes[0] p1 = l1 * line.dir() + l3 * line.norm p2 = l1 * line.dir() + l4 * line.norm p3 = l2 * line.dir() + l4 * line.norm p4 = l2 * line.dir() + l3 * line.norm return Polygon([p1, p2, p3, p4, p1])
def addClippedPoly(self, polygon, **attr): """fe.addClippedPoly(polygon, ...) Adds and returns exactly fig.Polygon objects for each part of the given polygon which is in the clipping range. Again, note the possibility of setting properties (depth, penColor, lineStyle, lineWidth, ...) on all resulting objects via keyword arguments (cf. documentation of the FigExporter class). If simplifyEpsilon (default: 0.5) is not None, simplifyPolygon is called on the *scaled* polygon (i.e. the default is to simplify the polygon to 0.5 fig units, which are integer anyways).""" if "fillColor" in attr and not "fillStyle" in attr: attr["fillStyle"] = fig.FillStyle.Solid # no ROI to clip to? if not self.roi: return [self.addEdge(polygon, **attr)] if type(polygon) != Polygon: if not isinstance(polygon, list): polygon = Polygon(list(polygon)) else: polygon = Polygon(polygon) clipRect = BoundingBox(self.roi) o = self.offset + attr.get('offset', (0, 0)) clipRect.moveBy(-o) # handle all-or-none cases: if not clipRect.intersects(polygon.boundingBox()): return [] if clipRect.contains(polygon.boundingBox()): return [self.addEdge(polygon, **attr)] # general case: perform clipping, add parts one-by-one: result = [] # fig.Compound(container) - I dont't dare grouping here.. closeAtBorder = (attr.get("fillStyle", fig.FillStyle.None) != fig.FillStyle.None) for part in clipPoly(polygon, clipRect, closeAtBorder): if part.length(): # don't add zero-length polygons result.append(self.addEdge(part, **attr)) return result
def rotatePoly(poly, angle): """Rotate polygon by the given angle around the origin.""" unitX = (math.cos(angle), -math.sin(angle)) unitY = (math.sin(angle), math.cos(angle)) result = Polygon() for point in poly: result.append((numpy.dot(point, unitX), numpy.dot(point, unitY))) return result
def kochCurve(level=5): result = Polygon() p0 = Vector2(-0.5, -math.sqrt(1. / 12)) result.append(p0) p1 = p0 + Vector2(_kochCos, _kochSin) result.append(p1) p2 = p1 + Vector2(_kochCos, -_kochSin) result.append(p2) result.append(p0) for i in range(level): result = _kochIteration(result) return result
def addROIRect(self, roi=None, container=True, **attr): """fe.addROIRect(roi, depth = 85, ...) Adds a rectangle around the given roi (ignoring fe.offset). If roi == None (default), the roi of the FigExporter itself is used. The fig.PolyBox object is returned.""" assert roi or self.roi, "addROIRect(): no ROI given!?" if container == True: container = self.f if isinstance(roi, Rect2D): roi = BoundingBox(roi) roi.moveBy((-0.5, -0.5)) if roi is None: roi = self.roi assert not isinstance(roi, Rect2D) roi = BoundingBox(roi) roi.moveBy(-self.offset) # BAAH! What a bad design (self.roi) elif self.roi and not self.roi.contains(roi): sys.stderr.write("WARNING: addROIRect: ROI out of bounds!\n") poly = Polygon([ roi.begin(), roi.begin() + (0, roi.size()[1]), roi.end(), roi.begin() + (roi.size()[0], 0), roi.begin() ]) result = self.addClippedPoly(poly, container=container, **attr) if result: assert len(result) == 1 return result[0] return roi = BoundingBox(roi) roi.moveBy(self.offset) if self.roi: roi.moveBy(-self.roi.begin()) if "fillColor" in attr and not "fillStyle" in attr: attr["fillStyle"] = fig.FillStyle.Solid result = fig.PolyBox(roi.begin()[0] * self.scale, roi.begin()[1] * self.scale, roi.end()[0] * self.scale, roi.end()[1] * self.scale) container.append(result) for a in attr: setattr(result, a, attr[a]) return result
def followEdge(crackConnectionImage, pos, direction): """Follow edge starting at `pos` in `direction` until crackConnectionImage[pos] has the CONN_NODE bit set, or until we arrive at `pos` again (self-loop). Any CONN_MAYBE_NODE bits along the way are cleared.""" pos = Point2D(pos[0], pos[1]) vPos = Vector2(pos[0] - 0.5, pos[1] - 0.5) result = Polygon([vPos]) startPos = copy.copy(pos) while True: vPos += _dirVector[direction] result.append(vPos) pos += _dirOffset[direction] if pos == startPos: break connection = int(crackConnectionImage[pos]) if connection & CONN_DIAG: if connection & CONN_DIAG_UPLEFT: turnLeft = direction in (DIR_NORTH, DIR_SOUTH) else: turnLeft = direction in (DIR_EAST, DIR_WEST) connection &= ~connections[(direction + 2) % 4] if turnLeft: direction = _turnLeft[direction] else: direction = _turnRight[direction] connection &= ~connections[direction] if not connection & CONN_ALL4: connection &= ~CONN_MAYBE_NODE crackConnectionImage[pos] = connection continue elif connection & CONN_NODE: break if connection & CONN_MAYBE_NODE: # we simply pass over it, but we do not want to start a # new edge here during further down in the process: crackConnectionImage[pos] = connection & ~CONN_MAYBE_NODE direction = _turnRight[direction] while connection & connections[direction] == 0: direction = _turnLeft[direction] return result, pos, connections[(direction + 2) % 4]
def checkPolygons(map): clean = True for edge in map.edgeIter(): l, pa = edge.length(), edge.partialArea() p = Polygon(edge) p.invalidateProperties() if abs(l - p.length()) > 1e-6: print "edge %d: length wrong (was %s, is %s)" % (edge.label(), l, p.length()) clean = False if abs(pa - p.partialArea()) > 1e-6: print "edge %d: partial area wrong (was %s, is %s)" % ( edge.label(), pa, p.partialArea()) clean = False return clean
def euclideanPath(crackPoly, closed = None): """Return tangent-driven Euclidean Path for the given `crackPoly`. If the polygon is not `closed` (default/None: auto-detect), the first and last points will not be changed (no tangent known).""" fc = freeman(crackPoly) if closed == None: closed = crackPoly[-1] == crackPoly[0] result = Polygon(crackPoly) if closed: eo = offset2(fc, 0, closed) result[0] += eo result[-1] += eo for i in range(1, len(result)-1): result[i] += offset2(fc, i, closed) return result
def subsetDigitization(poly, shift=None, size=None): """Sample poly with a regular grid at integer coordinates starting from (0,0) to the given size (which should be a Size2D object).""" if size == None: size = poly.boundingBox().size() size = (int(math.ceil(size[0])) + 2, int(math.ceil(size[1])) + 2) if not shift: shift = (0, 0) shift = numpy.asarray(shift) + (1, 1) - poly.boundingBox().begin() poly = Polygon(poly + shift) result = vigra.GrayImage(size) for p in vigra.meshIter(size): result[p] = poly.contains((p[0], p[1])) and 1 or 0 return result
def _kochIteration(poly): result = Polygon() for i in range(len(poly) - 1): segment = poly[i + 1] - poly[i] smaller = segment / 3 left = Vector2(smaller[0] * _kochCos - smaller[1] * _kochSin, smaller[0] * _kochSin + smaller[1] * _kochCos) right = Vector2(smaller[0] * _kochCos + smaller[1] * _kochSin, -smaller[0] * _kochSin + smaller[1] * _kochCos) p1 = poly[i] + smaller p2 = p1 + left p3 = p2 + right result.append(poly[i]) result.append(p1) result.append(p2) result.append(p3) result.append(result[0]) return result
def shrinkPoly(poly, offset): assert poly[0] == poly[-1], "polygon should be closed" lines = [seg.line() for seg in polyLineSegments(poly)] for line in lines: line.dist -= offset i = 1 while i < len(lines): if lines[i - 1].isParallel(lines[i]): del lines[i] else: i += 1 if lines[-1].isParallel(lines[0]): del lines[-1] result = Polygon([ lines[i].intersect(lines[(i + 1) % len(lines)]) for i in range(len(lines)) ]) result.append(result[0]) return result
def outputMarkedShapes(delaunayMap, fe, skipInnerEdges=True, regionDepth=50, edgeDepth=49, capStyle=fig.CapStyle.Round, **kwargs): """IIRC, this assumes that delaunayMap contains only triangles. Contiguous thick parts of the alpha shape will be exported as single regions (as if removeInterior[Edges] had been used for alphaBetaMap or removeUnmarkedEdges, but without modifying `delaunayMap`). You may set `edgeDepth` to None to disable the output of edges. If lineWidth is not set explicitly, it then defaults to zero for faces.""" # output all cells only once: edgeOutput = [False] * delaunayMap.maxEdgeLabel() faceOutput = [False] * delaunayMap.maxFaceLabel() faceAttr = dict(kwargs) if edgeDepth is None and not "lineWidth" in kwargs: faceAttr["lineWidth"] = 0 faceAttr["depth"] = regionDepth faceAttr["fillStyle"] = fig.FillStyle.Solid faceAttr["capStyle"] = capStyle print "- exporting marked regions as filled polygons..." for triangle in delaunayMap.faceIter(skipInfinite=True): if not triangle.flag(ALPHA_MARK) or faceOutput[triangle.label()]: continue faceOutput[triangle.label()] = True contour = list(triangle.contour().phiOrbit()) i = 0 while i < len(contour): edgeOutput[contour[i].edgeLabel()] = skipInnerEdges neighbor = contour[i].rightFace() if neighbor.flag(ALPHA_MARK) and not faceOutput[neighbor.label()]: _ = contour[i].nextAlpha().nextPhi() contour.insert(i + 1, contour[i].clone().nextPhi()) faceOutput[neighbor.label()] = True else: i += 1 contour = Polygon([dart[0] for dart in contour]) contour.append(contour[0]) # close poly (for filling) i = 2 while i < len(contour): if contour[i] == contour[i - 2]: del contour[i - 2] del contour[i - 2] if i > 2: i -= 1 else: i += 1 #print " * %d points (area %s)" % (len(contour), contour.partialArea()) fe.addClippedPoly(contour, **faceAttr) if "fillColor" in kwargs: del kwargs["fillColor"] if edgeDepth != None: print "- exporting remaining marked edges (depth %d)..." % edgeDepth for edge in delaunayMap.edgeIter(): if not edge.flag(ALPHA_MARK) or edgeOutput[edge.label()]: continue dart = edge.dart() poly = Polygon(list(dart)) edgeOutput[edge.label()] = True drawing = True while drawing: drawing = False dart.nextAlpha() for next in dart.sigmaOrbit(): outputEdge = next.edge() if not outputEdge.flag(ALPHA_MARK) or edgeOutput[ outputEdge.label()]: continue drawing = True assert poly[-1] == next[0] if len(outputEdge) == 2: poly.append(next[1]) else: poly.extend(Polygon(list(next)[1:])) edgeOutput[outputEdge.label()] = True dart = next break # continue in the other direction: poly.reverse() dart = edge.dart().nextAlpha() drawing = True while drawing: drawing = False dart.nextAlpha() next = dart.clone() while next.nextSigma() != dart: outputEdge = next.edge() if not outputEdge.flag(ALPHA_MARK) or edgeOutput[ outputEdge.label()]: continue drawing = True assert poly[-1] == next[0] poly.append(next[1]) edgeOutput[outputEdge.label()] = True dart = next break fe.addClippedPoly(poly, depth=edgeDepth, capStyle=capStyle, **kwargs)
def clipPoly(polygon, clipRect, closeAtBorder=None): """clipPoly(polygon, clipRect) Clips away those parts of polygon which are not in clipRect. Returns a list of polygons (since the polygon may leave clipRect, enter again, leave, ...). Polygon segments crossing clipRect's borders are cut, such that the resulting polyons get new endpoints exactly on the border.""" result = [] # print "clipPoly(%s..%s)" % (clipRect.begin(), clipRect.end()) # print list(polygon) if closeAtBorder is None: closeAtBorder = (polygon[0] == polygon[-1]) x1, y1 = clipRect.begin() x2, y2 = clipRect.end() part = None startBorder = None parts = [] relPos = None for i, p in enumerate(polygon): prevRP = relPos relPos = 0 if p[0] < x1: relPos |= LEFT elif p[0] > x2: relPos |= RIGHT if p[1] < y1: relPos |= TOP elif p[1] > y2: relPos |= BOTTOM if relPos: # outside if not i: # incomplete first segment continue if prevRP & relPos: # complete segment outside continue # calculate leaving intersection diff = polygon[i - 1] - p l = -1.0 if relPos & LEFT: l = max(l, (x1 - p[0]) / diff[0]) endBorder = LEFT if relPos & RIGHT: l = max(l, (x2 - p[0]) / diff[0]) endBorder = RIGHT if relPos & TOP: nl = (y1 - p[1]) / diff[1] if nl > l: l = nl endBorder = TOP if relPos & BOTTOM: nl = (y2 - p[1]) / diff[1] if nl > l: l = nl endBorder = BOTTOM ip = p + l * diff if prevRP: # segment may cross cliprect, calc. start intersection pl = 2.0 if prevRP & LEFT: pl = min(pl, (x1 - p[0]) / diff[0]) startBorder = LEFT if prevRP & RIGHT: pl = min(pl, (x2 - p[0]) / diff[0]) startBorder = RIGHT if prevRP & TOP: npl = (y1 - p[1]) / diff[1] if npl < pl: pl = npl startBorder = TOP if prevRP & BOTTOM: npl = (y2 - p[1]) / diff[1] if npl < pl: pl = npl startBorder = BOTTOM if pl <= l: # we never crossed the clipRect continue pip = p + pl * diff part = Polygon([pip, ip]) else: part.append(ip) if part.length(): parts.append((startBorder, part, endBorder)) part = None continue if not part: part = Polygon() if i: # calculate entering intersection: diff = polygon[i - 1] - p l = 2.0 if prevRP & LEFT: l = min(l, (x1 - p[0]) / diff[0]) startBorder = LEFT if prevRP & RIGHT: l = min(l, (x2 - p[0]) / diff[0]) startBorder = RIGHT if prevRP & TOP: nl = (y1 - p[1]) / diff[1] if nl < l: l = nl startBorder = TOP if prevRP & BOTTOM: nl = (y2 - p[1]) / diff[1] if nl < l: l = nl startBorder = BOTTOM ip = p + l * diff part.append(ip) part.append(p) if part and part.length(): parts.append((startBorder, part, None)) if not parts: return [] if not polygon.closed(): return [p[1] for p in parts] # if polygon[0] (== polygon[-1]) is inside clipRect, we may # need to join the first and last part here: if parts[0][1][0] == parts[-1][1][-1]: assert parts[0][0] is None and parts[-1][-1] is None # polygon is entirely within clipRect: if len(parts) == 1: return [parts[0][1]] parts[-1][1].extend(parts[0][1]) parts[0] = (parts[-1][0], parts[-1][1], parts[0][2]) del parts[-1] if not closeAtBorder: return [p[1] for p in parts] # compose counterclockwise list of intersection points at clip border: sides = (([(-p[1][-1][0], p[1], True) for p in parts if p[2] == TOP] + [(-p[1][0][0], p[1], False) for p in parts if p[0] == TOP]), ([(p[1][-1][1], p[1], True) for p in parts if p[2] == LEFT] + [(p[1][0][1], p[1], False) for p in parts if p[0] == LEFT]), ([(p[1][-1][0], p[1], True) for p in parts if p[2] == BOTTOM] + [(p[1][0][0], p[1], False) for p in parts if p[0] == BOTTOM]), ([(-p[1][-1][1], p[1], True) for p in parts if p[2] == RIGHT] + [(-p[1][0][1], p[1], False) for p in parts if p[0] == RIGHT])) # counterclockwise list of corner positions: corners = (clipRect.begin(), clipRect.begin() + (0, clipRect.size()[1]), clipRect.end(), clipRect.begin() + (clipRect.size()[0], 0)) isCCW = polygon.partialArea() > 0 # bookkeeping about mergings (always use the most current polygon) merged = {} def mergeRoot(poly): while True: result = merged.get(poly, poly) if result is poly: break poly = result return result lastPoly = None prevPoly = None prevOutside = None for side, end in zip(sides, corners): for _, poly, outside in sorted(side): # assert outside != prevOutside; prevOutside = outside if outside == isCCW: prevPoly = poly else: if prevPoly == None: lastPoly = poly continue prevPoly = mergeRoot(prevPoly) if prevPoly == poly: poly.append(poly[0]) result.append(poly) else: prevPoly.extend(poly) merged[poly] = prevPoly prevPoly = None if prevPoly: mergeRoot(prevPoly).append(end) if lastPoly: lastPoly.append(lastPoly[0]) if lastPoly.length(): result.append(lastPoly) return result
p1 = l1 * line.dir() + l3 * line.norm p2 = l1 * line.dir() + l4 * line.norm p3 = l2 * line.dir() + l4 * line.norm p4 = l2 * line.dir() + l3 * line.norm return Polygon([p1, p2, p3, p4, p1]) # -------------------------------------------------------------------- if __name__ == "__main__": import fig f = fig.File("cliptest.fig") cr = geomap.BoundingBox((0, 0), (4500, 4500)) f.layer(1).remove() for o in f.findObjects(type=fig.PolylineBase, depth=42): p = Polygon(o.points) if o.closed(): p.append(p[0]) pp = clipPoly(p, cr) for p in pp: no = fig.Polygon(p, p[0] == p[-1]) no.depth = 1 no.lineWidth = 3 if no.closed(): no.fillStyle = fig.FillStyle.Solid no.fillColor = f.getColor(0.5) else: no.forwardArrow = fig.Arrow() f.append(no) f.save(fig2dev="eps")
def polygon(self): """path.polygon() -> Polygon Returns a polygon containing all points of this path.""" return Polygon(list(self.points()))