Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

coordinates_for_segment: add optional parent_element… #492

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 59 additions & 6 deletions ocrd_utils/ocrd_utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* ``coordinates_of_segment``, ``coordinates_for_segment``

These functions convert polygon outlines for PAGE elements on all hierarchy
levels below page (i.e. region, line, word, glyph) between relative coordinates
levels (i.e. page, region, line, word, glyph) between relative coordinates
w.r.t. a corresponding image and absolute coordinates w.r.t. the top-level image.
This includes rotation and offset correction, based on affine transformations.
(Used by ``Workspace`` methods ``image_from_page`` and ``image_from_segment``)
Expand Down Expand Up @@ -96,6 +96,8 @@
'points_from_x0y0x1y1',
'points_from_xywh',
'points_from_y0x0y1x1',
'points_of_segment',
'polygon_for_parent',
'polygon_from_bbox',
'polygon_from_points',
'polygon_from_x0y0x1y1',
Expand Down Expand Up @@ -131,6 +133,7 @@

import numpy as np
from PIL import Image, ImageStat, ImageDraw, ImageChops
from shapely.geometry import Polygon

from .logging import * # pylint: disable=wildcard-import
from .constants import * # pylint: disable=wildcard-import
Expand Down Expand Up @@ -184,14 +187,16 @@ def xywh_from_polygon(polygon):
"""Construct a numeric dict representing a bounding box from polygon coordinates in numeric list representation."""
return xywh_from_bbox(*bbox_from_polygon(polygon))

def coordinates_for_segment(polygon, parent_image, parent_coords):
def coordinates_for_segment(polygon, parent_image, parent_coords, parent_element=None):
"""Convert relative coordinates to absolute.

Given...

- ``polygon``, a numpy array of points relative to
- ``parent_image``, a PIL.Image (not used), along with
- ``parent_coords``, its corresponding affine transformation,
- ``parent_element`` (optional), the direct parent element in
PAGE-XML's segment hierarchy,

...calculate the absolute coordinates within the page.

Expand All @@ -208,24 +213,32 @@ def coordinates_for_segment(polygon, parent_image, parent_coords):
opposite direction, rotated purely, and translated back;
the latter involves an additional offset from the increase
in canvas size necessary to accommodate all points).

If ``parent_element`` is given, then also see to it that the
resulting polygon is fully contained within the polygon of its parent
(via intersection). Note that this need not be the same element
that was used as "parent" for the image (the latter can be multiple
levels up).
kba marked this conversation as resolved.
Show resolved Hide resolved

Return the rounded numpy array of the resulting polygon.
"""
polygon = np.array(polygon, dtype=np.float32) # avoid implicit type cast problems
# apply inverse of affine transform:
inv_transform = np.linalg.inv(parent_coords['transform'])
polygon = transform_coordinates(polygon, inv_transform)
if parent_element:
polygon = polygon_for_parent(polygon, parent_element)
return np.round(polygon).astype(np.int32)

def coordinates_of_segment(segment, parent_image, parent_coords):
"""Extract the coordinates of a PAGE segment element relative to its parent.

Given...

- ``segment``, a PAGE segment object in absolute coordinates
(i.e. RegionType / TextLineType / WordType / GlyphType), and
- ``segment``, a PAGE segment object with absolute coordinates
(i.e. PageType / RegionType / TextLineType / WordType / GlyphType), and
- ``parent_image``, the PIL.Image of its corresponding parent object
(i.e. PageType / RegionType / TextLineType / WordType), (not used),
(i.e. PageType / RegionType / TextLineType / WordType), (currently not used),
along with
- ``parent_coords``, its corresponding affine transformation,

Expand All @@ -247,7 +260,7 @@ def coordinates_of_segment(segment, parent_image, parent_coords):
Return the rounded numpy array of the resulting polygon.
"""
# get polygon:
polygon = np.array(polygon_from_points(segment.get_Coords().points))
polygon = np.array(polygon_from_points(points_of_segment(segment)))
# apply affine transform:
polygon = transform_coordinates(polygon, parent_coords['transform'])
return np.round(polygon).astype(np.int32)
Expand Down Expand Up @@ -547,6 +560,46 @@ def points_from_x0y0x1y1(xyxy):
x0, y1
)

def points_of_segment(segment):
"""Extract the polygon coordinates of a PAGE segment element in page representation

Given ``segment``, a PAGE segment object with absolute coordinates
(i.e. PageType / RegionType / TextLineType / WordType / GlyphType),
return the Coords/@points of that object.

(For PageType, either get its Border (if any), or construct a bounding box
polygon from its @imageHeight and @imageWidth.)
"""
if segment.__class__.__name__ == 'PageType':
if segment.get_Border():
return segment.get_Border().get_Coords().points
return "%i,%i %i,%i %i,%i %i,%i" % (
0,0,
0,segment.get_imageHeight(),
segment.get_imageWidth(),segment.get_imageHeight(),
segment.get_imageWidth(),0)
return segment.get_Coords().points

def polygon_for_parent(polygon, parent):
"""Shrink the given polygon to within the outline of the parent.

Given a numpy array ``polygon`` of absolute coordinates, and
a PAGE segment object ``parent``, return the intersection of
both if ``polygon`` is not fully contained within ``parent``,
or ``polygon`` unchanged otherwise.
"""
childp = Polygon(polygon)
parentp = Polygon(polygon_from_points(points_of_segment(parent)))
if childp.within(parentp):
return polygon
interp = childp.intersection(parentp)
if interp.is_empty:
# FIXME: we need a better strategy against this
raise Exception("intersection of would-be segment with parent is empty")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to be robust against such blatant errors?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, probably just show a warning. But we have no context for localizing the message at that point...

if interp.type == 'MultiPolygon':
interp = interp.convex_hull
return interp.exterior.coords[:-1] # keep open

def polygon_from_bbox(minx, miny, maxx, maxy):
"""Construct polygon coordinates in numeric list representation from a numeric list representing a bounding box."""
return [[minx, miny], [maxx, miny], [maxx, maxy], [minx, maxy]]
Expand Down
1 change: 1 addition & 0 deletions ocrd_utils/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
Pillow >= 6.2.0
numpy >= 1.17.0
shapely
kba marked this conversation as resolved.
Show resolved Hide resolved