|
5 | 5 | from skimage import draw
|
6 | 6 | from skimage.morphology import convex_hull_image
|
7 | 7 | import cv2
|
8 |
| -from shapely.geometry import Polygon |
| 8 | +from shapely.geometry import Polygon, asPolygon |
9 | 9 | from shapely.prepared import prep
|
| 10 | +from shapely.ops import unary_union |
10 | 11 |
|
11 | 12 | from ocrd_modelfactory import page_from_file
|
12 | 13 | from ocrd_models.ocrd_page import (
|
@@ -666,16 +667,47 @@ def polygon_for_parent(polygon, parent):
|
666 | 667 | [parent.get_imageWidth(),0]])
|
667 | 668 | else:
|
668 | 669 | parentp = Polygon(polygon_from_points(parent.get_Coords().points))
|
| 670 | + # check if clipping is necessary |
669 | 671 | if childp.within(parentp):
|
670 | 672 | return polygon
|
| 673 | + # ensure input coords have valid paths (without self-intersection) |
| 674 | + # (this can happen when shapes valid in floating point are rounded) |
| 675 | + childp = make_valid(childp) |
| 676 | + parentp = make_valid(parentp) |
| 677 | + # clip to parent |
671 | 678 | interp = childp.intersection(parentp)
|
672 |
| - if interp.is_empty: |
| 679 | + # post-process |
| 680 | + if interp.is_empty or interp.area == 0.0: |
673 | 681 | # FIXME: we need a better strategy against this
|
674 | 682 | raise Exception("intersection of would-be segment with parent is empty")
|
| 683 | + if interp.type == 'GeometryCollection': |
| 684 | + # heterogeneous result: filter zero-area shapes (LineString, Point) |
| 685 | + interp = unary_union([geom for geom in interp.geoms if geom.area > 0]) |
675 | 686 | if interp.type == 'MultiPolygon':
|
| 687 | + # homogeneous result: construct convex hull to connect |
| 688 | + # FIXME: construct concave hull / alpha shape |
676 | 689 | interp = interp.convex_hull
|
| 690 | + if interp.minimum_clearance < 1.0: |
| 691 | + # follow-up calculations will necessarily be integer; |
| 692 | + # so anticipate rounding here and then ensure validity |
| 693 | + interp = asPolygon(np.round(interp.exterior.coords)) |
| 694 | + interp = make_valid(interp) |
677 | 695 | return interp.exterior.coords[:-1] # keep open
|
678 | 696 |
|
| 697 | +def make_valid(polygon): |
| 698 | + for split in range(1, len(polygon.exterior.coords)-1): |
| 699 | + if polygon.is_valid or polygon.simplify(polygon.area).is_valid: |
| 700 | + break |
| 701 | + # simplification may not be possible (at all) due to ordering |
| 702 | + # in that case, try another starting point |
| 703 | + polygon = Polygon(polygon.exterior.coords[-split:]+polygon.exterior.coords[:-split]) |
| 704 | + for tolerance in range(1, int(polygon.area)): |
| 705 | + if polygon.is_valid: |
| 706 | + break |
| 707 | + # simplification may require a larger tolerance |
| 708 | + polygon = polygon.simplify(tolerance) |
| 709 | + return polygon |
| 710 | + |
679 | 711 | def page_get_reading_order(ro, rogroup):
|
680 | 712 | """Add all elements from the given reading order group to the given dictionary.
|
681 | 713 |
|
|
0 commit comments