mirror of
https://github.com/qurator-spk/eynollah.git
synced 2026-08-09 04:12:42 +02:00
add extract-page CLI for cropping only
This commit is contained in:
parent
628e76cae7
commit
fa21cff3a9
3 changed files with 238 additions and 0 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
from .cli import main
|
from .cli import main
|
||||||
from .cli_binarize import binarize_cli
|
from .cli_binarize import binarize_cli
|
||||||
from .cli_enhance import enhance_cli
|
from .cli_enhance import enhance_cli
|
||||||
|
from .cli_extract_page import extract_page_cli
|
||||||
from .cli_extract_images import extract_images_cli
|
from .cli_extract_images import extract_images_cli
|
||||||
from .cli_layout import layout_cli
|
from .cli_layout import layout_cli
|
||||||
from .cli_models import models_cli
|
from .cli_models import models_cli
|
||||||
|
|
@ -13,4 +14,5 @@ main.add_command(layout_cli, 'layout')
|
||||||
main.add_command(readingorder_cli, 'reorder')
|
main.add_command(readingorder_cli, 'reorder')
|
||||||
main.add_command(models_cli, 'models')
|
main.add_command(models_cli, 'models')
|
||||||
main.add_command(ocr_cli, 'ocr')
|
main.add_command(ocr_cli, 'ocr')
|
||||||
|
main.add_command(extract_page_cli, 'extract-page')
|
||||||
main.add_command(extract_images_cli, 'extract-images')
|
main.add_command(extract_images_cli, 'extract-images')
|
||||||
|
|
|
||||||
80
src/eynollah/cli/cli_extract_page.py
Normal file
80
src/eynollah/cli/cli_extract_page.py
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
import click
|
||||||
|
|
||||||
|
@click.command(context_settings=dict(
|
||||||
|
help_option_names=['-h', '--help'],
|
||||||
|
show_default=True))
|
||||||
|
@click.option(
|
||||||
|
"--image",
|
||||||
|
"-i",
|
||||||
|
help="input image filename",
|
||||||
|
type=click.Path(exists=True, dir_okay=False),
|
||||||
|
)
|
||||||
|
|
||||||
|
@click.option(
|
||||||
|
"--out",
|
||||||
|
"-o",
|
||||||
|
help="directory for output PAGE-XML files",
|
||||||
|
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.option(
|
||||||
|
"--dir_in",
|
||||||
|
"-di",
|
||||||
|
help="directory of input images (instead of --image)",
|
||||||
|
type=click.Path(exists=True, file_okay=False),
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--input_binary",
|
||||||
|
"-ib",
|
||||||
|
is_flag=True,
|
||||||
|
help="In general, eynollah uses RGB as input, but if the input document is very dark, very bright or for any other reason you can turn on internal binarization here. When set, eynollah will binarize the RGB input document first.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--num_col_upper",
|
||||||
|
"-ncu",
|
||||||
|
default=0,
|
||||||
|
type=click.IntRange(min=0),
|
||||||
|
help="lower limit of columns in document image; 0 means autodetected from model",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--num_col_lower",
|
||||||
|
"-ncl",
|
||||||
|
default=0,
|
||||||
|
type=click.IntRange(min=0),
|
||||||
|
help="upper limit of columns in document image; 0 means autodetected from model",
|
||||||
|
)
|
||||||
|
@click.pass_context
|
||||||
|
def extract_page_cli(
|
||||||
|
ctx,
|
||||||
|
image,
|
||||||
|
out,
|
||||||
|
overwrite,
|
||||||
|
dir_in,
|
||||||
|
input_binary,
|
||||||
|
num_col_upper,
|
||||||
|
num_col_lower,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Detect image regions only
|
||||||
|
"""
|
||||||
|
assert bool(image) != bool(dir_in), "Either -i (single input) or -di (directory) must be provided, but not both."
|
||||||
|
|
||||||
|
from ..extract_page import EynollahPageExtractor
|
||||||
|
extractor = EynollahPageExtractor(
|
||||||
|
model_zoo=ctx.obj.model_zoo,
|
||||||
|
input_binary=input_binary,
|
||||||
|
num_col_upper=num_col_upper,
|
||||||
|
num_col_lower=num_col_lower,
|
||||||
|
)
|
||||||
|
extractor.run(overwrite=overwrite,
|
||||||
|
image_filename=image,
|
||||||
|
dir_in=dir_in,
|
||||||
|
dir_out=out,
|
||||||
|
)
|
||||||
|
|
||||||
156
src/eynollah/extract_page.py
Normal file
156
src/eynollah/extract_page.py
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
"""
|
||||||
|
extract page border (i.e. crop)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from concurrent.futures import ProcessPoolExecutor
|
||||||
|
import logging
|
||||||
|
from multiprocessing import cpu_count
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
from typing import Optional
|
||||||
|
from pathlib import Path
|
||||||
|
import numpy as np
|
||||||
|
import cv2
|
||||||
|
|
||||||
|
from eynollah.utils.contour import filter_contours_area_of_image, return_contours_of_image, return_contours_of_interested_region
|
||||||
|
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 EynollahPageExtractor(Eynollah):
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
model_zoo: EynollahModelZoo,
|
||||||
|
enable_plotting : bool = False,
|
||||||
|
input_binary : bool = False,
|
||||||
|
ignore_page_extraction : bool = False,
|
||||||
|
num_col_upper : Optional[int] = None,
|
||||||
|
num_col_lower : Optional[int] = None,
|
||||||
|
full_layout : bool = False,
|
||||||
|
tables : bool = False,
|
||||||
|
curved_line : bool = False,
|
||||||
|
allow_enhancement : bool = False,
|
||||||
|
|
||||||
|
):
|
||||||
|
self.logger = logging.getLogger('eynollah.extract_page')
|
||||||
|
self.model_zoo = model_zoo
|
||||||
|
self.plotter = None
|
||||||
|
self.tables = tables
|
||||||
|
self.curved_line = curved_line
|
||||||
|
self.allow_enhancement = allow_enhancement
|
||||||
|
|
||||||
|
self.enable_plotting = enable_plotting
|
||||||
|
# --input-binary sensible if image is very dark, if layout is not working.
|
||||||
|
self.input_binary = input_binary
|
||||||
|
self.full_layout = full_layout
|
||||||
|
self.ignore_page_extraction = ignore_page_extraction
|
||||||
|
if num_col_upper:
|
||||||
|
self.num_col_upper = int(num_col_upper)
|
||||||
|
else:
|
||||||
|
self.num_col_upper = num_col_upper
|
||||||
|
if num_col_lower:
|
||||||
|
self.num_col_lower = int(num_col_lower)
|
||||||
|
else:
|
||||||
|
self.num_col_lower = num_col_lower
|
||||||
|
|
||||||
|
# for parallelization of CPU-intensive tasks:
|
||||||
|
self.executor = ProcessPoolExecutor(max_workers=cpu_count())
|
||||||
|
|
||||||
|
t_start = time.time()
|
||||||
|
|
||||||
|
self.logger.info("Loading models...")
|
||||||
|
self.setup_models()
|
||||||
|
self.logger.info(f"Model initialization complete ({time.time() - t_start:.1f}s)")
|
||||||
|
|
||||||
|
def setup_models(self, device=''):
|
||||||
|
|
||||||
|
loadable = [
|
||||||
|
"col_classifier",
|
||||||
|
"page",
|
||||||
|
]
|
||||||
|
if self.input_binary:
|
||||||
|
loadable.append("binarization")
|
||||||
|
self.model_zoo.load_models(*loadable, device=device)
|
||||||
|
|
||||||
|
def run(self,
|
||||||
|
overwrite: bool = False,
|
||||||
|
image_filename: Optional[str] = None,
|
||||||
|
dir_in: Optional[str] = None,
|
||||||
|
dir_out: Optional[str] = None,
|
||||||
|
**kwargs
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Get scanned image and scales, then detect the page border
|
||||||
|
"""
|
||||||
|
self.logger.debug("enter run")
|
||||||
|
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))]
|
||||||
|
elif image_filename:
|
||||||
|
ls_imgs = [image_filename]
|
||||||
|
else:
|
||||||
|
raise ValueError("run requires either a single image filename or a directory")
|
||||||
|
|
||||||
|
for img_filename in ls_imgs:
|
||||||
|
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,
|
||||||
|
img_filename: str,
|
||||||
|
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)
|
||||||
|
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']
|
||||||
|
|
||||||
|
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, _, _ = self.extract_page(image)
|
||||||
|
page = Region(page_cont)
|
||||||
|
|
||||||
|
pcgts = writer.build_pagexml(
|
||||||
|
page=page,
|
||||||
|
img_bin=self.imread(image, binary=True) if self.input_binary else None,
|
||||||
|
num_col=num_col_classifier,
|
||||||
|
)
|
||||||
|
writer.write_pagexml(pcgts)
|
||||||
|
self.logger.info("Job done in %.1fs", time.time() - t0)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue