layout/writer: simplify and refactor using dataclasses for segments…

- `utils`:
  - add dataclasses `Region` (including contour, skew, conf,
    centers and area – the latter computed during init, but only once
    instead of over and over) and `TextRegion` (ditto, but with a list
    of lines) to encapsulate intermediate results more succinctly
  - drop `check_any_text_region_in_model_one_is_main_or_header`
- `run_single()`:
  - instantiate `Region` and `TextRegion` lists for separator lines,
    drop-caps, marginals, paragraphs, headings, images, tables instead
    of multiple sets of identifiers for these segment types, name them
    more consistently
  - apply contour "dilation" (really Shapely polygon instantiation,
    repair/join and buffering) only once in the writer after rescaling
    to original/final image size
- `filter_contours_inside_a_bigger_one()`: separate and rename
  - → `filter_small_regions()`: taking lists of `Region` (original and
    deskewed) for paragraphs and marginals, filtering small regions
    that are contained in large regions or marginals in-place
  - → `filter_small_textlines()`: taking a list of `TextRegion` and
    filtering lines that are contained in larger lines (of possibly
    other regions) in-place
- `filter_contours_without_textlines_inside()`: simplify and rename
  → `filter_textregions_without_textlines()`
- rename `separate_marginals_to_left_and_right_and_order_from_top_to_down`
  → `separate_marginals_and_order()` and simplify
- `get_slopes_and_deskew*`: work on list of `Region` in-place
  (modifying their `.skew` and `.lines`), extract line confidence, too
- writer:
  - expect `Region` and `TextRegion` instances for various segment types
    instead of wild list of identifiers
  - simplify (a lot)
  - merge `build_pagexml_no_full_layout` and build_pagexml_full_layout`
    → `build_pagexml()` (w/ empty lists as default)
  - also annotate textline confidence
  - enlarge/dilate/buffer coordinates differently for various segment types:
    6px for text and tables, 2px for images and seplines
- add more type hints
This commit is contained in:
Robert Sachunsky 2026-07-19 14:07:20 +02:00
parent 5e3fde31d9
commit beb5545899
5 changed files with 547 additions and 808 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,11 @@
from typing import Optional
import os.path
try:
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
except ImportError:
plt = mpatches = None
import numpy as np
import os.path
import cv2
from scipy.ndimage import gaussian_filter1d
@ -21,11 +22,11 @@ class EynollahPlotter:
self,
*,
dir_out,
dir_of_all,
dir_save_page,
dir_of_deskewed,
dir_of_layout,
dir_of_cropped_images,
dir_of_all: Optional[str] = None,
dir_save_page: Optional[str] = None,
dir_of_deskewed: Optional[str] = None,
dir_of_layout: Optional[str] = None,
dir_of_cropped_images: Optional[str] = None,
):
self.dir_out = dir_out
self.dir_of_all = dir_of_all
@ -172,6 +173,8 @@ class EynollahPlotter:
x, y, w, h = cv2.boundingRect(cont_ind)
box = [x, y, w, h]
image, _ = crop_image_inside_box(box, image_page)
if not image.size:
continue
image = resize_image(image,
int(image.shape[0] / scale_y),
int(image.shape[1] / scale_x))

View file

@ -2,7 +2,8 @@ from typing import Iterable, List, Tuple
from logging import getLogger
import time
import math
from itertools import islice
from dataclasses import dataclass, field
from itertools import islice, compress
try:
import matplotlib.pyplot as plt
@ -10,16 +11,18 @@ try:
except ImportError:
plt = None
import numpy as np
from shapely import geometry
from shapely import geometry, prepare, ops, plotting
import cv2
from scipy.signal import find_peaks
from scipy.ndimage import gaussian_filter1d
from skimage import morphology
from .is_nan import isNaN
from .contour import (contours_in_same_horizon,
from .contour import (contour2polygon,
contours_in_same_horizon,
find_center_of_contours,
find_new_features_of_contours,
polygon2contour,
return_contours_of_image,
return_parent_contours)
@ -39,6 +42,32 @@ def batched(iterable, n):
while batch := tuple(islice(iterator, n)):
yield batch
def itemgetter(seq):
# replacement for operator.itemgetter
# (which is ambiguous about its return type:
# single-item if 1 argument, tuple otherwise)
def fun(obj):
return list(obj[idx] for idx in seq)
return fun
@dataclass
class Region:
contour: np.ndarray
area: int = 0
cx: float = 0
cy: float = 0
skew: float = 0
conf: float = 0
def __post_init__(self):
self.area = cv2.contourArea(self.contour)
moments = cv2.moments(self.contour)
self.cx = moments['m10'] / (moments['m00'] or 1e-32)
self.cy = moments['m01'] / (moments['m00'] or 1e-32)
@dataclass
class TextRegion(Region):
lines: List[Region] = field(default_factory=list)
def return_multicol_separators_x_start_end(
regions_without_separators, peak_points, top, bot,
x_min_hor_some, x_max_hor_some, cy_hor_some, y_min_hor_some, y_max_hor_some):
@ -800,93 +829,16 @@ def fill_bb_of_drop_capitals(
return full_prediction == label_drop_fl_model
def check_any_text_region_in_model_one_is_main_or_header(
regions_model_1, regions_model_full,
contours_only_text_parent,
all_box_coord, all_found_textline_polygons,
slopes,
contours_only_text_parent_d_ordered, conf_contours):
cx_main, cy_main, x_min_main, x_max_main, y_min_main, y_max_main, y_corr_x_min_from_argmin = \
find_new_features_of_contours(contours_only_text_parent)
length_con=x_max_main-x_min_main
height_con=y_max_main-y_min_main
all_found_textline_polygons_main=[]
all_found_textline_polygons_head=[]
all_box_coord_main=[]
all_box_coord_head=[]
slopes_main=[]
slopes_head=[]
contours_only_text_parent_main=[]
contours_only_text_parent_head=[]
conf_contours_main=[]
conf_contours_head=[]
contours_only_text_parent_main_d=[]
contours_only_text_parent_head_d=[]
for ii, con in enumerate(contours_only_text_parent):
img = np.zeros(regions_model_1.shape[:2])
img = cv2.fillPoly(img, pts=[con], color=255)
all_pixels=((img == 255)*1).sum()
pixels_header=( ( (img == 255) & (regions_model_full[:,:,0]==2) )*1 ).sum()
pixels_main=all_pixels-pixels_header
if (pixels_header>=pixels_main) and ( (length_con[ii]/float(height_con[ii]) )>=1.3 ):
regions_model_1[:,:][(regions_model_1[:,:]==1) & (img == 255) ]=2
contours_only_text_parent_head.append(con)
if len(contours_only_text_parent_d_ordered):
contours_only_text_parent_head_d.append(contours_only_text_parent_d_ordered[ii])
all_box_coord_head.append(all_box_coord[ii])
slopes_head.append(slopes[ii])
all_found_textline_polygons_head.append(all_found_textline_polygons[ii])
conf_contours_head.append(None)
else:
regions_model_1[:,:][(regions_model_1[:,:]==1) & (img == 255) ]=1
contours_only_text_parent_main.append(con)
conf_contours_main.append(conf_contours[ii])
if len(contours_only_text_parent_d_ordered):
contours_only_text_parent_main_d.append(contours_only_text_parent_d_ordered[ii])
all_box_coord_main.append(all_box_coord[ii])
slopes_main.append(slopes[ii])
all_found_textline_polygons_main.append(all_found_textline_polygons[ii])
#print(all_pixels,pixels_main,pixels_header)
return (regions_model_1,
contours_only_text_parent_main,
contours_only_text_parent_head,
all_box_coord_main,
all_box_coord_head,
all_found_textline_polygons_main,
all_found_textline_polygons_head,
slopes_main,
slopes_head,
contours_only_text_parent_main_d,
contours_only_text_parent_head_d,
conf_contours_main,
conf_contours_head)
def split_textregion_main_vs_head(
regions_model_1,
regions_model_full,
polygons_of_textregions,
polygons_of_textregions_d,
all_found_textline_polygons,
slopes,
conf_textregions,
regions_model_1: np.ndarray,
regions_model_full: np.ndarray,
textregions: List[Region],
textregions_d: List[Region],
label_text=1,
label_head_full=2,
label_head_final=2,
label_main_final=1,
):
) -> Tuple[np.ndarray, List[Region], List[Region], List[Region], List[Region]]:
### to make it faster
h_o = regions_model_1.shape[0]
@ -900,19 +852,12 @@ def split_textregion_main_vs_head(
(regions_model_full.shape[1] // zoom,
regions_model_full.shape[0] // zoom),
interpolation=cv2.INTER_NEAREST)
contours_z = [contour // zoom
for contour in polygons_of_textregions]
contours_z = [textregion.contour // zoom
for textregion in textregions]
###
_, _, x_min_main, x_max_main, y_min_main, y_max_main, _ = \
find_new_features_of_contours(contours_z)
length_con=x_max_main-x_min_main
height_con=y_max_main-y_min_main
main = []
head = []
main = np.ones(len(contours_z), dtype=bool)
for ii, con in enumerate(contours_z):
width, height = cv2.boundingRect(con)[2:]
parent = np.zeros_like(regions_model_1)
parent = cv2.fillPoly(parent, pts=[con], color=1)
@ -920,16 +865,14 @@ def split_textregion_main_vs_head(
pixels_main = parent.sum() - pixels_head
if (( pixels_head >= 0.6 * pixels_main and
length_con[ii] >= 1.3 * height_con[ii] and
length_con[ii] <= 3 * height_con[ii] ) or
width >= 1.3 * height and
width <= 3 * height ) or
( pixels_head >= 0.3 * pixels_main and
length_con[ii] >= 3 * height_con[ii] )):
width >= 3 * height )):
head.append(ii)
main[ii] = False
label = label_head_final
else:
main.append(ii)
label = label_main_final
regions_model_1[(regions_model_1 == label_text) & (parent > 0)] = label
@ -941,35 +884,26 @@ def split_textregion_main_vs_head(
# interpolation=cv2.INTER_NEAREST)
###
def select(lis, indexes):
if not len(lis):
return []
return [lis[ind] for ind in indexes]
return (regions_model_1,
select(polygons_of_textregions, main),
select(polygons_of_textregions, head),
select(polygons_of_textregions_d, main),
select(polygons_of_textregions_d, head),
select(all_found_textline_polygons, main),
select(all_found_textline_polygons, head),
select(slopes, main),
select(slopes, head),
select(conf_textregions, main),
select(conf_textregions, head),
list(compress(textregions, main)),
list(compress(textregions, ~main)),
list(compress(textregions_d, main)),
list(compress(textregions_d, ~main)),
)
def small_textlines_to_parent_adherence2(textlines_con, textline_mask, num_col):
def small_textlines_to_parent_adherence2(
textregions: List[TextRegion],
area_factor: float,
num_col: int,
) -> None:
"""
for each region, split up textlines into small and large areas;
for each region, split up textlines into small and large;
keep only the ones with large area, but expanded (by merging
contours) by all intersecting lines with small area
"""
textlines_con_new = []
for region in textlines_con:
areas_cnt_text = np.array(list(map(cv2.contourArea, region)))
areas_cnt_text = areas_cnt_text / float(textline_mask.size)
indexes_textlines = np.arange(len(region))
for textregion in textregions:
areas = np.array([line.area for line in textregion.lines]) * area_factor
if num_col == 0:
min_area = 0.0004
@ -977,52 +911,43 @@ def small_textlines_to_parent_adherence2(textlines_con, textline_mask, num_col):
min_area = 0.0003
else:
min_area = 0.0001
indexes_textlines_small = indexes_textlines[areas_cnt_text < min_area]
indexes_textlines_large = indexes_textlines[areas_cnt_text >= min_area]
textlines_small = list(compress(textregion.lines, areas < min_area))
textlines_large = list(compress(textregion.lines, areas >= min_area))
textlines_small_poly = [contour2polygon(line.contour) for line in textlines_small]
textlines_large_poly = [contour2polygon(line.contour) for line in textlines_large]
textregion.lines = textlines_large
textlines_small = [region[i] for i in indexes_textlines_small]
textlines_large = [region[i] for i in indexes_textlines_large]
img_small = np.zeros_like(textline_mask)
img_small = cv2.fillPoly(img_small, pts=textlines_small, color=1)
img_large = np.zeros_like(textline_mask)
img_large = cv2.fillPoly(img_large, pts=textlines_large, color=1)
img_inter = img_small + img_large == 2
if np.any(img_inter):
indexes_textlines_inter = []
for contour_small in textlines_small:
intersections = []
for contour_large in textlines_large:
img0_small = np.zeros_like(textline_mask)
img0_small = cv2.fillPoly(img0_small, pts=[contour_small], color=1)
img0_large = np.zeros_like(textline_mask)
img0_large = cv2.fillPoly(img0_large, pts=[contour_large], color=1)
img0_inter = img0_small + img0_large == 2
intersections.append(np.count_nonzero(img0_inter))
if geometry.MultiPolygon(textlines_small_poly).intersects(
geometry.MultiPolygon(textlines_large_poly)):
# FIXME: also consider confidence (less certain lines replaced by better ones)...
textlines_large_prep = [prepare(poly) for poly in textlines_large_poly]
textlines_small_indexes_interlarge = []
for small_poly in textlines_small_poly:
intersections = [small_poly.intersection(prep).area
if prep.intersects(small_poly) else 0
for prep in textlines_large_prep]
idx_large = np.argmax(intersections)
if intersections[idx_large] <= 0:
if intersections[idx_large] == 0:
idx_large = -1
indexes_textlines_inter.append(idx_large)
indexes_textlines_inter = np.array(indexes_textlines_inter)
for idx_large in set(indexes_textlines_inter):
textlines_small_indexes_interlarge.append(idx_large)
textlines_small_indexes_interlarge = np.array(textlines_small_indexes_interlarge)
for idx_large in set(textlines_small_indexes_interlarge):
if idx_large < 0:
continue
img0_union = np.zeros_like(textline_mask)
img0_union = cv2.fillPoly(img0_union, pts=[textlines_large[idx_large]], color=255)
indexes_inter_small = np.flatnonzero(indexes_textlines_inter == idx_large)
for idx_small in indexes_inter_small:
img0_union = cv2.fillPoly(img0_union, pts=[textlines_small[idx_small]], color=255)
_, thresh = cv2.threshold(img0_union, 0, 255, 0)
contours_union, _ = cv2.findContours(thresh.astype(np.uint8),
cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
areas_union = np.array(list(map(cv2.contourArea, contours_union)))
contour_union = contours_union[np.argmax(areas_union)] #contours_union[0]
textlines_large[idx_large] = contour_union
textlines_con_new.append(textlines_large)
return textlines_con_new
large_poly = textlines_large_poly[idx_large]
indexes_small = np.flatnonzero(textlines_small_indexes_interlarge == idx_large)
for idx_small in indexes_small:
large_poly = large_poly.union(textlines_small_poly[idx_small])
if large_poly.geom_type == 'GeometryCollection':
# hetergeneous: filter lines and points
large_poly = ops.unary_union([geom for geom in large_poly.geoms
if geom.area > 0])
if large_poly.geom_type == 'MultiPolygon':
# disjoint: pick largest part
idx_large = np.argmax([geom.area for geom in large_poly.geoms])
large_poly = large_poly.geoms[idx_large]
# replace original
textregion.lines[idx_large].contour = polygon2contour(large_poly)
def order_of_regions(textline_mask, contours_main, contours_head, contours_drop, y_ref, x_ref):
"""
@ -1051,7 +976,7 @@ def order_of_regions(textline_mask, contours_main, contours_head, contours_drop,
total = len(contours_main) + len(contours_head) + len(contours_drop)
assert total == 0 or np.any(textline_mask)
# ax1 = plt.subplot(2, 1, 1, title="order_of_regions textline_mask")
# ax1 = plt.subplot(1, 2, 1, title="order_of_regions textline_mask")
# plt.imshow(textline_mask, aspect='auto')
y = textline_mask.sum(axis=1) # horizontal projection profile
y_padded = np.zeros(len(y) + 40)
@ -1061,15 +986,21 @@ def order_of_regions(textline_mask, contours_main, contours_head, contours_drop,
#z = gaussian_filter1d(y_padded, sigma_gaus)
#peaks, _ = find_peaks(z, height=0)
#peaks = peaks - 20
# ax2 = plt.subplot(2, 1, 2, title="smoothed horizontal projection", sharex=ax1)
# plt.plot(y)
# ax2 = plt.subplot(1, 2, 2, title="smoothed horizontal projection", sharey=ax1)
# ax2plot = plt.plot(y)[0]
# xdata = ax2plot.get_xdata()
# ydata = ax2plot.get_ydata()
# ax2plot.set_xdata(ydata)
# ax2plot.set_ydata(xdata)
# ax2.set_xlim(*ax1.get_xlim())
# ax2.set_ylim(*ax1.get_ylim())
zneg_rev = np.max(y_padded) - y_padded
zneg = np.zeros(len(zneg_rev) + 40)
zneg[20 : len(zneg_rev) + 20] = zneg_rev
zneg = gaussian_filter1d(zneg, sigma_gaus)
peaks_neg, _ = find_peaks(zneg, height=0)
# plt.vlines(peaks_neg - 40, 0, None, label="peaks")
# plt.hlines(peaks_neg - 40, 0, textline_mask.shape[1], label="peaks")
# plt.show()
peaks_neg = peaks_neg - 20 - 20

View file

@ -1,4 +1,4 @@
from typing import Sequence, Union
from typing import List, Sequence, Union, Tuple
from numbers import Number
from functools import partial
import itertools
@ -167,26 +167,34 @@ def dilate_textregion_contours(all_found_textregion_polygons):
[polygon2contour(contour2polygon(contour, dilate=6))
for contour in all_found_textregion_polygons])
def match_deskewed_contours(slope_deskew, contours_o, contours_d, shape_o, shape_d):
def match_deskewed_contours(
slope_deskew: float,
regions_o, #: List[Region], # (cyclic import)
regions_d, #: List[Region], # (cyclic import)
shape_o: Tuple[int, int],
shape_d: Tuple[int, int],
) -> np.ndarray:
from . import ensure_array
cntareas_o = np.array([cv2.contourArea(contour) for contour in contours_o])
cntareas_d = np.array([cv2.contourArea(contour) for contour in contours_d])
centers_o = np.array([[region.cx, region.cy] for region in regions_o]) # [N, 2]
centers_d = np.array([[region.cx, region.cy] for region in regions_d]) # [N, 2]
cntareas_o = np.array([region.area for region in regions_o])
cntareas_d = np.array([region.area for region in regions_d])
cntareas_o = cntareas_o / float(np.prod(shape_o[:2]))
cntareas_d = cntareas_d / float(np.prod(shape_d[:2]))
contours_o = ensure_array(contours_o)
contours_d = ensure_array(contours_d)
contours_o = ensure_array([region.contour for region in regions_o])
contours_d = ensure_array([region.contour for region in regions_d])
sort_o = np.argsort(cntareas_o)
sort_d = np.argsort(cntareas_d)
centers_o = centers_o[sort_o].T # [2, N]
centers_d = centers_d[sort_d].T # [2, N]
contours_o = contours_o[sort_o]
contours_d = contours_d[sort_d]
cntareas_o = cntareas_o[sort_o]
cntareas_d = cntareas_d[sort_d]
centers_o = np.stack(find_center_of_contours(contours_o)) # [2, N]
centers_d = np.stack(find_center_of_contours(contours_d)) # [2, N]
center0_o = centers_o[:, -1:] # [2, 1]
center0_d = centers_d[:, -1:] # [2, 1]

View file

@ -3,7 +3,7 @@
from pathlib import Path
import os.path
import logging
from typing import Optional
from typing import Optional, List, Tuple
import numpy as np
from shapely import affinity, clip_by_rect
@ -17,22 +17,30 @@ from ocrd_models.ocrd_page import (
ImageRegionType,
TableRegionType,
SeparatorRegionType,
PcGtsType,
to_xml
)
from .utils import Region, TextRegion
from .utils.xml import create_page_xml, xml_reading_order
from .utils.counter import EynollahIdCounter
from .utils.contour import contour2polygon, make_valid
class EynollahXmlWriter:
def __init__(self, *, dir_out, image_filename, image_width, image_height, curved_line, pcgts=None):
def __init__(
self, *,
dir_out: Optional[str],
image_filename: str,
image_width: int,
image_height: int,
pcgts: Optional[PcGtsType] = None,
):
self.logger = logging.getLogger('eynollah.writer')
self.counter = EynollahIdCounter()
self.dir_out = dir_out
self.image_filename = image_filename
self.output_filename = os.path.join(self.dir_out or "", self.image_filename_stem) + ".xml"
self.curved_line = curved_line
self.pcgts = pcgts
self.image_height = image_height
self.image_width = image_width
@ -40,132 +48,56 @@ class EynollahXmlWriter:
self.scale_y = 1.0
@property
def image_filename_stem(self):
def image_filename_stem(self) -> str:
return Path(Path(self.image_filename).name).stem
def calculate_points(self, contour, offset=None):
poly = contour2polygon(contour)
def calculate_points(
self, contour: np.ndarray,
offset: Optional[List[int]] = None,
dilate: int = 0,
) -> str:
poly = contour2polygon(contour, dilate=dilate)
if offset is not None:
poly = affinity.translate(poly, *offset)
poly = affinity.scale(poly, xfact=1 / self.scale_x, yfact=1 / self.scale_y, origin=(0, 0))
poly = make_valid(clip_by_rect(poly, 0, 0, self.image_width, self.image_height))
return points_from_polygon(poly.exterior.coords[:-1])
def serialize_lines_in_region(self, text_region, all_found_textline_polygons, region_idx, page_coord, slopes, counter, ocr_all_textlines_textregion):
for j, polygon_textline in enumerate(all_found_textline_polygons[region_idx]):
coords = CoordsType()
textline = TextLineType(id=counter.next_line_id, Coords=coords)
if ocr_all_textlines_textregion:
# FIXME: add OCR confidence
textline.set_TextEquiv([TextEquivType(Unicode=ocr_all_textlines_textregion[j])])
def serialize_lines_in_region(
self, text_region: TextRegionType,
offset: List[int],
counter: EynollahIdCounter,
lines: List[Region],
) -> None:
for line in lines:
textline = TextLineType(
id=counter.next_line_id,
Coords=CoordsType(points=self.calculate_points(line.contour, offset, 5),
conf=line.conf)
)
text_region.add_TextLine(textline)
text_region.set_orientation(-slopes[region_idx])
offset = [page_coord[2], page_coord[0]]
coords.set_points(self.calculate_points(polygon_textline, offset))
def write_pagexml(self, pcgts):
self.logger.info("output filename: '%s'", self.output_filename)
with open(self.output_filename, 'w') as f:
f.write(to_xml(pcgts))
def build_pagexml_no_full_layout(
def build_pagexml(
self,
*,
num_col,
found_polygons_text_region,
page_coord,
page_slope,
order_of_texts,
all_found_textline_polygons,
found_polygons_images,
found_polygons_tables,
found_polygons_marginals_left,
found_polygons_marginals_right,
all_found_textline_polygons_marginals_left,
all_found_textline_polygons_marginals_right,
slopes,
slopes_marginals_left,
slopes_marginals_right,
cont_page,
polygons_seplines,
ocr_all_textlines=None,
ocr_all_textlines_marginals_left=None,
ocr_all_textlines_marginals_right=None,
ocr_all_textlines_drop=None,
conf_textregions=None,
conf_marginals_left=None,
conf_marginals_right=None,
conf_images=None,
conf_tables=None,
):
return self.build_pagexml_full_layout(
num_col=num_col,
found_polygons_text_region=found_polygons_text_region,
found_polygons_text_region_h=[],
page_coord=page_coord,
page_slope=page_slope,
order_of_texts=order_of_texts,
all_found_textline_polygons=all_found_textline_polygons,
all_found_textline_polygons_h=[],
found_polygons_images=found_polygons_images,
found_polygons_tables=found_polygons_tables,
found_polygons_drop_capitals=[],
found_polygons_marginals_left=found_polygons_marginals_left,
found_polygons_marginals_right=found_polygons_marginals_right,
all_found_textline_polygons_marginals_left=all_found_textline_polygons_marginals_left,
all_found_textline_polygons_marginals_right=all_found_textline_polygons_marginals_right,
slopes=slopes,
slopes_h=[],
slopes_marginals_left=slopes_marginals_left,
slopes_marginals_right=slopes_marginals_right,
cont_page=cont_page,
polygons_seplines=polygons_seplines,
ocr_all_textlines=ocr_all_textlines,
ocr_all_textlines_marginals_left=ocr_all_textlines_marginals_left,
ocr_all_textlines_marginals_right=ocr_all_textlines_marginals_right,
conf_textregions=conf_textregions,
conf_marginals_left=conf_marginals_left,
conf_marginals_right=conf_marginals_right,
conf_images=conf_images,
conf_tables=conf_tables,
)
def build_pagexml_full_layout(
self,
*,
num_col,
found_polygons_text_region,
found_polygons_text_region_h,
page_coord,
page_slope,
order_of_texts,
all_found_textline_polygons,
all_found_textline_polygons_h,
found_polygons_images,
found_polygons_tables,
found_polygons_drop_capitals,
found_polygons_marginals_left,
found_polygons_marginals_right,
all_found_textline_polygons_marginals_left,
all_found_textline_polygons_marginals_right,
slopes,
slopes_h,
slopes_marginals_left,
slopes_marginals_right,
cont_page,
polygons_seplines,
ocr_all_textlines=None,
ocr_all_textlines_h=None,
ocr_all_textlines_marginals_left=None,
ocr_all_textlines_marginals_right=None,
ocr_all_textlines_drop=None,
conf_textregions=None,
conf_textregions_h=None,
conf_marginals_left=None,
conf_marginals_right=None,
conf_images=None,
conf_tables=None,
conf_drops=None,
num_col=1,
page_coord: Optional[List[int]] = None,
page_contour: Optional[np.ndarray] = None,
page_skew: float = 0.,
order_of_texts: List[int] = [],
textregions: List[TextRegion] = [],
textregions_h: List[TextRegion] = [],
images: List[Region] = [],
tables: List[Region] = [],
drop_caps: List[Region] = [],
marginals_left: List[TextRegion] = [],
marginals_right: List[TextRegion] = [],
seplines: List[Region] = [],
):
self.logger.debug('enter build_pagexml')
@ -175,118 +107,92 @@ class EynollahXmlWriter:
page = pcgts.get_Page()
pcgts.Metadata.Comments = "num_col %d" % num_col
page.set_custom('layout {num_col:%d;} ' % num_col)
page.set_orientation(-page_slope)
if len(cont_page):
page.set_Border(BorderType(Coords=CoordsType(points=self.calculate_points(cont_page[0]))))
page.set_orientation(-page_skew)
if page_contour is not None:
page.set_Border(BorderType(Coords=CoordsType(points=self.calculate_points(page_contour))))
if page_coord is None:
offset = [0, 0]
else:
offset = [page_coord[2], page_coord[0]]
counter = EynollahIdCounter()
if len(order_of_texts):
_counter_marginals = EynollahIdCounter(region_idx=len(order_of_texts))
id_of_marginalia_left = [_counter_marginals.next_region_id
for _ in found_polygons_marginals_left]
for _ in marginals_left]
id_of_marginalia_right = [_counter_marginals.next_region_id
for _ in found_polygons_marginals_right]
for _ in marginals_right]
xml_reading_order(page, order_of_texts, id_of_marginalia_left, id_of_marginalia_right)
for mm, region_contour in enumerate(found_polygons_text_region):
for region in textregions:
textregion = TextRegionType(
id=counter.next_region_id, type_='paragraph',
Coords=CoordsType(points=self.calculate_points(region_contour, offset))
Coords=CoordsType(points=self.calculate_points(region.contour, offset, 6),
conf=region.conf),
orientation=-region.skew
)
assert textregion.Coords
if conf_textregions:
textregion.Coords.set_conf(conf_textregions[mm])
self.serialize_lines_in_region(textregion, offset, counter, region.lines)
page.add_TextRegion(textregion)
if ocr_all_textlines:
ocr_textlines = ocr_all_textlines[mm]
else:
ocr_textlines = None
self.serialize_lines_in_region(textregion, all_found_textline_polygons, mm, page_coord,
slopes, counter, ocr_textlines)
self.logger.debug('len(found_polygons_text_region_h) %s', len(found_polygons_text_region_h))
for mm, region_contour in enumerate(found_polygons_text_region_h):
self.logger.debug('len(textregions_h) %s', len(textregions_h))
for region in textregions_h:
textregion = TextRegionType(
id=counter.next_region_id, type_='heading',
Coords=CoordsType(points=self.calculate_points(region_contour, offset))
Coords=CoordsType(points=self.calculate_points(region.contour, offset, 6),
conf=region.conf),
orientation=-region.skew
)
assert textregion.Coords
if conf_textregions_h:
textregion.Coords.set_conf(conf_textregions_h[mm])
self.serialize_lines_in_region(textregion, offset, counter, region.lines)
page.add_TextRegion(textregion)
if ocr_all_textlines_h:
ocr_textlines = ocr_all_textlines_h[mm]
else:
ocr_textlines = None
self.serialize_lines_in_region(textregion, all_found_textline_polygons_h, mm, page_coord,
slopes_h, counter, ocr_textlines)
for mm, region_contour in enumerate(found_polygons_drop_capitals):
dropcapital = TextRegionType(
for region in drop_caps:
textregion = TextRegionType(
id=counter.next_region_id, type_='drop-capital',
Coords=CoordsType(points=self.calculate_points(region_contour, offset))
Coords=CoordsType(points=self.calculate_points(region.contour, offset, 6),
conf=region.conf),
orientation=-region.skew
)
if conf_drops:
dropcapital.Coords.set_conf(conf_drops[mm])
page.add_TextRegion(dropcapital)
slopes_drop = [0]
if ocr_all_textlines_drop:
ocr_textlines = ocr_all_textlines_drop[mm]
else:
ocr_textlines = None
self.serialize_lines_in_region(dropcapital, [[found_polygons_drop_capitals[mm]]], 0, page_coord,
slopes_drop, counter, ocr_textlines)
self.serialize_lines_in_region(textregion, offset, counter, [region])
page.add_TextRegion(textregion)
for mm, region_contour in enumerate(found_polygons_marginals_left):
marginal = TextRegionType(
for region in marginals_left:
textregion = TextRegionType(
id=counter.next_region_id, type_='marginalia',
Coords=CoordsType(points=self.calculate_points(region_contour, offset))
Coords=CoordsType(points=self.calculate_points(region.contour, offset, 6),
conf=region.conf),
orientation=-region.skew
)
if conf_marginals_left:
marginal.Coords.set_conf(conf_marginals_left[mm])
page.add_TextRegion(marginal)
if ocr_all_textlines_marginals_left:
ocr_textlines = ocr_all_textlines_marginals_left[mm]
else:
ocr_textlines = None
self.serialize_lines_in_region(marginal, all_found_textline_polygons_marginals_left, mm, page_coord,
slopes_marginals_left, counter, ocr_textlines)
self.serialize_lines_in_region(textregion, offset, counter, region.lines)
page.add_TextRegion(textregion)
for mm, region_contour in enumerate(found_polygons_marginals_right):
marginal = TextRegionType(
for region in marginals_right:
textregion = TextRegionType(
id=counter.next_region_id, type_='marginalia',
Coords=CoordsType(points=self.calculate_points(region_contour, offset))
Coords=CoordsType(points=self.calculate_points(region.contour, offset, 6),
conf=region.conf),
orientation=-region.skew
)
if conf_marginals_right:
marginal.Coords.set_conf(conf_marginals_right[mm])
page.add_TextRegion(marginal)
if ocr_all_textlines_marginals_right:
ocr_textlines = ocr_all_textlines_marginals_right[mm]
else:
ocr_textlines = None
self.serialize_lines_in_region(marginal, all_found_textline_polygons_marginals_right, mm, page_coord,
slopes_marginals_right, counter, ocr_textlines)
self.serialize_lines_in_region(textregion, offset, counter, region.lines)
page.add_TextRegion(textregion)
for mm, region_contour in enumerate(found_polygons_images):
for region in images:
image = ImageRegionType(
id=counter.next_region_id,
Coords=CoordsType(points=self.calculate_points(region_contour, offset)))
if conf_images:
image.Coords.set_conf(conf_images[mm])
Coords=CoordsType(points=self.calculate_points(region.contour, offset, 2),
conf=region.conf))
page.add_ImageRegion(image)
for region_contour in polygons_seplines:
for region in seplines:
page.add_SeparatorRegion(
SeparatorRegionType(id=counter.next_region_id,
Coords=CoordsType(points=self.calculate_points(region_contour, offset))))
SeparatorRegionType(
id=counter.next_region_id,
Coords=CoordsType(points=self.calculate_points(region.contour, offset, 2),
conf=region.conf)))
for mm, region_contour in enumerate(found_polygons_tables):
for region in tables:
table = TableRegionType(
id=counter.next_region_id,
Coords=CoordsType(points=self.calculate_points(region_contour, offset)))
if conf_tables:
table.Coords.set_conf(conf_tables[mm])
Coords=CoordsType(points=self.calculate_points(region.contour, offset, 6),
conf=region.conf))
page.add_TableRegion(table)
return pcgts