mirror of
https://github.com/qurator-spk/eynollah.git
synced 2026-07-26 05:29:16 +02:00
Merge b8b427cf69 into 88ca39dedb
This commit is contained in:
commit
879e479054
19 changed files with 1148 additions and 1811 deletions
2
Makefile
2
Makefile
|
|
@ -83,7 +83,7 @@ smoke-test: tests/resources/2files/kant_aufklaerung_1784_0020.tif
|
|||
eynollah -m $(CURDIR) layout -di $(<D) -o $(TMPDIR)
|
||||
test -s $(TMPDIR)/euler_rechenkunst01_1738_0025.xml
|
||||
# mbreorder, directory mode (overwrite):
|
||||
eynollah -m $(CURDIR) machine-based-reading-order -di $(<D) -o $(TMPDIR)
|
||||
eynollah -m $(CURDIR) reorder -mb -di $(<D) -o $(TMPDIR)
|
||||
fgrep -q http://schema.primaresearch.org/PAGE/gts/pagecontent/2019-07-15 $(TMPDIR)/$(basename $(<F)).xml
|
||||
fgrep -c -e RegionRefIndexed $(TMPDIR)/$(basename $(<F)).xml
|
||||
# binarize:
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from .cli_readingorder import readingorder_cli
|
|||
main.add_command(binarize_cli, 'binarization')
|
||||
main.add_command(enhance_cli, 'enhancement')
|
||||
main.add_command(layout_cli, 'layout')
|
||||
main.add_command(readingorder_cli, 'machine-based-reading-order')
|
||||
main.add_command(readingorder_cli, 'reorder')
|
||||
main.add_command(models_cli, 'models')
|
||||
main.add_command(ocr_cli, 'ocr')
|
||||
main.add_command(extract_images_cli, 'extract-images')
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ def extract_images_cli(
|
|||
ignore_page_extraction,
|
||||
):
|
||||
"""
|
||||
Detect Layout (with optional image enhancement and reading order detection)
|
||||
Detect image regions only
|
||||
"""
|
||||
assert enable_plotting or not save_images, "Plotting with -si also requires -ep"
|
||||
assert not enable_plotting or save_images, "Plotting with -ep also requires -si"
|
||||
|
|
|
|||
|
|
@ -3,6 +3,12 @@ import click
|
|||
@click.command(context_settings=dict(
|
||||
help_option_names=['-h', '--help'],
|
||||
show_default=True))
|
||||
@click.option(
|
||||
"--model_based",
|
||||
"-mb",
|
||||
help="use machine-learning model instead of heuristic rules",
|
||||
is_flag=True,
|
||||
)
|
||||
@click.option(
|
||||
"--input",
|
||||
"-i",
|
||||
|
|
@ -15,6 +21,12 @@ import click
|
|||
help="directory of PAGE-XML input files (instead of --input)",
|
||||
type=click.Path(exists=True, file_okay=False),
|
||||
)
|
||||
@click.option(
|
||||
"--dir_imgs",
|
||||
"-dim",
|
||||
help="directory of image input files (in addition to --dir_in or --input; filename stems must match the XML files, with image file format suffixes). Not needed for --model_based.",
|
||||
type=click.Path(exists=True, file_okay=False),
|
||||
)
|
||||
@click.option(
|
||||
"--out",
|
||||
"-o",
|
||||
|
|
@ -22,17 +34,27 @@ import click
|
|||
type=click.Path(exists=True, file_okay=False),
|
||||
required=True,
|
||||
)
|
||||
@click.option(
|
||||
"--overwrite",
|
||||
"-O",
|
||||
help="overwrite (instead of skipping) if output xml exists",
|
||||
is_flag=True,
|
||||
)
|
||||
@click.pass_context
|
||||
def readingorder_cli(ctx, input, dir_in, out):
|
||||
def readingorder_cli(ctx, model_based, input, dir_in, dir_imgs, out, overwrite):
|
||||
"""
|
||||
Generate ReadingOrder with a ML model
|
||||
Generate ReadingOrder for existing segmentation from ML model or from heuristic rules
|
||||
"""
|
||||
from ..mb_ro_on_layout import Reorder
|
||||
from ..reorder import Reorder
|
||||
assert bool(input) != bool(dir_in), "Either -i (single input) or -di (directory) must be provided, but not both."
|
||||
assert bool(model_based) or bool(dir_imgs), "For heuristic reading order, -dim must be provided, too."
|
||||
orderer = Reorder(model_zoo=ctx.obj.model_zoo,
|
||||
device=ctx.obj.device)
|
||||
orderer.run(xml_filename=input,
|
||||
device=ctx.obj.device,
|
||||
model_based=model_based)
|
||||
orderer.run(overwrite=overwrite,
|
||||
xml_filename=input,
|
||||
dir_in=dir_in,
|
||||
dir_imgs=dir_imgs,
|
||||
dir_out=out,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,9 +16,11 @@ from eynollah.utils.contour import filter_contours_area_of_image, return_contour
|
|||
from eynollah.utils.resize import resize_image
|
||||
|
||||
from .model_zoo.model_zoo import EynollahModelZoo
|
||||
from .writer import EynollahXmlWriter
|
||||
from .eynollah import Eynollah
|
||||
from .utils import box2rect, is_image_filename
|
||||
from .plot import EynollahPlotter
|
||||
from .utils import Region
|
||||
|
||||
class EynollahImageExtractor(Eynollah):
|
||||
|
||||
|
|
@ -67,22 +69,29 @@ class EynollahImageExtractor(Eynollah):
|
|||
self.setup_models()
|
||||
self.logger.info(f"Model initialization complete ({time.time() - t_start:.1f}s)")
|
||||
|
||||
def setup_models(self):
|
||||
def setup_models(self, device=''):
|
||||
|
||||
loadable = [
|
||||
"col_classifier",
|
||||
"binarization",
|
||||
"page",
|
||||
"extract_images",
|
||||
]
|
||||
self.model_zoo.load_models(*loadable)
|
||||
if self.input_binary:
|
||||
loadable.append("binarization")
|
||||
self.model_zoo.load_models(*loadable, device=device)
|
||||
|
||||
def get_regions_light_v_extract_only_images(self,img, num_col_classifier):
|
||||
def get_early_layout(
|
||||
self,
|
||||
img,
|
||||
num_col_classifier,
|
||||
label_text=1,
|
||||
label_imgs=2,
|
||||
label_seps=3,
|
||||
):
|
||||
self.logger.debug("enter get_regions_extract_images_only")
|
||||
erosion_hurts = False
|
||||
img_org = np.copy(img)
|
||||
img_height_h = img_org.shape[0]
|
||||
img_width_h = img_org.shape[1]
|
||||
# already cropped
|
||||
img_height_h, img_width_h = img.shape[:2]
|
||||
|
||||
if num_col_classifier == 1:
|
||||
img_w_new = 700
|
||||
|
|
@ -98,60 +107,47 @@ class EynollahImageExtractor(Eynollah):
|
|||
img_w_new = 2500
|
||||
else:
|
||||
raise ValueError("num_col_classifier must be in range 1..6")
|
||||
img_h_new = int(img.shape[0] / float(img.shape[1]) * img_w_new)
|
||||
img_resized = resize_image(img,img_h_new, img_w_new )
|
||||
img_h_new = img_w_new * img_height_h // img_width_h
|
||||
img_resized = resize_image(img, img_h_new, img_w_new)
|
||||
|
||||
prediction_regions_org, _ = self.do_prediction_new_concept(True, img_resized, self.model_zoo.get("extract_images"))
|
||||
prediction_regions, _ = self.do_prediction_new_concept(
|
||||
True, img_resized, self.model_zoo.get("extract_images"))
|
||||
prediction_regions = resize_image(prediction_regions, img_height_h, img_width_h)
|
||||
|
||||
prediction_regions_org = resize_image(prediction_regions_org,img_height_h, img_width_h )
|
||||
image_page, page_coord, cont_page = self.extract_page()
|
||||
mask_texts_only = (prediction_regions == label_text).astype(np.uint8)
|
||||
mask_images_only = (prediction_regions == label_imgs).astype(np.uint8)
|
||||
mask_seps_only = (prediction_regions == label_seps).astype(np.uint8)
|
||||
|
||||
prediction_regions_org = prediction_regions_org[page_coord[0] : page_coord[1], page_coord[2] : page_coord[3]]
|
||||
prediction_regions_org=prediction_regions_org[:,:,0]
|
||||
texts_only_cont = return_contours_of_interested_region(mask_texts_only,1,0.00001)
|
||||
seps_only_cont = return_contours_of_interested_region(mask_seps_only,1,0.00001)
|
||||
|
||||
mask_seps_only = (prediction_regions_org[:,:] ==3)*1
|
||||
mask_texts_only = (prediction_regions_org[:,:] ==1)*1
|
||||
mask_images_only=(prediction_regions_org[:,:] ==2)*1
|
||||
text_regions_p = np.zeros_like(prediction_regions)
|
||||
text_regions_p = cv2.fillPoly(text_regions_p, pts=seps_only_cont, color=label_seps)
|
||||
text_regions_p[mask_images_only == 1] = label_imgs
|
||||
text_regions_p = cv2.fillPoly(text_regions_p, pts=texts_only_cont, color=label_text)
|
||||
|
||||
polygons_seplines, hir_seplines = return_contours_of_image(mask_seps_only)
|
||||
polygons_seplines = filter_contours_area_of_image(
|
||||
mask_seps_only, polygons_seplines, hir_seplines, max_area=1, min_area=0.00001, dilate=1)
|
||||
# rs: why?
|
||||
text_regions_p[-15:] = 0
|
||||
text_regions_p[:, -15:] = 0
|
||||
|
||||
polygons_of_only_texts = return_contours_of_interested_region(mask_texts_only,1,0.00001)
|
||||
polygons_of_only_seps = return_contours_of_interested_region(mask_seps_only,1,0.00001)
|
||||
images_cont = return_contours_of_interested_region(text_regions_p, label_imgs, 0.001)
|
||||
|
||||
text_regions_p_true = np.zeros(prediction_regions_org.shape)
|
||||
text_regions_p_true = cv2.fillPoly(text_regions_p_true, pts = polygons_of_only_seps, color=(3,3,3))
|
||||
|
||||
text_regions_p_true[:,:][mask_images_only[:,:] == 1] = 2
|
||||
text_regions_p_true = cv2.fillPoly(text_regions_p_true, pts=polygons_of_only_texts, color=(1,1,1))
|
||||
|
||||
text_regions_p_true[text_regions_p_true.shape[0]-15:text_regions_p_true.shape[0], :] = 0
|
||||
text_regions_p_true[:, text_regions_p_true.shape[1]-15:text_regions_p_true.shape[1]] = 0
|
||||
|
||||
##polygons_of_images = return_contours_of_interested_region(text_regions_p_true, 2, 0.0001)
|
||||
polygons_of_images = return_contours_of_interested_region(text_regions_p_true, 2, 0.001)
|
||||
|
||||
polygons_of_images_fin = []
|
||||
for ploy_img_ind in polygons_of_images:
|
||||
box = _, _, w, h = cv2.boundingRect(ploy_img_ind)
|
||||
images_cont_fin = []
|
||||
for cont in images_cont:
|
||||
_, _, w, h = box = cv2.boundingRect(cont)
|
||||
if h < 150 or w < 150:
|
||||
pass
|
||||
else:
|
||||
page_coord_img = box2rect(box) # type: ignore
|
||||
polygons_of_images_fin.append(np.array([[page_coord_img[2], page_coord_img[0]],
|
||||
[page_coord_img[3], page_coord_img[0]],
|
||||
[page_coord_img[3], page_coord_img[1]],
|
||||
[page_coord_img[2], page_coord_img[1]]]))
|
||||
y1, y2, x1, x2 = box2rect(box) # type: ignore
|
||||
images_cont_fin.append(np.array([[[x1, y1]],
|
||||
[[x2, y1]],
|
||||
[[x2, y2]],
|
||||
[[x1, y2]]]))
|
||||
|
||||
self.logger.debug("exit get_regions_extract_images_only")
|
||||
return (text_regions_p_true,
|
||||
return (text_regions_p,
|
||||
erosion_hurts,
|
||||
polygons_seplines,
|
||||
polygons_of_images_fin,
|
||||
image_page,
|
||||
page_coord,
|
||||
cont_page)
|
||||
images_cont_fin)
|
||||
|
||||
def run(self,
|
||||
overwrite: bool = False,
|
||||
|
|
@ -159,16 +155,12 @@ class EynollahImageExtractor(Eynollah):
|
|||
dir_in: Optional[str] = None,
|
||||
dir_out: Optional[str] = None,
|
||||
dir_of_cropped_images: Optional[str] = None,
|
||||
dir_of_layout: Optional[str] = None,
|
||||
dir_of_deskewed: Optional[str] = None,
|
||||
dir_of_all: Optional[str] = None,
|
||||
dir_save_page: Optional[str] = None,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Get image and scales, then extract the page of scanned image
|
||||
"""
|
||||
self.logger.debug("enter run")
|
||||
t0_tot = time.time()
|
||||
# Log enabled features directly
|
||||
enabled_modes = []
|
||||
if self.full_layout:
|
||||
|
|
@ -181,12 +173,12 @@ class EynollahImageExtractor(Eynollah):
|
|||
self.logger.info("Saving debug plots")
|
||||
if dir_of_cropped_images:
|
||||
self.logger.info(f"Saving cropped images to: {dir_of_cropped_images}")
|
||||
if dir_of_layout:
|
||||
self.logger.info(f"Saving layout plots to: {dir_of_layout}")
|
||||
if dir_of_deskewed:
|
||||
self.logger.info(f"Saving deskewed images to: {dir_of_deskewed}")
|
||||
|
||||
self.plotter = EynollahPlotter(
|
||||
dir_out=dir_out,
|
||||
dir_of_cropped_images=dir_of_cropped_images,
|
||||
)
|
||||
if dir_in:
|
||||
t0_tot = time.time()
|
||||
ls_imgs = [os.path.join(dir_in, image_filename)
|
||||
for image_filename in filter(is_image_filename,
|
||||
os.listdir(dir_in))]
|
||||
|
|
@ -196,79 +188,73 @@ class EynollahImageExtractor(Eynollah):
|
|||
raise ValueError("run requires either a single image filename or a directory")
|
||||
|
||||
for img_filename in ls_imgs:
|
||||
self.logger.info(img_filename)
|
||||
t0 = time.time()
|
||||
|
||||
self.reset_file_name_dir(img_filename, dir_out)
|
||||
if self.enable_plotting:
|
||||
self.plotter = EynollahPlotter(dir_out=dir_out,
|
||||
dir_of_all=dir_of_all,
|
||||
dir_save_page=dir_save_page,
|
||||
dir_of_deskewed=dir_of_deskewed,
|
||||
dir_of_cropped_images=dir_of_cropped_images,
|
||||
dir_of_layout=dir_of_layout,
|
||||
image_filename_stem=Path(img_filename).stem)
|
||||
#print("text region early -11 in %.1fs", time.time() - t0)
|
||||
if os.path.exists(self.writer.output_filename):
|
||||
if overwrite:
|
||||
self.logger.warning("will overwrite existing output file '%s'", self.writer.output_filename)
|
||||
else:
|
||||
self.logger.warning("will skip input for existing output file '%s'", self.writer.output_filename)
|
||||
continue
|
||||
|
||||
pcgts = self.run_single()
|
||||
self.logger.info("Job done in %.1fs", time.time() - t0)
|
||||
self.writer.write_pagexml(pcgts)
|
||||
self.run_single(img_filename, dir_out=dir_out, overwrite=overwrite)
|
||||
|
||||
if dir_in:
|
||||
self.logger.info("All jobs done in %.1fs", time.time() - t0_tot)
|
||||
|
||||
def run_single(self):
|
||||
def run_single(self,
|
||||
img_filename: str,
|
||||
dir_out: Optional[str] = None,
|
||||
overwrite: bool = False
|
||||
) -> None:
|
||||
t0 = time.time()
|
||||
|
||||
self.logger.info(f"Processing file: {self.writer.image_filename}")
|
||||
self.logger.info(img_filename)
|
||||
|
||||
image = self.cache_images(image_filename=img_filename)
|
||||
writer = EynollahXmlWriter(
|
||||
dir_out=dir_out,
|
||||
image_filename=img_filename,
|
||||
image_width=image['img'].shape[1],
|
||||
image_height=image['img'].shape[0],
|
||||
)
|
||||
|
||||
if os.path.exists(writer.output_filename):
|
||||
if overwrite:
|
||||
self.logger.warning("will overwrite existing output file '%s'", writer.output_filename)
|
||||
else:
|
||||
self.logger.warning("will skip input for existing output file '%s'", writer.output_filename)
|
||||
return
|
||||
|
||||
self.logger.info(f"Processing file: {writer.image_filename}")
|
||||
self.logger.info("Step 1/5: Image Enhancement")
|
||||
|
||||
num_col_classifier, _ = self.run_enhancement(image)
|
||||
writer.scale_x = image['scale_x']
|
||||
writer.scale_y = image['scale_y']
|
||||
|
||||
img_res, is_image_enhanced, num_col_classifier, _ = \
|
||||
self.run_enhancement()
|
||||
|
||||
self.logger.info(f"Image: {self.image.shape[1]}x{self.image.shape[0]}, "
|
||||
f"{self.dpi} DPI, {num_col_classifier} columns")
|
||||
if is_image_enhanced:
|
||||
self.logger.info("Enhancement applied")
|
||||
|
||||
self.logger.info(f"Image: {image['img_res'].shape[1]}x{image['img_res'].shape[0]}, "
|
||||
f"scale {image['scale_x']:.1f}x{image['scale_y']:.1f}, "
|
||||
f"{image['dpi']} DPI, {num_col_classifier} columns")
|
||||
self.logger.info(f"Enhancement complete ({time.time() - t0:.1f}s)")
|
||||
|
||||
|
||||
# Image Extraction Mode
|
||||
self.logger.info("Step 2/5: Image Extraction Mode")
|
||||
t1 = time.time()
|
||||
page_cont, image_page, _ = self.extract_page(image)
|
||||
page = Region(page_cont)
|
||||
|
||||
_, _, _, polygons_of_images, \
|
||||
image_page, page_coord, cont_page = \
|
||||
self.get_regions_light_v_extract_only_images(img_res, num_col_classifier)
|
||||
_, _, images_cont = self.get_early_layout(
|
||||
image['img_res'], num_col_classifier)
|
||||
self.logger.debug("Found %d images", len(images_cont))
|
||||
|
||||
pcgts = self.writer.build_pagexml_no_full_layout(
|
||||
found_polygons_text_region=[],
|
||||
page_coord=page_coord,
|
||||
order_of_texts=[],
|
||||
all_found_textline_polygons=[],
|
||||
all_box_coord=[],
|
||||
found_polygons_text_region_img=polygons_of_images,
|
||||
found_polygons_marginals_left=[],
|
||||
found_polygons_marginals_right=[],
|
||||
all_found_textline_polygons_marginals_left=[],
|
||||
all_found_textline_polygons_marginals_right=[],
|
||||
all_box_coord_marginals_left=[],
|
||||
all_box_coord_marginals_right=[],
|
||||
slopes=[],
|
||||
slopes_marginals_left=[],
|
||||
slopes_marginals_right=[],
|
||||
cont_page=cont_page,
|
||||
polygons_seplines=[],
|
||||
found_polygons_tables=[],
|
||||
)
|
||||
# FIXME: post-hoc cropping (remove when models support it, and replace image['img_res'] with image_page)
|
||||
page_box = cv2.boundingRect(page.contour)
|
||||
images_cont = [cont - [page_box[:2]]
|
||||
for cont in images_cont]
|
||||
if self.plotter:
|
||||
self.plotter.write_images_into_directory(polygons_of_images, image_page)
|
||||
|
||||
self.plotter.write_images_into_directory(images_cont, image_page,
|
||||
name=image['name'])
|
||||
self.logger.info("Image extraction complete")
|
||||
return pcgts
|
||||
|
||||
images = [Region(cont) for cont in images_cont]
|
||||
# can be empty if above page frame
|
||||
images = [image for image in images if image.area]
|
||||
pcgts = writer.build_pagexml(
|
||||
page=page,
|
||||
img_bin=self.imread(image, binary=True) if self.input_binary else None,
|
||||
num_col=num_col_classifier,
|
||||
images=images,
|
||||
)
|
||||
writer.write_pagexml(pcgts)
|
||||
self.logger.info("Job done in %.1fs", time.time() - t0)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -3,9 +3,9 @@ Image enhancer. The output can be written as same scale of input or in new predi
|
|||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
import os
|
||||
from typing import Optional
|
||||
from pathlib import Path
|
||||
|
||||
import cv2
|
||||
|
||||
|
|
@ -48,6 +48,8 @@ class Enhancer(Eynollah):
|
|||
dir_out: Optional[str] = None,
|
||||
overwrite: bool = False,
|
||||
) -> None:
|
||||
t0 = time.time()
|
||||
self.logger.info(img_filename)
|
||||
|
||||
image = self.cache_images(image_filename=img_filename, image_pil=img_pil)
|
||||
output_filename = os.path.join(dir_out or "", image['name'] + '.png')
|
||||
|
|
@ -67,6 +69,7 @@ class Enhancer(Eynollah):
|
|||
|
||||
cv2.imwrite(output_filename, img_res)
|
||||
self.logger.info("output filename: '%s'", output_filename)
|
||||
self.logger.info("Job done in %.1fs", time.time() - t0)
|
||||
|
||||
def run(self,
|
||||
overwrite: bool = False,
|
||||
|
|
@ -78,6 +81,7 @@ class Enhancer(Eynollah):
|
|||
Enlarge and enhance the scanned images
|
||||
"""
|
||||
if dir_in:
|
||||
t0_tot = time.time()
|
||||
ls_imgs = [os.path.join(dir_in, image_filename)
|
||||
for image_filename in filter(is_image_filename,
|
||||
os.listdir(dir_in))]
|
||||
|
|
@ -87,8 +91,8 @@ class Enhancer(Eynollah):
|
|||
raise ValueError("run requires either a single image filename or a directory")
|
||||
|
||||
for img_filename in ls_imgs:
|
||||
self.logger.info(img_filename)
|
||||
|
||||
self.run_single(img_filename,
|
||||
dir_out=dir_out,
|
||||
overwrite=overwrite)
|
||||
if dir_in:
|
||||
self.logger.info("All jobs done in %.1fs", time.time() - t0_tot)
|
||||
|
|
|
|||
|
|
@ -1,806 +0,0 @@
|
|||
"""
|
||||
Machine learning based reading order detection
|
||||
"""
|
||||
|
||||
# pyright: reportCallIssue=false
|
||||
# pyright: reportUnboundVariable=false
|
||||
# pyright: reportArgumentType=false
|
||||
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
from typing import Optional
|
||||
from pathlib import Path
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import statistics
|
||||
|
||||
from .eynollah import Eynollah
|
||||
from .model_zoo import EynollahModelZoo
|
||||
from .utils.resize import resize_image
|
||||
from .utils.contour import (
|
||||
find_new_features_of_contours,
|
||||
return_contours_of_image,
|
||||
return_parent_contours,
|
||||
)
|
||||
from .utils import is_xml_filename
|
||||
|
||||
DPI_THRESHOLD = 298
|
||||
KERNEL = np.ones((5, 5), np.uint8)
|
||||
|
||||
|
||||
class Reorder(Eynollah):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
model_zoo: EynollahModelZoo,
|
||||
logger : Optional[logging.Logger] = None,
|
||||
device: str = '',
|
||||
):
|
||||
self.logger = logger or logging.getLogger('eynollah.mbreorder')
|
||||
self.model_zoo = model_zoo
|
||||
|
||||
self.setup_models(device=device)
|
||||
|
||||
def setup_models(self, device=''):
|
||||
loadable = ['reading_order']
|
||||
self.model_zoo.load_models(*loadable, device=device)
|
||||
for model in loadable:
|
||||
self.logger.debug("model %s has input shape %s", model,
|
||||
self.model_zoo.get(model).input_shape)
|
||||
|
||||
|
||||
def read_xml(self, xml_file):
|
||||
tree1 = ET.parse(xml_file, parser = ET.XMLParser(encoding='utf-8'))
|
||||
root1=tree1.getroot()
|
||||
alltags=[elem.tag for elem in root1.iter()]
|
||||
link=alltags[0].split('}')[0]+'}'
|
||||
|
||||
index_tot_regions = []
|
||||
tot_region_ref = []
|
||||
|
||||
y_len, x_len = 0, 0
|
||||
for jj in root1.iter(link+'Page'):
|
||||
y_len=int(jj.attrib['imageHeight'])
|
||||
x_len=int(jj.attrib['imageWidth'])
|
||||
|
||||
for jj in root1.iter(link+'RegionRefIndexed'):
|
||||
index_tot_regions.append(jj.attrib['index'])
|
||||
tot_region_ref.append(jj.attrib['regionRef'])
|
||||
|
||||
if (link+'PrintSpace' in alltags) or (link+'Border' in alltags):
|
||||
co_printspace = []
|
||||
if link+'PrintSpace' in alltags:
|
||||
region_tags_printspace = np.unique([x for x in alltags if x.endswith('PrintSpace')])
|
||||
else:
|
||||
region_tags_printspace = np.unique([x for x in alltags if x.endswith('Border')])
|
||||
|
||||
for tag in region_tags_printspace:
|
||||
if link+'PrintSpace' in alltags:
|
||||
tag_endings_printspace = ['}PrintSpace','}printspace']
|
||||
else:
|
||||
tag_endings_printspace = ['}Border','}border']
|
||||
|
||||
if tag.endswith(tag_endings_printspace[0]) or tag.endswith(tag_endings_printspace[1]):
|
||||
for nn in root1.iter(tag):
|
||||
c_t_in = []
|
||||
sumi = 0
|
||||
for vv in nn.iter():
|
||||
# check the format of coords
|
||||
if vv.tag == link + 'Coords':
|
||||
coords = bool(vv.attrib)
|
||||
if coords:
|
||||
p_h = vv.attrib['points'].split(' ')
|
||||
c_t_in.append(
|
||||
np.array([[int(x.split(',')[0]), int(x.split(',')[1])] for x in p_h]))
|
||||
break
|
||||
else:
|
||||
pass
|
||||
|
||||
if vv.tag == link + 'Point':
|
||||
c_t_in.append([int(float(vv.attrib['x'])), int(float(vv.attrib['y']))])
|
||||
sumi += 1
|
||||
elif vv.tag != link + 'Point' and sumi >= 1:
|
||||
break
|
||||
co_printspace.append(np.array(c_t_in))
|
||||
img_printspace = np.zeros( (y_len,x_len,3) )
|
||||
img_printspace=cv2.fillPoly(img_printspace, pts =co_printspace, color=(1,1,1))
|
||||
img_printspace = img_printspace.astype(np.uint8)
|
||||
|
||||
imgray = cv2.cvtColor(img_printspace, cv2.COLOR_BGR2GRAY)
|
||||
_, thresh = cv2.threshold(imgray, 0, 255, 0)
|
||||
contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
cnt_size = np.array([cv2.contourArea(contours[j]) for j in range(len(contours))])
|
||||
cnt = contours[np.argmax(cnt_size)]
|
||||
x, y, w, h = cv2.boundingRect(cnt)
|
||||
|
||||
bb_coord_printspace = [x, y, w, h]
|
||||
|
||||
else:
|
||||
bb_coord_printspace = None
|
||||
|
||||
|
||||
region_tags=np.unique([x for x in alltags if x.endswith('Region')])
|
||||
co_text_paragraph=[]
|
||||
co_text_drop=[]
|
||||
co_text_heading=[]
|
||||
co_text_header=[]
|
||||
co_text_marginalia=[]
|
||||
co_text_catch=[]
|
||||
co_text_page_number=[]
|
||||
co_text_signature_mark=[]
|
||||
co_sep=[]
|
||||
co_img=[]
|
||||
co_table=[]
|
||||
co_graphic=[]
|
||||
co_graphic_text_annotation=[]
|
||||
co_graphic_decoration=[]
|
||||
co_noise=[]
|
||||
|
||||
co_text_paragraph_text=[]
|
||||
co_text_drop_text=[]
|
||||
co_text_heading_text=[]
|
||||
co_text_header_text=[]
|
||||
co_text_marginalia_text=[]
|
||||
co_text_catch_text=[]
|
||||
co_text_page_number_text=[]
|
||||
co_text_signature_mark_text=[]
|
||||
co_sep_text=[]
|
||||
co_img_text=[]
|
||||
co_table_text=[]
|
||||
co_graphic_text=[]
|
||||
co_graphic_text_annotation_text=[]
|
||||
co_graphic_decoration_text=[]
|
||||
co_noise_text=[]
|
||||
|
||||
id_paragraph = []
|
||||
id_header = []
|
||||
id_heading = []
|
||||
id_marginalia = []
|
||||
|
||||
for tag in region_tags:
|
||||
if tag.endswith('}TextRegion') or tag.endswith('}Textregion'):
|
||||
for nn in root1.iter(tag):
|
||||
for child2 in nn:
|
||||
tag2 = child2.tag
|
||||
if tag2.endswith('}TextEquiv') or tag2.endswith('}TextEquiv'):
|
||||
for childtext2 in child2:
|
||||
if childtext2.tag.endswith('}Unicode') or childtext2.tag.endswith('}Unicode'):
|
||||
if "type" in nn.attrib and nn.attrib['type']=='drop-capital':
|
||||
co_text_drop_text.append(childtext2.text)
|
||||
elif "type" in nn.attrib and nn.attrib['type']=='heading':
|
||||
co_text_heading_text.append(childtext2.text)
|
||||
elif "type" in nn.attrib and nn.attrib['type']=='signature-mark':
|
||||
co_text_signature_mark_text.append(childtext2.text)
|
||||
elif "type" in nn.attrib and nn.attrib['type']=='header':
|
||||
co_text_header_text.append(childtext2.text)
|
||||
###elif "type" in nn.attrib and nn.attrib['type']=='catch-word':
|
||||
###co_text_catch_text.append(childtext2.text)
|
||||
###elif "type" in nn.attrib and nn.attrib['type']=='page-number':
|
||||
###co_text_page_number_text.append(childtext2.text)
|
||||
elif "type" in nn.attrib and nn.attrib['type']=='marginalia':
|
||||
co_text_marginalia_text.append(childtext2.text)
|
||||
else:
|
||||
co_text_paragraph_text.append(childtext2.text)
|
||||
c_t_in_drop=[]
|
||||
c_t_in_paragraph=[]
|
||||
c_t_in_heading=[]
|
||||
c_t_in_header=[]
|
||||
c_t_in_page_number=[]
|
||||
c_t_in_signature_mark=[]
|
||||
c_t_in_catch=[]
|
||||
c_t_in_marginalia=[]
|
||||
|
||||
|
||||
sumi=0
|
||||
for vv in nn.iter():
|
||||
# check the format of coords
|
||||
if vv.tag==link+'Coords':
|
||||
|
||||
coords=bool(vv.attrib)
|
||||
if coords:
|
||||
#print('birda1')
|
||||
p_h=vv.attrib['points'].split(' ')
|
||||
|
||||
|
||||
|
||||
if "type" in nn.attrib and nn.attrib['type']=='drop-capital':
|
||||
|
||||
c_t_in_drop.append( np.array( [ [ int(x.split(',')[0]) , int(x.split(',')[1]) ] for x in p_h] ) )
|
||||
|
||||
elif "type" in nn.attrib and nn.attrib['type']=='heading':
|
||||
##id_heading.append(nn.attrib['id'])
|
||||
c_t_in_heading.append( np.array( [ [ int(x.split(',')[0]) , int(x.split(',')[1]) ] for x in p_h] ) )
|
||||
|
||||
|
||||
elif "type" in nn.attrib and nn.attrib['type']=='signature-mark':
|
||||
|
||||
c_t_in_signature_mark.append( np.array( [ [ int(x.split(',')[0]) , int(x.split(',')[1]) ] for x in p_h] ) )
|
||||
#print(c_t_in_paragraph)
|
||||
elif "type" in nn.attrib and nn.attrib['type']=='header':
|
||||
#id_header.append(nn.attrib['id'])
|
||||
c_t_in_header.append( np.array( [ [ int(x.split(',')[0]) , int(x.split(',')[1]) ] for x in p_h] ) )
|
||||
|
||||
|
||||
###elif "type" in nn.attrib and nn.attrib['type']=='catch-word':
|
||||
###c_t_in_catch.append( np.array( [ [ int(x.split(',')[0]) , int(x.split(',')[1]) ] for x in p_h] ) )
|
||||
|
||||
|
||||
###elif "type" in nn.attrib and nn.attrib['type']=='page-number':
|
||||
|
||||
###c_t_in_page_number.append( np.array( [ [ int(x.split(',')[0]) , int(x.split(',')[1]) ] for x in p_h] ) )
|
||||
|
||||
elif "type" in nn.attrib and nn.attrib['type']=='marginalia':
|
||||
#id_marginalia.append(nn.attrib['id'])
|
||||
|
||||
c_t_in_marginalia.append( np.array( [ [ int(x.split(',')[0]) , int(x.split(',')[1]) ] for x in p_h] ) )
|
||||
else:
|
||||
#id_paragraph.append(nn.attrib['id'])
|
||||
|
||||
c_t_in_paragraph.append( np.array( [ [ int(x.split(',')[0]) , int(x.split(',')[1]) ] for x in p_h] ) )
|
||||
|
||||
break
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
if vv.tag==link+'Point':
|
||||
if "type" in nn.attrib and nn.attrib['type']=='drop-capital':
|
||||
|
||||
c_t_in_drop.append([ int(float(vv.attrib['x'])) , int(float(vv.attrib['y'])) ])
|
||||
sumi+=1
|
||||
|
||||
elif "type" in nn.attrib and nn.attrib['type']=='heading':
|
||||
#id_heading.append(nn.attrib['id'])
|
||||
c_t_in_heading.append([ int(float(vv.attrib['x'])) , int(float(vv.attrib['y'])) ])
|
||||
sumi+=1
|
||||
|
||||
|
||||
elif "type" in nn.attrib and nn.attrib['type']=='signature-mark':
|
||||
|
||||
c_t_in_signature_mark.append([ int(float(vv.attrib['x'])) , int(float(vv.attrib['y'])) ])
|
||||
sumi+=1
|
||||
elif "type" in nn.attrib and nn.attrib['type']=='header':
|
||||
#id_header.append(nn.attrib['id'])
|
||||
c_t_in_header.append([ int(float(vv.attrib['x'])) , int(float(vv.attrib['y'])) ])
|
||||
sumi+=1
|
||||
|
||||
|
||||
###elif "type" in nn.attrib and nn.attrib['type']=='catch-word':
|
||||
###c_t_in_catch.append([ int(float(vv.attrib['x'])) , int(float(vv.attrib['y'])) ])
|
||||
###sumi+=1
|
||||
|
||||
###elif "type" in nn.attrib and nn.attrib['type']=='page-number':
|
||||
|
||||
###c_t_in_page_number.append([ int(float(vv.attrib['x'])) , int(float(vv.attrib['y'])) ])
|
||||
###sumi+=1
|
||||
|
||||
elif "type" in nn.attrib and nn.attrib['type']=='marginalia':
|
||||
#id_marginalia.append(nn.attrib['id'])
|
||||
|
||||
c_t_in_marginalia.append([ int(float(vv.attrib['x'])) , int(float(vv.attrib['y'])) ])
|
||||
sumi+=1
|
||||
|
||||
else:
|
||||
#id_paragraph.append(nn.attrib['id'])
|
||||
c_t_in_paragraph.append([ int(float(vv.attrib['x'])) , int(float(vv.attrib['y'])) ])
|
||||
sumi+=1
|
||||
|
||||
elif vv.tag!=link+'Point' and sumi>=1:
|
||||
break
|
||||
|
||||
if len(c_t_in_drop)>0:
|
||||
co_text_drop.append(np.array(c_t_in_drop))
|
||||
if len(c_t_in_paragraph)>0:
|
||||
co_text_paragraph.append(np.array(c_t_in_paragraph))
|
||||
id_paragraph.append(nn.attrib['id'])
|
||||
if len(c_t_in_heading)>0:
|
||||
co_text_heading.append(np.array(c_t_in_heading))
|
||||
id_heading.append(nn.attrib['id'])
|
||||
|
||||
if len(c_t_in_header)>0:
|
||||
co_text_header.append(np.array(c_t_in_header))
|
||||
id_header.append(nn.attrib['id'])
|
||||
if len(c_t_in_page_number)>0:
|
||||
co_text_page_number.append(np.array(c_t_in_page_number))
|
||||
if len(c_t_in_catch)>0:
|
||||
co_text_catch.append(np.array(c_t_in_catch))
|
||||
|
||||
if len(c_t_in_signature_mark)>0:
|
||||
co_text_signature_mark.append(np.array(c_t_in_signature_mark))
|
||||
|
||||
if len(c_t_in_marginalia)>0:
|
||||
co_text_marginalia.append(np.array(c_t_in_marginalia))
|
||||
id_marginalia.append(nn.attrib['id'])
|
||||
|
||||
|
||||
elif tag.endswith('}GraphicRegion') or tag.endswith('}graphicregion'):
|
||||
for nn in root1.iter(tag):
|
||||
c_t_in=[]
|
||||
c_t_in_text_annotation=[]
|
||||
c_t_in_decoration=[]
|
||||
sumi=0
|
||||
for vv in nn.iter():
|
||||
# check the format of coords
|
||||
if vv.tag==link+'Coords':
|
||||
coords=bool(vv.attrib)
|
||||
if coords:
|
||||
p_h=vv.attrib['points'].split(' ')
|
||||
|
||||
if "type" in nn.attrib and nn.attrib['type']=='handwritten-annotation':
|
||||
c_t_in_text_annotation.append( np.array( [ [ int(x.split(',')[0]) , int(x.split(',')[1]) ] for x in p_h] ) )
|
||||
|
||||
elif "type" in nn.attrib and nn.attrib['type']=='decoration':
|
||||
c_t_in_decoration.append( np.array( [ [ int(x.split(',')[0]) , int(x.split(',')[1]) ] for x in p_h] ) )
|
||||
|
||||
else:
|
||||
c_t_in.append( np.array( [ [ int(x.split(',')[0]) , int(x.split(',')[1]) ] for x in p_h] ) )
|
||||
|
||||
|
||||
break
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
if vv.tag==link+'Point':
|
||||
if "type" in nn.attrib and nn.attrib['type']=='handwritten-annotation':
|
||||
c_t_in_text_annotation.append([ int(float(vv.attrib['x'])) , int(float(vv.attrib['y'])) ])
|
||||
sumi+=1
|
||||
|
||||
elif "type" in nn.attrib and nn.attrib['type']=='decoration':
|
||||
c_t_in_decoration.append([ int(float(vv.attrib['x'])) , int(float(vv.attrib['y'])) ])
|
||||
sumi+=1
|
||||
|
||||
else:
|
||||
c_t_in.append([ int(float(vv.attrib['x'])) , int(float(vv.attrib['y'])) ])
|
||||
sumi+=1
|
||||
|
||||
if len(c_t_in_text_annotation)>0:
|
||||
co_graphic_text_annotation.append(np.array(c_t_in_text_annotation))
|
||||
if len(c_t_in_decoration)>0:
|
||||
co_graphic_decoration.append(np.array(c_t_in_decoration))
|
||||
if len(c_t_in)>0:
|
||||
co_graphic.append(np.array(c_t_in))
|
||||
|
||||
|
||||
|
||||
elif tag.endswith('}ImageRegion') or tag.endswith('}imageregion'):
|
||||
for nn in root1.iter(tag):
|
||||
c_t_in=[]
|
||||
sumi=0
|
||||
for vv in nn.iter():
|
||||
# check the format of coords
|
||||
if vv.tag==link+'Coords':
|
||||
coords=bool(vv.attrib)
|
||||
if coords:
|
||||
p_h=vv.attrib['points'].split(' ')
|
||||
c_t_in.append( np.array( [ [ int(x.split(',')[0]) , int(x.split(',')[1]) ] for x in p_h] ) )
|
||||
break
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
if vv.tag==link+'Point':
|
||||
c_t_in.append([ int(float(vv.attrib['x'])) , int(float(vv.attrib['y'])) ])
|
||||
sumi+=1
|
||||
elif vv.tag!=link+'Point' and sumi>=1:
|
||||
break
|
||||
co_img.append(np.array(c_t_in))
|
||||
co_img_text.append(' ')
|
||||
|
||||
|
||||
elif tag.endswith('}SeparatorRegion') or tag.endswith('}separatorregion'):
|
||||
for nn in root1.iter(tag):
|
||||
c_t_in=[]
|
||||
sumi=0
|
||||
for vv in nn.iter():
|
||||
# check the format of coords
|
||||
if vv.tag==link+'Coords':
|
||||
coords=bool(vv.attrib)
|
||||
if coords:
|
||||
p_h=vv.attrib['points'].split(' ')
|
||||
c_t_in.append( np.array( [ [ int(x.split(',')[0]) , int(x.split(',')[1]) ] for x in p_h] ) )
|
||||
break
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
if vv.tag==link+'Point':
|
||||
c_t_in.append([ int(float(vv.attrib['x'])) , int(float(vv.attrib['y'])) ])
|
||||
sumi+=1
|
||||
elif vv.tag!=link+'Point' and sumi>=1:
|
||||
break
|
||||
co_sep.append(np.array(c_t_in))
|
||||
|
||||
|
||||
|
||||
elif tag.endswith('}TableRegion') or tag.endswith('}tableregion'):
|
||||
for nn in root1.iter(tag):
|
||||
c_t_in=[]
|
||||
sumi=0
|
||||
for vv in nn.iter():
|
||||
# check the format of coords
|
||||
if vv.tag==link+'Coords':
|
||||
coords=bool(vv.attrib)
|
||||
if coords:
|
||||
p_h=vv.attrib['points'].split(' ')
|
||||
c_t_in.append( np.array( [ [ int(x.split(',')[0]) , int(x.split(',')[1]) ] for x in p_h] ) )
|
||||
break
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
if vv.tag==link+'Point':
|
||||
c_t_in.append([ int(float(vv.attrib['x'])) , int(float(vv.attrib['y'])) ])
|
||||
sumi+=1
|
||||
|
||||
elif vv.tag!=link+'Point' and sumi>=1:
|
||||
break
|
||||
co_table.append(np.array(c_t_in))
|
||||
co_table_text.append(' ')
|
||||
|
||||
elif tag.endswith('}NoiseRegion') or tag.endswith('}noiseregion'):
|
||||
for nn in root1.iter(tag):
|
||||
c_t_in=[]
|
||||
sumi=0
|
||||
for vv in nn.iter():
|
||||
# check the format of coords
|
||||
if vv.tag==link+'Coords':
|
||||
coords=bool(vv.attrib)
|
||||
if coords:
|
||||
p_h=vv.attrib['points'].split(' ')
|
||||
c_t_in.append( np.array( [ [ int(x.split(',')[0]) , int(x.split(',')[1]) ] for x in p_h] ) )
|
||||
break
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
if vv.tag==link+'Point':
|
||||
c_t_in.append([ int(float(vv.attrib['x'])) , int(float(vv.attrib['y'])) ])
|
||||
sumi+=1
|
||||
|
||||
elif vv.tag!=link+'Point' and sumi>=1:
|
||||
break
|
||||
co_noise.append(np.array(c_t_in))
|
||||
co_noise_text.append(' ')
|
||||
|
||||
img = np.zeros( (y_len,x_len,3) )
|
||||
img_poly=cv2.fillPoly(img, pts =co_text_paragraph, color=(1,1,1))
|
||||
|
||||
img_poly=cv2.fillPoly(img, pts =co_text_heading, color=(2,2,2))
|
||||
img_poly=cv2.fillPoly(img, pts =co_text_header, color=(2,2,2))
|
||||
img_poly=cv2.fillPoly(img, pts =co_text_marginalia, color=(3,3,3))
|
||||
img_poly=cv2.fillPoly(img, pts =co_img, color=(4,4,4))
|
||||
img_poly=cv2.fillPoly(img, pts =co_sep, color=(5,5,5))
|
||||
|
||||
return tree1, root1, bb_coord_printspace, id_paragraph, id_header+id_heading, co_text_paragraph, co_text_header+co_text_heading,\
|
||||
tot_region_ref,x_len, y_len,index_tot_regions, img_poly
|
||||
|
||||
def return_indexes_of_contours_loctaed_inside_another_list_of_contours(self, contours, contours_loc, cx_main_loc, cy_main_loc, indexes_loc):
|
||||
indexes_of_located_cont = []
|
||||
center_x_coordinates_of_located = []
|
||||
center_y_coordinates_of_located = []
|
||||
#M_main_tot = [cv2.moments(contours_loc[j])
|
||||
#for j in range(len(contours_loc))]
|
||||
#cx_main_loc = [(M_main_tot[j]["m10"] / (M_main_tot[j]["m00"] + 1e-32)) for j in range(len(M_main_tot))]
|
||||
#cy_main_loc = [(M_main_tot[j]["m01"] / (M_main_tot[j]["m00"] + 1e-32)) for j in range(len(M_main_tot))]
|
||||
|
||||
for ij in range(len(contours)):
|
||||
results = [cv2.pointPolygonTest(contours[ij], (cx_main_loc[ind], cy_main_loc[ind]), False)
|
||||
for ind in range(len(cy_main_loc)) ]
|
||||
results = np.array(results)
|
||||
indexes_in = np.where((results == 0) | (results == 1))
|
||||
indexes = indexes_loc[indexes_in]# [(results == 0) | (results == 1)]#np.where((results == 0) | (results == 1))
|
||||
|
||||
indexes_of_located_cont.append(indexes)
|
||||
center_x_coordinates_of_located.append(np.array(cx_main_loc)[indexes_in] )
|
||||
center_y_coordinates_of_located.append(np.array(cy_main_loc)[indexes_in] )
|
||||
|
||||
return indexes_of_located_cont, center_x_coordinates_of_located, center_y_coordinates_of_located
|
||||
|
||||
def do_order_of_regions_with_model(self, contours_only_text_parent, contours_only_text_parent_h, text_regions_p):
|
||||
height1 =672#448
|
||||
width1 = 448#224
|
||||
|
||||
height2 =672#448
|
||||
width2= 448#224
|
||||
|
||||
height3 =672#448
|
||||
width3 = 448#224
|
||||
|
||||
inference_bs = 3
|
||||
|
||||
ver_kernel = np.ones((5, 1), dtype=np.uint8)
|
||||
hor_kernel = np.ones((1, 5), dtype=np.uint8)
|
||||
|
||||
|
||||
min_cont_size_to_be_dilated = 10
|
||||
if len(contours_only_text_parent)>min_cont_size_to_be_dilated:
|
||||
cx_conts, cy_conts, x_min_conts, x_max_conts, y_min_conts, y_max_conts, _ = find_new_features_of_contours(contours_only_text_parent)
|
||||
args_cont_located = np.array(range(len(contours_only_text_parent)))
|
||||
|
||||
diff_y_conts = np.abs(y_max_conts[:]-y_min_conts)
|
||||
diff_x_conts = np.abs(x_max_conts[:]-x_min_conts)
|
||||
|
||||
mean_x = statistics.mean(diff_x_conts)
|
||||
median_x = statistics.median(diff_x_conts)
|
||||
|
||||
|
||||
diff_x_ratio= diff_x_conts/mean_x
|
||||
|
||||
args_cont_located_excluded = args_cont_located[diff_x_ratio>=1.3]
|
||||
args_cont_located_included = args_cont_located[diff_x_ratio<1.3]
|
||||
|
||||
contours_only_text_parent_excluded = [contours_only_text_parent[ind] for ind in range(len(contours_only_text_parent)) if diff_x_ratio[ind]>=1.3]#contours_only_text_parent[diff_x_ratio>=1.3]
|
||||
contours_only_text_parent_included = [contours_only_text_parent[ind] for ind in range(len(contours_only_text_parent)) if diff_x_ratio[ind]<1.3]#contours_only_text_parent[diff_x_ratio<1.3]
|
||||
|
||||
|
||||
cx_conts_excluded = [cx_conts[ind] for ind in range(len(cx_conts)) if diff_x_ratio[ind]>=1.3]#cx_conts[diff_x_ratio>=1.3]
|
||||
cx_conts_included = [cx_conts[ind] for ind in range(len(cx_conts)) if diff_x_ratio[ind]<1.3]#cx_conts[diff_x_ratio<1.3]
|
||||
|
||||
cy_conts_excluded = [cy_conts[ind] for ind in range(len(cy_conts)) if diff_x_ratio[ind]>=1.3]#cy_conts[diff_x_ratio>=1.3]
|
||||
cy_conts_included = [cy_conts[ind] for ind in range(len(cy_conts)) if diff_x_ratio[ind]<1.3]#cy_conts[diff_x_ratio<1.3]
|
||||
|
||||
#print(diff_x_ratio, 'ratio')
|
||||
text_regions_p = text_regions_p.astype('uint8')
|
||||
|
||||
if len(contours_only_text_parent_excluded)>0:
|
||||
textregion_par = np.zeros((text_regions_p.shape[0], text_regions_p.shape[1])).astype('uint8')
|
||||
textregion_par = cv2.fillPoly(textregion_par, pts=contours_only_text_parent_included, color=(1,1))
|
||||
else:
|
||||
textregion_par = (text_regions_p[:,:]==1)*1
|
||||
textregion_par = textregion_par.astype('uint8')
|
||||
|
||||
text_regions_p_textregions_dilated = cv2.erode(textregion_par , hor_kernel, iterations=2)
|
||||
text_regions_p_textregions_dilated = cv2.dilate(text_regions_p_textregions_dilated , ver_kernel, iterations=4)
|
||||
text_regions_p_textregions_dilated = cv2.erode(text_regions_p_textregions_dilated , hor_kernel, iterations=1)
|
||||
text_regions_p_textregions_dilated = cv2.dilate(text_regions_p_textregions_dilated , ver_kernel, iterations=5)
|
||||
text_regions_p_textregions_dilated[text_regions_p[:,:]>1] = 0
|
||||
|
||||
|
||||
contours_only_dilated, hir_on_text_dilated = return_contours_of_image(text_regions_p_textregions_dilated)
|
||||
contours_only_dilated = return_parent_contours(contours_only_dilated, hir_on_text_dilated)
|
||||
|
||||
indexes_of_located_cont, center_x_coordinates_of_located, center_y_coordinates_of_located = self.return_indexes_of_contours_loctaed_inside_another_list_of_contours(contours_only_dilated, contours_only_text_parent_included, cx_conts_included, cy_conts_included, args_cont_located_included)
|
||||
|
||||
|
||||
if len(args_cont_located_excluded)>0:
|
||||
for ind in args_cont_located_excluded:
|
||||
indexes_of_located_cont.append(np.array([ind]))
|
||||
contours_only_dilated.append(contours_only_text_parent[ind])
|
||||
center_y_coordinates_of_located.append(0)
|
||||
|
||||
array_list = [np.array([elem]) if isinstance(elem, int) else elem for elem in indexes_of_located_cont]
|
||||
flattened_array = np.concatenate([arr.ravel() for arr in array_list])
|
||||
#print(len( np.unique(flattened_array)), 'indexes_of_located_cont uniques')
|
||||
|
||||
missing_textregions = list( set(np.array(range(len(contours_only_text_parent))) ) - set(np.unique(flattened_array)) )
|
||||
#print(missing_textregions, 'missing_textregions')
|
||||
|
||||
for ind in missing_textregions:
|
||||
indexes_of_located_cont.append(np.array([ind]))
|
||||
contours_only_dilated.append(contours_only_text_parent[ind])
|
||||
center_y_coordinates_of_located.append(0)
|
||||
|
||||
|
||||
if contours_only_text_parent_h:
|
||||
for vi in range(len(contours_only_text_parent_h)):
|
||||
indexes_of_located_cont.append(int(vi+len(contours_only_text_parent)))
|
||||
|
||||
array_list = [np.array([elem]) if isinstance(elem, int) else elem for elem in indexes_of_located_cont]
|
||||
flattened_array = np.concatenate([arr.ravel() for arr in array_list])
|
||||
|
||||
y_len = text_regions_p.shape[0]
|
||||
x_len = text_regions_p.shape[1]
|
||||
|
||||
img_poly = np.zeros((y_len,x_len), dtype='uint8')
|
||||
###img_poly[text_regions_p[:,:]==1] = 1
|
||||
###img_poly[text_regions_p[:,:]==2] = 2
|
||||
###img_poly[text_regions_p[:,:]==3] = 4
|
||||
###img_poly[text_regions_p[:,:]==6] = 5
|
||||
|
||||
##img_poly[text_regions_p[:,:]==1] = 1
|
||||
##img_poly[text_regions_p[:,:]==2] = 2
|
||||
##img_poly[text_regions_p[:,:]==3] = 3
|
||||
##img_poly[text_regions_p[:,:]==4] = 4
|
||||
##img_poly[text_regions_p[:,:]==5] = 5
|
||||
|
||||
img_poly = np.copy(text_regions_p)
|
||||
|
||||
img_header_and_sep = np.zeros((y_len,x_len), dtype='uint8')
|
||||
if contours_only_text_parent_h:
|
||||
_, cy_main, x_min_main, x_max_main, y_min_main, y_max_main, _ = find_new_features_of_contours(
|
||||
contours_only_text_parent_h)
|
||||
for j in range(len(cy_main)):
|
||||
img_header_and_sep[int(y_max_main[j]):int(y_max_main[j])+12,
|
||||
int(x_min_main[j]):int(x_max_main[j])] = 1
|
||||
co_text_all_org = contours_only_text_parent + contours_only_text_parent_h
|
||||
if len(contours_only_text_parent)>min_cont_size_to_be_dilated:
|
||||
co_text_all = contours_only_dilated + contours_only_text_parent_h
|
||||
else:
|
||||
co_text_all = contours_only_text_parent + contours_only_text_parent_h
|
||||
else:
|
||||
co_text_all_org = contours_only_text_parent
|
||||
if len(contours_only_text_parent)>min_cont_size_to_be_dilated:
|
||||
co_text_all = contours_only_dilated
|
||||
else:
|
||||
co_text_all = contours_only_text_parent
|
||||
|
||||
if not len(co_text_all):
|
||||
return [], []
|
||||
|
||||
labels_con = np.zeros((int(y_len /6.), int(x_len/6.), len(co_text_all)), dtype=bool)
|
||||
|
||||
co_text_all = [(i/6).astype(int) for i in co_text_all]
|
||||
for i in range(len(co_text_all)):
|
||||
img = labels_con[:,:,i].astype(np.uint8)
|
||||
|
||||
#img = cv2.resize(img, (int(img.shape[1]/6), int(img.shape[0]/6)), interpolation=cv2.INTER_NEAREST)
|
||||
|
||||
cv2.fillPoly(img, pts=[co_text_all[i]], color=(1,))
|
||||
labels_con[:,:,i] = img
|
||||
|
||||
|
||||
labels_con = resize_image(labels_con.astype(np.uint8), height1, width1).astype(bool)
|
||||
img_header_and_sep = resize_image(img_header_and_sep, height1, width1)
|
||||
img_poly = resize_image(img_poly, height3, width3)
|
||||
|
||||
|
||||
|
||||
input_1 = np.zeros((inference_bs, height1, width1, 3))
|
||||
ordered = [list(range(len(co_text_all)))]
|
||||
index_update = 0
|
||||
#print(labels_con.shape[2],"number of regions for reading order")
|
||||
while index_update>=0:
|
||||
ij_list = ordered.pop(index_update)
|
||||
i = ij_list.pop(0)
|
||||
|
||||
ante_list = []
|
||||
post_list = []
|
||||
tot_counter = 0
|
||||
batch = []
|
||||
for j in ij_list:
|
||||
img1 = labels_con[:,:,i].astype(float)
|
||||
img2 = labels_con[:,:,j].astype(float)
|
||||
img1[img_poly==5] = 2
|
||||
img2[img_poly==5] = 2
|
||||
img1[img_header_and_sep==1] = 3
|
||||
img2[img_header_and_sep==1] = 3
|
||||
|
||||
input_1[len(batch), :, :, 0] = img1 / 3.
|
||||
input_1[len(batch), :, :, 2] = img2 / 3.
|
||||
input_1[len(batch), :, :, 1] = img_poly / 5.
|
||||
|
||||
tot_counter += 1
|
||||
batch.append(j)
|
||||
if tot_counter % inference_bs == 0 or tot_counter == len(ij_list):
|
||||
y_pr = self.model_zoo.get('reading_order').predict(input_1, verbose=0)
|
||||
for jb, j in enumerate(batch):
|
||||
if y_pr[jb][0]>=0.5:
|
||||
post_list.append(j)
|
||||
else:
|
||||
ante_list.append(j)
|
||||
batch = []
|
||||
|
||||
if len(ante_list):
|
||||
ordered.insert(index_update, ante_list)
|
||||
index_update += 1
|
||||
ordered.insert(index_update, [i])
|
||||
if len(post_list):
|
||||
ordered.insert(index_update + 1, post_list)
|
||||
|
||||
index_update = -1
|
||||
for index_next, ij_list in enumerate(ordered):
|
||||
if len(ij_list) > 1:
|
||||
index_update = index_next
|
||||
break
|
||||
|
||||
ordered = [i[0] for i in ordered]
|
||||
|
||||
##id_all_text = np.array(id_all_text)[index_sort]
|
||||
|
||||
|
||||
if len(contours_only_text_parent)>min_cont_size_to_be_dilated:
|
||||
org_contours_indexes = []
|
||||
for ind in range(len(ordered)):
|
||||
region_with_curr_order = ordered[ind]
|
||||
if region_with_curr_order < len(contours_only_dilated):
|
||||
if np.isscalar(indexes_of_located_cont[region_with_curr_order]):
|
||||
org_contours_indexes = org_contours_indexes + [indexes_of_located_cont[region_with_curr_order]]
|
||||
else:
|
||||
arg_sort_located_cont = np.argsort(center_y_coordinates_of_located[region_with_curr_order])
|
||||
org_contours_indexes = org_contours_indexes + list(np.array(indexes_of_located_cont[region_with_curr_order])[arg_sort_located_cont]) ##org_contours_indexes + list (
|
||||
else:
|
||||
org_contours_indexes = org_contours_indexes + [indexes_of_located_cont[region_with_curr_order]]
|
||||
|
||||
region_ids = ['region_%04d' % i for i in range(len(co_text_all_org))]
|
||||
return org_contours_indexes, region_ids
|
||||
else:
|
||||
region_ids = ['region_%04d' % i for i in range(len(co_text_all_org))]
|
||||
return ordered, region_ids
|
||||
|
||||
|
||||
|
||||
|
||||
def run(self,
|
||||
overwrite: bool = False,
|
||||
xml_filename: Optional[str] = None,
|
||||
dir_in: Optional[str] = None,
|
||||
dir_out: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
Get image and scales, then extract the page of scanned image
|
||||
"""
|
||||
self.logger.debug("enter run")
|
||||
t0_tot = time.time()
|
||||
|
||||
if dir_in:
|
||||
ls_xmls = [os.path.join(dir_in, xml_filename)
|
||||
for xml_filename in filter(is_xml_filename,
|
||||
os.listdir(dir_in))]
|
||||
elif xml_filename:
|
||||
ls_xmls = [xml_filename]
|
||||
else:
|
||||
raise ValueError("run requires either a single image filename or a directory")
|
||||
|
||||
for xml_filename in ls_xmls:
|
||||
self.logger.info(xml_filename)
|
||||
t0 = time.time()
|
||||
|
||||
file_name = Path(xml_filename).stem
|
||||
(tree_xml, root_xml, bb_coord_printspace, id_paragraph, id_header,
|
||||
co_text_paragraph, co_text_header, tot_region_ref,
|
||||
x_len, y_len, index_tot_regions, img_poly) = self.read_xml(xml_filename)
|
||||
|
||||
id_all_text = id_paragraph + id_header
|
||||
|
||||
order_text_new, id_of_texts_tot = self.do_order_of_regions_with_model(co_text_paragraph, co_text_header, img_poly[:,:,0])
|
||||
|
||||
id_all_text = np.array(id_all_text)[order_text_new]
|
||||
|
||||
alltags=[elem.tag for elem in root_xml.iter()]
|
||||
|
||||
|
||||
|
||||
link=alltags[0].split('}')[0]+'}'
|
||||
name_space = alltags[0].split('}')[0]
|
||||
name_space = name_space.split('{')[1]
|
||||
|
||||
page_element = root_xml.find(link+'Page')
|
||||
|
||||
|
||||
old_ro = root_xml.find(".//{*}ReadingOrder")
|
||||
|
||||
if old_ro is not None:
|
||||
page_element.remove(old_ro)
|
||||
|
||||
#print(old_ro, 'old_ro')
|
||||
ro_subelement = ET.Element('ReadingOrder')
|
||||
|
||||
ro_subelement2 = ET.SubElement(ro_subelement, 'OrderedGroup')
|
||||
ro_subelement2.set('id', "ro357564684568544579089")
|
||||
|
||||
for index, id_text in enumerate(id_all_text):
|
||||
new_element_2 = ET.SubElement(ro_subelement2, 'RegionRefIndexed')
|
||||
new_element_2.set('regionRef', id_all_text[index])
|
||||
new_element_2.set('index', str(index))
|
||||
|
||||
if (link+'PrintSpace' in alltags) or (link+'Border' in alltags):
|
||||
page_element.insert(1, ro_subelement)
|
||||
else:
|
||||
page_element.insert(0, ro_subelement)
|
||||
|
||||
alltags=[elem.tag for elem in root_xml.iter()]
|
||||
|
||||
ET.register_namespace("",name_space)
|
||||
assert dir_out
|
||||
tree_xml.write(os.path.join(dir_out, file_name+'.xml'),
|
||||
xml_declaration=True,
|
||||
method='xml',
|
||||
encoding="utf-8",
|
||||
default_namespace=None)
|
||||
|
||||
#sys.exit()
|
||||
|
||||
|
|
@ -25,6 +25,7 @@ MODEL_VRAM_LIMITS = {
|
|||
"table": 1818,
|
||||
"reading_order": 632,
|
||||
"ocr": 2600, # 850 for bs 8
|
||||
"extract_images": 954,
|
||||
}
|
||||
|
||||
class EynollahModelZoo:
|
||||
|
|
@ -48,7 +49,7 @@ class EynollahModelZoo:
|
|||
self._overrides = []
|
||||
if model_overrides:
|
||||
self.override_models(*model_overrides)
|
||||
self._loaded: Dict[str, Union[Predictor, AnyModel]] = {}
|
||||
self._loaded: Dict[str, Predictor] = {}
|
||||
|
||||
@property
|
||||
def model_overrides(self):
|
||||
|
|
@ -162,7 +163,7 @@ class EynollahModelZoo:
|
|||
model._name = model_category
|
||||
return model
|
||||
|
||||
def get(self, model_category: str) -> Union[Predictor, AnyModel]:
|
||||
def get(self, model_category: str) -> Predictor:
|
||||
if model_category not in self._loaded:
|
||||
raise ValueError(f'Model "{model_category}" not previously loaded with "load_model(..)"')
|
||||
return self._loaded[model_category]
|
||||
|
|
@ -245,7 +246,7 @@ class EynollahModelZoo:
|
|||
self.logger.warning("no GPU device available")
|
||||
return device0
|
||||
|
||||
def _load_keras_model(self, model_category, model_path, device=''):
|
||||
def _load_keras_model(self, model_category, model_path, device='') -> AnyModel:
|
||||
os.environ['TF_USE_LEGACY_KERAS'] = '1' # avoid Keras 3 after TF 2.15
|
||||
from ocrd_utils import tf_disable_interactive_logs
|
||||
tf_disable_interactive_logs()
|
||||
|
|
@ -279,7 +280,7 @@ class EynollahModelZoo:
|
|||
|
||||
return model
|
||||
|
||||
def _load_serving_model(self, model_category, model_path, device=''):
|
||||
def _load_serving_model(self, model_category, model_path, device='') -> AnyModel:
|
||||
from ocrd_utils import tf_disable_interactive_logs
|
||||
tf_disable_interactive_logs()
|
||||
import tensorflow as tf
|
||||
|
|
@ -303,7 +304,7 @@ class EynollahModelZoo:
|
|||
|
||||
return model
|
||||
|
||||
def _load_onnx_model(self, model_category, model_path, device=''):
|
||||
def _load_onnx_model(self, model_category, model_path, device='') -> AnyModel:
|
||||
import onnxruntime as ort
|
||||
import numpy as np
|
||||
from ocrd_utils import config
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
from functools import cached_property
|
||||
from typing import Optional
|
||||
from PIL import Image
|
||||
from ocrd_models import OcrdPage
|
||||
from ocrd import OcrdPageResultImage, Processor, OcrdPageResult
|
||||
|
||||
from eynollah.model_zoo.model_zoo import EynollahModelZoo
|
||||
|
||||
from .eynollah import Eynollah, EynollahXmlWriter
|
||||
from .eynollah import Eynollah
|
||||
|
||||
class EynollahProcessor(Processor):
|
||||
@cached_property
|
||||
|
|
@ -95,4 +96,9 @@ class EynollahProcessor(Processor):
|
|||
img_pil=page_image, pcgts=pcgts,
|
||||
# ocrd.Processor will handle OCRD_EXISTING_OUTPUT more flexibly
|
||||
overwrite=True)
|
||||
if self.parameter['binarize'] and (img_alt := next(
|
||||
(img for img in pcgts.Page.AlternativeImage
|
||||
if img.comments == "binarized"), None)):
|
||||
result.images.append(OcrdPageResultImage(
|
||||
Image.fromarray(img_alt.filename), '.IMG-BIN', img_alt))
|
||||
return result
|
||||
|
|
|
|||
359
src/eynollah/reorder.py
Normal file
359
src/eynollah/reorder.py
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
"""
|
||||
Machine learning based reading order detection
|
||||
"""
|
||||
|
||||
# pyright: reportCallIssue=false
|
||||
# pyright: reportUnboundVariable=false
|
||||
# pyright: reportArgumentType=false
|
||||
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
from typing import Optional
|
||||
from pathlib import Path
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import statistics
|
||||
|
||||
from ocrd_utils import (
|
||||
polygon_from_points,
|
||||
xywh_from_points,
|
||||
)
|
||||
|
||||
from .eynollah import Eynollah
|
||||
from .model_zoo import EynollahModelZoo
|
||||
from .utils.resize import resize_image
|
||||
from .utils.contour import (
|
||||
find_new_features_of_contours,
|
||||
return_contours_of_image,
|
||||
return_parent_contours,
|
||||
)
|
||||
from .utils import is_xml_filename
|
||||
|
||||
DPI_THRESHOLD = 298
|
||||
KERNEL = np.ones((5, 5), np.uint8)
|
||||
|
||||
|
||||
class Reorder(Eynollah):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
model_zoo: EynollahModelZoo,
|
||||
logger : Optional[logging.Logger] = None,
|
||||
device: str = '',
|
||||
model_based: bool = True,
|
||||
# also expose these on CLI?
|
||||
ignore_page_extraction: bool = False,
|
||||
right2left : bool = False,
|
||||
input_binary: bool = False,
|
||||
num_col_upper: int = 0,
|
||||
num_col_lower: int = 0,
|
||||
):
|
||||
self.logger = logger or logging.getLogger('eynollah.reorder')
|
||||
self.model_based = model_based
|
||||
self.ignore_page_extraction = ignore_page_extraction
|
||||
self.tables = True # for find_num_col
|
||||
self.right2left = right2left
|
||||
self.input_binary = input_binary
|
||||
self.num_col_upper = num_col_upper
|
||||
self.num_col_lower = num_col_lower
|
||||
self.model_zoo = model_zoo
|
||||
self.setup_models(device=device)
|
||||
|
||||
def setup_models(self, device=''):
|
||||
if self.model_based:
|
||||
loadable = ['reading_order']
|
||||
else:
|
||||
loadable = ['page', 'col_classifier']
|
||||
if self.input_binary:
|
||||
loadable.append('binarization')
|
||||
self.model_zoo.load_models(*loadable, device=device)
|
||||
for model in loadable:
|
||||
self.logger.debug("model %s has input shape %s", model,
|
||||
self.model_zoo.get(model).input_shape)
|
||||
|
||||
def read_xml(self,
|
||||
xml_file: str,
|
||||
label_text=1,
|
||||
label_head=2,
|
||||
label_imgs=5,
|
||||
label_seps=6,
|
||||
label_marg=8,
|
||||
label_drop=4,
|
||||
label_tabs=10,
|
||||
):
|
||||
tree1 = ET.parse(xml_file, parser=ET.XMLParser(encoding='utf-8'))
|
||||
root1 = tree1.getroot()
|
||||
alltags=[elem.tag for elem in root1.iter()]
|
||||
link=alltags[0].split('}')[0]+'}'
|
||||
|
||||
index_tot_regions = []
|
||||
tot_region_ref = []
|
||||
|
||||
page = root1.find(link+'Page')
|
||||
height = int(page.get('imageHeight', 0))
|
||||
width = int(page.get('imageWidth', 0))
|
||||
skew = -float(page.get('orientation', 0))
|
||||
img_filename = page.get('imageFilename', '')
|
||||
|
||||
for jj in root1.iter(link+'RegionRefIndexed'):
|
||||
index_tot_regions.append(jj.attrib['index'])
|
||||
tot_region_ref.append(jj.attrib['regionRef'])
|
||||
|
||||
bb_coord_printspace = None
|
||||
if (link+'PrintSpace' in alltags or
|
||||
link+'Border' in alltags):
|
||||
tag_printspace = next(x for x in alltags
|
||||
if x.endswith(('Border', 'PrintSpace')))
|
||||
nn = page.find(tag_printspace)
|
||||
coords = nn.find(link + 'Coords')
|
||||
if points := coords.attrib.get('points'):
|
||||
xywh = xywh_from_points(points)
|
||||
bb_coord_printspace = [xywh['x'],
|
||||
xywh['y'],
|
||||
xywh['w'],
|
||||
xywh['h']]
|
||||
|
||||
seps_cont = []
|
||||
imgs_cont = []
|
||||
tabs_cont = []
|
||||
text_para_cont = []
|
||||
text_para_ids = []
|
||||
text_drop_cont = []
|
||||
text_drop_ids = []
|
||||
text_head_cont = []
|
||||
text_head_ids = []
|
||||
text_marg_cont = []
|
||||
text_marg_ids = []
|
||||
|
||||
for nn in root1.iter():
|
||||
if not nn.tag.endswith('Region'):
|
||||
continue
|
||||
if (coords := nn.find(link + 'Coords')) is None:
|
||||
continue
|
||||
if (points := coords.attrib.get('points')) is None:
|
||||
continue
|
||||
if (id_ := nn.get('id')) is None:
|
||||
continue
|
||||
poly = polygon_from_points(points)
|
||||
cont = np.array(poly, dtype=int)[:, np.newaxis]
|
||||
if nn.tag.endswith('}TextRegion'):
|
||||
type_ = nn.get('type', '')
|
||||
if type_ == 'drop-capital':
|
||||
text_drop_cont.append(cont)
|
||||
text_drop_ids.append(id_)
|
||||
elif type_ == 'heading':
|
||||
text_head_cont.append(cont)
|
||||
text_head_ids.append(id_)
|
||||
elif type_ == 'header':
|
||||
# FIXME: do not keep that mapping
|
||||
text_head_cont.append(cont)
|
||||
text_head_ids.append(id_)
|
||||
elif type_ == 'marginalia':
|
||||
text_marg_cont.append(cont)
|
||||
text_marg_ids.append(id_)
|
||||
else:
|
||||
text_para_cont.append(cont)
|
||||
text_para_ids.append(id_)
|
||||
|
||||
elif nn.tag.endswith('}TableRegion'):
|
||||
tabs_cont.append(cont)
|
||||
|
||||
elif nn.tag.endswith('}GraphicRegion'):
|
||||
imgs_cont.append(cont)
|
||||
|
||||
elif nn.tag.endswith('}ImageRegion'):
|
||||
imgs_cont.append(cont)
|
||||
|
||||
elif nn.tag.endswith('}SeparatorRegion'):
|
||||
seps_cont.append(cont)
|
||||
|
||||
img = np.zeros((height, width), dtype=np.uint8)
|
||||
img = cv2.fillPoly(img, pts=text_para_cont, color=label_text)
|
||||
img = cv2.fillPoly(img, pts=text_head_cont, color=label_head)
|
||||
img = cv2.fillPoly(img, pts=text_marg_cont, color=label_marg)
|
||||
img = cv2.fillPoly(img, pts=text_drop_cont, color=label_drop)
|
||||
img = cv2.fillPoly(img, pts=tabs_cont, color=label_tabs)
|
||||
img = cv2.fillPoly(img, pts=imgs_cont, color=label_imgs)
|
||||
img = cv2.fillPoly(img, pts=seps_cont, color=label_seps)
|
||||
|
||||
return (tree1, root1,
|
||||
bb_coord_printspace,
|
||||
text_para_ids, text_head_ids, text_drop_ids,
|
||||
text_para_cont, text_head_cont, text_drop_cont,
|
||||
tot_region_ref,
|
||||
width, height, skew, img_filename,
|
||||
index_tot_regions,
|
||||
img)
|
||||
|
||||
def run(self,
|
||||
overwrite: bool = False,
|
||||
xml_filename: Optional[str] = None,
|
||||
dir_in: Optional[str] = None,
|
||||
dir_imgs: Optional[str] = None,
|
||||
dir_out: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
Get image and scales, then extract the page of scanned image
|
||||
"""
|
||||
self.logger.debug("enter run")
|
||||
|
||||
if dir_in:
|
||||
t0_tot = time.time()
|
||||
ls_xmls = [os.path.join(dir_in, xml_filename)
|
||||
for xml_filename in filter(is_xml_filename,
|
||||
os.listdir(dir_in))]
|
||||
elif xml_filename:
|
||||
ls_xmls = [xml_filename]
|
||||
else:
|
||||
raise ValueError("run requires either a single image filename or a directory")
|
||||
|
||||
for xml_filename in ls_xmls:
|
||||
self.run_single(xml_filename,
|
||||
dir_out=dir_out,
|
||||
dir_imgs=dir_imgs,
|
||||
overwrite=overwrite)
|
||||
|
||||
if dir_in:
|
||||
self.logger.info("All jobs done in %.1fs", time.time() - t0_tot)
|
||||
|
||||
def run_single(self,
|
||||
xml_filename: str,
|
||||
dir_imgs: Optional[str] = None,
|
||||
dir_out: Optional[str] = None,
|
||||
overwrite: bool = False,
|
||||
label_imgs=5,
|
||||
label_seps=6,
|
||||
) -> None:
|
||||
self.logger.info(xml_filename)
|
||||
t0 = time.time()
|
||||
|
||||
file_name = Path(xml_filename).stem
|
||||
(tree_xml, root_xml,
|
||||
_, # FIXME: crop img_poly and contours (bb_coord_printspace)
|
||||
para_ids, head_ids, drop_ids,
|
||||
para_cont, head_cont, drop_cont,
|
||||
_, # FIXME: do not ignore existing RO (tot_region_ref)
|
||||
width, height, skew, img_filename,
|
||||
_, # FIXME: do not ignore existing RO (index_tot_regions)
|
||||
region_labels) = self.read_xml(xml_filename)
|
||||
|
||||
all_text_ids = np.array(para_ids + head_ids + drop_ids)
|
||||
|
||||
self.logger.debug("ordering %d paragraphs, %d headings and %d drop-capitals",
|
||||
len(para_ids), len(head_ids), len(drop_ids))
|
||||
if self.model_based:
|
||||
order_text = self.do_order_of_regions_with_model(
|
||||
para_cont,
|
||||
head_cont,
|
||||
drop_cont,
|
||||
region_labels)
|
||||
else:
|
||||
if img_filename and (
|
||||
os.path.exists(img_path := img_filename) or
|
||||
os.path.exists(img_path := os.path.join('..', img_filename)) or
|
||||
dir_imgs and
|
||||
os.path.exists(img_path := os.path.join(dir_imgs, img_filename)) or
|
||||
dir_imgs and
|
||||
os.path.exists(img_path := os.path.join(dir_imgs, os.path.basename(img_filename)))):
|
||||
img_filename = img_path
|
||||
else:
|
||||
xml_basename = Path(xml_filename).with_suffix('')
|
||||
def try_suffixes(basename, suffixes):
|
||||
for suf in suffixes:
|
||||
if (filename := basename.with_suffix(suf)).exists():
|
||||
yield filename
|
||||
elif dir_imgs and (filename := (dir_imgs / basename).with_suffix(suf)).exists():
|
||||
yield filename
|
||||
img_filename = next(try_suffixes(xml_basename,
|
||||
['.tif', '.TIF',
|
||||
'.jpg', '.JPG',
|
||||
'.png', '.PNG',
|
||||
'.jpeg', '.gif']))
|
||||
# load and analyse image
|
||||
image = self.cache_images(image_filename=img_filename)
|
||||
_, num_col, _ = self.resize_and_enhance_image_with_column_classifier(image)
|
||||
if image['img_res'].shape[:2] != region_labels.shape:
|
||||
# image was resized by col-classifier
|
||||
# so bring label map to same size
|
||||
# (order rules have similar expectations)
|
||||
region_labels = resize_image(region_labels, *image['img_res'].shape[:2])
|
||||
scale_factor = np.array([[image['scale_x'], image['scale_y']]])
|
||||
para_cont = [(cont * scale_factor).astype(int) for cont in para_cont]
|
||||
head_cont = [(cont * scale_factor).astype(int) for cont in head_cont]
|
||||
drop_cont = [(cont * scale_factor).astype(int) for cont in drop_cont]
|
||||
|
||||
# in Eynollah: regions_without_separators
|
||||
nonsep_labels = np.copy(region_labels)
|
||||
nonsep_labels[label_seps] = 0
|
||||
nonsep_labels[label_imgs] = 0
|
||||
|
||||
# deskew
|
||||
if np.abs(skew) >= 0.13: # in Eynollah: SLOPE_THRESHOLD
|
||||
_, region_labels, nonsep_labels = self.get_deskewed_masks(
|
||||
skew, np.zeros((1, 1)), region_labels, nonsep_labels)
|
||||
# also deskew contours
|
||||
# (directly instead of match_deskewed_contours)
|
||||
# rotate_image() does not enlarge canvas,
|
||||
# so our calculation must compensate
|
||||
h_o, w_o = image['img_res'].shape[:2]
|
||||
M = cv2.getRotationMatrix2D((0.5 * w_o, 0.5 * h_o), -skew, 1.0)[:2, :2]
|
||||
cos = np.abs(M[0, 0])
|
||||
sin = np.abs(M[0, 1])
|
||||
off = np.array([[0.5 * (w_o * cos + h_o * sin - w_o),
|
||||
0.5 * (w_o * sin + h_o * cos - h_o)]],
|
||||
dtype=int)
|
||||
if skew > 0:
|
||||
off[0, 1] = -off[0, 1]
|
||||
else:
|
||||
off[0, 0] = -off[0, 0]
|
||||
para_cont = [np.dot(cont, M).astype(int) - off for cont in para_cont]
|
||||
head_cont = [np.dot(cont, M).astype(int) - off for cont in head_cont]
|
||||
drop_cont = [np.dot(cont, M).astype(int) - off for cont in drop_cont]
|
||||
|
||||
order_text = self.do_order_of_regions_heuristic(
|
||||
para_cont,
|
||||
head_cont,
|
||||
drop_cont,
|
||||
region_labels,
|
||||
nonsep_labels,
|
||||
num_col,
|
||||
False) # in Eynollah: erosion_hurts
|
||||
|
||||
all_text_ids = all_text_ids[order_text]
|
||||
|
||||
alltags=[elem.tag for elem in root_xml.iter()]
|
||||
|
||||
link=alltags[0].split('}')[0]+'}'
|
||||
ET.register_namespace("", link[1:-1])
|
||||
|
||||
page = root_xml.find(link+'Page')
|
||||
ro_old = page.find(link+'ReadingOrder')
|
||||
if ro_old is not None:
|
||||
page.remove(ro_old)
|
||||
|
||||
ro_new = ET.Element('ReadingOrder')
|
||||
ro_group = ET.SubElement(ro_new, 'OrderedGroup')
|
||||
ro_group.set('id', "ro357564684568544579089")
|
||||
|
||||
for index, id_text in enumerate(all_text_ids):
|
||||
ro_ref = ET.SubElement(ro_group, 'RegionRefIndexed')
|
||||
ro_ref.set('regionRef', id_text)
|
||||
ro_ref.set('index', str(index))
|
||||
|
||||
pos = len(page.findall(link+'AlternativeImage') +
|
||||
page.findall(link+'Border') +
|
||||
page.findall(link+'PrintSpace'))
|
||||
page.insert(pos, ro_new)
|
||||
|
||||
output_filename = os.path.join(dir_out or "", file_name + '.xml')
|
||||
self.logger.info("output filename: '%s'", output_filename)
|
||||
tree_xml.write(output_filename,
|
||||
xml_declaration=True,
|
||||
method='xml',
|
||||
encoding="utf-8",
|
||||
default_namespace=None)
|
||||
self.logger.info("Job done in %.1fs", time.time() - t0)
|
||||
|
||||
|
|
@ -22,6 +22,7 @@ CURRENT_MODELS += model_eynollah_ocr_cnnrnn_20250930
|
|||
CURRENT_MODELS += eynollah-binarization_20210425
|
||||
CURRENT_MODELS += eynollah-column-classifier_20210425
|
||||
CURRENT_MODELS += eynollah-enhancement_20210425
|
||||
CURRENT_MODELS += eynollah-main-regions_20231127_672_org_ens_11_13_16_17_18
|
||||
|
||||
# tf (SavedModel format) for training
|
||||
# onnx conversion for fast inference
|
||||
|
|
|
|||
|
|
@ -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, prepared, ops
|
||||
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 = [prepared.prep(poly) for poly in textlines_large_poly]
|
||||
textlines_small_indexes_interlarge = []
|
||||
for small_poly in textlines_small_poly:
|
||||
intersections = [small_poly.intersection(prep.context).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
|
||||
|
||||
|
|
@ -1288,7 +1219,7 @@ def find_number_of_columns_in_document(
|
|||
separator_mask: np.ndarray,
|
||||
num_col_classifier: int,
|
||||
tables: bool,
|
||||
contours_h: List[np.ndarray] = None,
|
||||
contours_h: List[np.ndarray] = [],
|
||||
logger=None
|
||||
) -> Tuple[int, List[int], np.ndarray, List[int], np.ndarray]:
|
||||
"""
|
||||
|
|
@ -1299,7 +1230,7 @@ def find_number_of_columns_in_document(
|
|||
* separator_mask: mask of (separator-only) region labels
|
||||
* num_col_classifier: predicted (expected) number of columns of the page
|
||||
* tables: whether tables may be present
|
||||
* contours_h: polygons of potential headings (serving as additional horizontal separators)
|
||||
* contours_h: contours of potential headings (serving as additional horizontal separators)
|
||||
* logger
|
||||
|
||||
Returns: a tuple of
|
||||
|
|
@ -1422,7 +1353,7 @@ def find_number_of_columns_in_document(
|
|||
matrix_of_seps_ch[len(cy_seps_hor):,8]=dist_y_ver
|
||||
matrix_of_seps_ch[len(cy_seps_hor):,9]=1
|
||||
|
||||
if contours_h is not None:
|
||||
if len(contours_h):
|
||||
_, dist_x_head, x_min_head, x_max_head, cy_head, _, y_min_head, y_max_head, _ = \
|
||||
find_features_of_lines(contours_h)
|
||||
matrix_l_n = np.zeros((len(cy_head), matrix_of_seps_ch.shape[1]), dtype=int)
|
||||
|
|
@ -1451,7 +1382,7 @@ def find_number_of_columns_in_document(
|
|||
(x_max_seps_hor >= .84 * width)]
|
||||
cy_seps_splitters = np.append(cy_seps_splitters, special_separators)
|
||||
|
||||
if contours_h is not None:
|
||||
if len(contours_h):
|
||||
y_min_splitters_head = y_min_head[(x_min_head <= .16 * width) &
|
||||
(x_max_head >= .84 * width)]
|
||||
y_max_splitters_head = y_max_head[(x_min_head <= .16 * width) &
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
||||
|
|
|
|||
|
|
@ -1585,6 +1585,7 @@ def do_work_of_slopes_new_curved(
|
|||
mask_line = np.zeros_like(mask_parent)
|
||||
mask_line = cv2.fillPoly(mask_line, pts=[contour], color=1)
|
||||
mask_line = cv2.dilate(mask_line, KERNEL, iterations=5 if num_col == 0 else 4)
|
||||
mask_line *= mask_parent
|
||||
# plt.subplot(1, 2, 1, title="parent mask")
|
||||
# plt.imshow(mask_parent)
|
||||
# plt.subplot(1, 2, 2, title="single textline")
|
||||
|
|
|
|||
|
|
@ -3,36 +3,47 @@
|
|||
from pathlib import Path
|
||||
import os.path
|
||||
import logging
|
||||
from typing import Optional
|
||||
from typing import Optional, List, Tuple
|
||||
|
||||
import numpy as np
|
||||
import cv2
|
||||
from shapely import affinity, clip_by_rect
|
||||
|
||||
from ocrd_utils import points_from_polygon
|
||||
from ocrd_models.ocrd_page import (
|
||||
BorderType,
|
||||
CoordsType,
|
||||
TextLineType,
|
||||
TextEquivType,
|
||||
TextRegionType,
|
||||
ImageRegionType,
|
||||
TableRegionType,
|
||||
SeparatorRegionType,
|
||||
to_xml
|
||||
)
|
||||
AlternativeImageType,
|
||||
BorderType,
|
||||
CoordsType,
|
||||
TextLineType,
|
||||
TextEquivType,
|
||||
TextRegionType,
|
||||
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,254 +51,159 @@ 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)
|
||||
if img_alt := next(
|
||||
(img for img in pcgts.Page.AlternativeImage
|
||||
if img.comments == "binarized"
|
||||
and isinstance(img.filename, np.ndarray)), None):
|
||||
img_alt_filename = self.output_filename[:-4] + '.bin.png'
|
||||
cv2.imwrite(img_alt_filename, img_alt.filename)
|
||||
img_alt.filename = os.path.basename(img_alt_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,
|
||||
page: Region,
|
||||
img_bin: Optional[np.ndarray] = None,
|
||||
num_col: int = 1,
|
||||
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')
|
||||
|
||||
# create the file structure
|
||||
pcgts = self.pcgts if self.pcgts else create_page_xml(
|
||||
self.image_filename, self.image_height, self.image_width)
|
||||
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]))))
|
||||
|
||||
offset = [page_coord[2], page_coord[0]]
|
||||
if img_bin is not None:
|
||||
img_alt = AlternativeImageType(filename=img_bin, # will be replaced later
|
||||
comments="binarized")
|
||||
pcgts.Page.add_AlternativeImage(img_alt)
|
||||
pcgts.Page.set_custom('layout {num_col:%d;} ' % num_col)
|
||||
pcgts.Page.set_orientation(-page.skew)
|
||||
pcgts.Page.set_Border(BorderType(Coords=CoordsType(
|
||||
points=self.calculate_points(page.contour))))
|
||||
x, y, w, h = cv2.boundingRect(page.contour)
|
||||
offset = [x, y]
|
||||
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]
|
||||
xml_reading_order(page, order_of_texts, id_of_marginalia_left, id_of_marginalia_right)
|
||||
for _ in marginals_right]
|
||||
xml_reading_order(pcgts.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])
|
||||
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.serialize_lines_in_region(textregion, offset, counter, region.lines)
|
||||
pcgts.Page.add_TextRegion(textregion)
|
||||
|
||||
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])
|
||||
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)
|
||||
self.serialize_lines_in_region(textregion, offset, counter, region.lines)
|
||||
pcgts.Page.add_TextRegion(textregion)
|
||||
|
||||
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])
|
||||
pcgts.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)
|
||||
pcgts.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)
|
||||
pcgts.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])
|
||||
page.add_ImageRegion(image)
|
||||
Coords=CoordsType(points=self.calculate_points(region.contour, offset, 2),
|
||||
conf=region.conf))
|
||||
pcgts.Page.add_ImageRegion(image)
|
||||
|
||||
for region_contour in polygons_seplines:
|
||||
page.add_SeparatorRegion(
|
||||
SeparatorRegionType(id=counter.next_region_id,
|
||||
Coords=CoordsType(points=self.calculate_points(region_contour, offset))))
|
||||
for region in seplines:
|
||||
pcgts.Page.add_SeparatorRegion(
|
||||
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])
|
||||
page.add_TableRegion(table)
|
||||
Coords=CoordsType(points=self.calculate_points(region.contour, offset, 6),
|
||||
conf=region.conf))
|
||||
pcgts.Page.add_TableRegion(table)
|
||||
|
||||
return pcgts
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,30 @@
|
|||
import pytest
|
||||
from ocrd_modelfactory import page_from_file
|
||||
from ocrd_models.constants import NAMESPACES as NS
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"options",
|
||||
[
|
||||
[], # defaults
|
||||
["--model_based"],
|
||||
], ids=str)
|
||||
def test_run_eynollah_mbreorder_filename(
|
||||
tmp_path,
|
||||
resources_dir,
|
||||
run_eynollah_ok_and_check_logs,
|
||||
options,
|
||||
):
|
||||
infile = resources_dir / '2files/kant_aufklaerung_1784_0020.xml'
|
||||
outfile = tmp_path /'kant_aufklaerung_1784_0020.xml'
|
||||
run_eynollah_ok_and_check_logs(
|
||||
'machine-based-reading-order',
|
||||
'reorder',
|
||||
[
|
||||
'-i', str(infile),
|
||||
'-dim', str(resources_dir / '2files'),
|
||||
'-o', str(outfile.parent),
|
||||
],
|
||||
] + options,
|
||||
[
|
||||
# FIXME: mbreorder has no logging!
|
||||
str(infile)
|
||||
]
|
||||
)
|
||||
assert outfile.exists()
|
||||
|
|
@ -34,13 +43,15 @@ def test_run_eynollah_mbreorder_directory(
|
|||
):
|
||||
outdir = tmp_path
|
||||
run_eynollah_ok_and_check_logs(
|
||||
'machine-based-reading-order',
|
||||
'reorder',
|
||||
[
|
||||
'-di', str(resources_dir / '2files'),
|
||||
'-dim', str(resources_dir / '2files'),
|
||||
'-o', str(outdir),
|
||||
],
|
||||
[
|
||||
# FIXME: mbreorder has no logging!
|
||||
'Job done in',
|
||||
'All jobs done in',
|
||||
]
|
||||
)
|
||||
assert len(list(outdir.iterdir())) == 2
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ def eynollah_subcommands():
|
|||
'layout',
|
||||
'ocr',
|
||||
'enhancement',
|
||||
'machine-based-reading-order',
|
||||
'reorder',
|
||||
'models',
|
||||
]
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue