Skip to content

Commit 8841abc

Browse files
author
Robert Sachunsky
committed
update to shapely 1.8
1 parent b856f5b commit 8841abc

File tree

4 files changed

+168
-47
lines changed

4 files changed

+168
-47
lines changed

ocrd_cis/ocropy/common.py

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from scipy.ndimage import measurements, filters, interpolation, morphology
88
from scipy import stats, signal
99
#from skimage.morphology import convex_hull_image
10+
from skimage.measure import find_contours, approximate_polygon
1011
from PIL import Image
1112

1213
from . import ocrolib
@@ -996,7 +997,9 @@ def h_compatible(obj1, obj2, center1, center2):
996997
# (which must be split anyway)
997998
# - with tighter polygonal spread around foreground
998999
# - with spread of line labels against separator labels
1000+
# - with centerline extraction
9991001
# - return bg line and sep labels intead of just fg line labels
1002+
# - return centerline coords, too
10001003
@checks(ABINARY2)
10011004
def compute_segmentation(binary,
10021005
zoom=1.0,
@@ -1046,6 +1049,7 @@ def compute_segmentation(binary,
10461049
foreground may remain unlabelled for
10471050
separators and other non-text like small
10481051
noise, or large drop-capitals / images),
1052+
- list of Numpy arrays of centerline coordinates [x, y points in lr order]
10491053
- Numpy array of horizontal foreground lines mask,
10501054
- Numpy array of vertical foreground lines mask,
10511055
- Numpy array of large/non-text foreground component mask,
@@ -1141,10 +1145,66 @@ def compute_segmentation(binary,
11411145
LOG.debug('sorting labels by reading order')
11421146
llabels = morph.reading_order(llabels,rl,bt)[llabels]
11431147
DSAVE('llabels_ordered', llabels)
1144-
1148+
11451149
#segmentation = llabels*binary
11461150
#return segmentation
1147-
return llabels, hlines, vlines, images, colseps, scale
1151+
clines = compute_centerlines(bottom, top, llabels, scale)
1152+
return llabels, clines, hlines, vlines, images, colseps, scale
1153+
1154+
@checks(AFLOAT2,AFLOAT2,SEGMENTATION,NUMBER)
1155+
def compute_centerlines(bottom, top, lines, scale):
1156+
"""Get the coordinates of center lines running between each bottom and top gradient peak."""
1157+
# smooth bottom+top maps horizontally for centerline estimation
1158+
bottom = filters.gaussian_filter(bottom, (scale*0.25,scale), mode='constant')
1159+
top = filters.gaussian_filter(top, (scale*0.25,scale), mode='constant')
1160+
# idea: center is where bottom and top gradient meet in the middle
1161+
# (but between top and bottom, not between bottom and top)
1162+
# - calculation via numpy == or isclose is too fragile numerically:
1163+
#clines = np.isclose(top, bottom, rtol=0.1) & (np.diff(top - bottom, axis=0, append=0) < 0)
1164+
#DSAVE('clines', [clines, bottom, top], enabled=True)
1165+
# - calculation via skimage.measure contours is reliable, but produces polygon segments
1166+
gradients = bottom - top
1167+
#seeds = np.diff(bottom - top, axis=0, append=0) > 0
1168+
seeds = lines > 0
1169+
contours = find_contours(gradients, 0, mask=seeds)
1170+
img = np.zeros_like(bottom, np.int)
1171+
#img = gradients
1172+
from skimage import draw
1173+
clines = []
1174+
for j, contour in enumerate(contours):
1175+
# map y,x to x,y points
1176+
contour = contour[:,::-1]
1177+
#contour = approximate_polygon(contour, 1.0)
1178+
if len(contour) <= 3:
1179+
# too short already
1180+
clines.append(contour[np.argsort(contour[:,0])])
1181+
continue
1182+
img[draw.polygon_perimeter(contour[:,1], contour[:,0], img.shape)] = j
1183+
# ensure the segment runs from left-most to right-most point once,
1184+
# find the middle between both paths (back and forth)
1185+
left = contour[:,0].argmin()
1186+
contour = np.concatenate((contour[left:], contour[:left]))
1187+
right = contour[:,0].argmax()
1188+
if right >= len(contour)-2 or right <= 1:
1189+
# no plateau - no back path
1190+
clines.append(contour[np.argsort(contour[:,0])])
1191+
continue
1192+
contour1 = contour[0:right]
1193+
contour2 = contour[right:]
1194+
interp1 = np.interp(contour2[:,0], contour1[:,0], contour1[:,1])
1195+
interp2 = np.interp(contour1[:,0], contour2[:,0], contour2[:,1])
1196+
order = np.argsort(contour[:,0])
1197+
interpolated = []
1198+
for i in order:
1199+
if i >= right:
1200+
interpolated.append([contour[i,0], 0.5 * (contour2[i-right,1] + interp1[i-right])])
1201+
else:
1202+
interpolated.append([contour[i,0], 0.5 * (contour1[i,1] + interp2[i])])
1203+
interpolated = np.array(interpolated)
1204+
img[draw.polygon_perimeter(interpolated[:,1], interpolated[:,0], img.shape)] = j+0.5
1205+
clines.append(interpolated)
1206+
DSAVE("centerline contours", img, enabled=True)
1207+
return clines
11481208

11491209
# from ocropus-gpageseg, but
11501210
# - on both foreground and background,

ocrd_cis/ocropy/resegment.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from itertools import chain
55
import numpy as np
66
from skimage import draw
7-
from shapely.geometry import Polygon, asPolygon, LineString
7+
from shapely.geometry import Polygon, LineString
88
from shapely.prepared import prep
99
from shapely.ops import unary_union
1010
import alphashape
@@ -209,7 +209,7 @@ def _process_segment(self, parent, parent_image, parent_coords, page_id, zoom, l
209209
segment_polygon = coordinates_of_segment(segment, parent_image, parent_coords)
210210
segment_polygon = make_valid(Polygon(segment_polygon)).buffer(margin)
211211
line_polygons.append(prep(segment_polygon))
212-
segment_polygon = np.array(segment_polygon.exterior, np.int)[:-1]
212+
segment_polygon = np.array(segment_polygon.exterior.coords, np.int)[:-1]
213213
# draw.polygon: If any segment_polygon lies outside of parent
214214
# (causing negative/above-max indices), either fully or partially,
215215
# then this will silently ignore them. The caller does not need
@@ -224,7 +224,7 @@ def _process_segment(self, parent, parent_image, parent_coords, page_id, zoom, l
224224
segment.id, page_id if fullpage else parent.id)
225225
segment_polygon = coordinates_of_segment(segment, parent_image, parent_coords)
226226
segment_polygon = make_valid(Polygon(segment_polygon)).buffer(margin)
227-
segment_polygon = np.array(segment_polygon.exterior, np.int)[:-1]
227+
segment_polygon = np.array(segment_polygon.exterior.coords, np.int)[:-1]
228228
ignore_bin[draw.polygon(segment_polygon[:, 1],
229229
segment_polygon[:, 0],
230230
parent_bin.shape)] = False
@@ -271,7 +271,7 @@ def _process_segment(self, parent, parent_image, parent_coords, page_id, zoom, l
271271
# left-hand side if left-to-right, and vice versa
272272
scale * (-1) ** line_ltr, single_sided=True)],
273273
loc=line.id, scale=scale))
274-
line_polygon = np.array(line_polygon.exterior, np.int)[:-1]
274+
line_polygon = np.array(line_polygon.exterior.coords, np.int)[:-1]
275275
line_y, line_x = draw.polygon(line_polygon[:, 1],
276276
line_polygon[:, 0],
277277
parent_bin.shape)
@@ -280,12 +280,12 @@ def _process_segment(self, parent, parent_image, parent_coords, page_id, zoom, l
280280
scale=scale, loc=parent.id, threshold=threshold)
281281
return
282282
try:
283-
new_line_labels, _, _, _, _, scale = compute_segmentation(
283+
new_line_labels, _, _, _, _, _, scale = compute_segmentation(
284284
parent_bin, seps=ignore_bin, zoom=zoom, fullpage=fullpage,
285285
maxseps=0, maxcolseps=len(ignore), maximages=0)
286286
except Exception as err:
287-
LOG.warning('Cannot line-segment %s "%s": %s',
288-
tag, page_id if fullpage else parent.id, err)
287+
LOG.error('Cannot line-segment %s "%s": %s',
288+
tag, page_id if fullpage else parent.id, err)
289289
return
290290
LOG.info("Found %d new line labels for %d existing lines on %s '%s'",
291291
new_line_labels.max(), len(lines), tag, parent.id)
@@ -476,7 +476,7 @@ def diff_polygons(poly1, poly2):
476476
if poly.type == 'MultiPolygon':
477477
poly = poly.convex_hull
478478
if poly.minimum_clearance < 1.0:
479-
poly = asPolygon(np.round(poly.exterior.coords))
479+
poly = Polygon(np.round(poly.exterior.coords))
480480
poly = make_valid(poly)
481481
return poly
482482

@@ -517,7 +517,7 @@ def join_polygons(polygons, loc='', scale=20):
517517
if jointp.minimum_clearance < 1.0:
518518
# follow-up calculations will necessarily be integer;
519519
# so anticipate rounding here and then ensure validity
520-
jointp = asPolygon(np.round(jointp.exterior.coords))
520+
jointp = Polygon(np.round(jointp.exterior.coords))
521521
jointp = make_valid(jointp)
522522
return jointp
523523

0 commit comments

Comments
 (0)