mirror of
https://github.com/qurator-spk/eynollah.git
synced 2025-06-15 15:19:55 +02:00
Merge pull request #148 from bertsky/v3-api
fix, merge, resolve conflicts, apply review, migrate sbb-binarize
This commit is contained in:
commit
85566c2186
25 changed files with 6071 additions and 3432 deletions
6
.dockerignore
Normal file
6
.dockerignore
Normal file
|
@ -0,0 +1,6 @@
|
|||
tests
|
||||
dist
|
||||
build
|
||||
env*
|
||||
*.egg-info
|
||||
models_eynollah*
|
44
.github/workflows/build-docker.yml
vendored
Normal file
44
.github/workflows/build-docker.yml
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
name: CD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
workflow_dispatch: # run manually
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# we need tags for docker version tagging
|
||||
fetch-tags: true
|
||||
fetch-depth: 0
|
||||
- # Activate cache export feature to reduce build time of images
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERIO_USERNAME }}
|
||||
password: ${{ secrets.DOCKERIO_PASSWORD }}
|
||||
- name: Build the Docker image
|
||||
# build both tags at the same time
|
||||
run: make docker DOCKER_TAG="docker.io/ocrd/eynollah -t ghcr.io/qurator-spk/eynollah"
|
||||
- name: Test the Docker image
|
||||
run: docker run --rm ocrd/eynollah ocrd-eynollah-segment -h
|
||||
- name: Push to Dockerhub
|
||||
run: docker push docker.io/ocrd/eynollah
|
||||
- name: Push to Github Container Registry
|
||||
run: docker push ghcr.io/qurator-spk/eynollah
|
28
.github/workflows/test-eynollah.yml
vendored
28
.github/workflows/test-eynollah.yml
vendored
|
@ -14,14 +14,28 @@ jobs:
|
|||
python-version: ['3.8', '3.9', '3.10', '3.11']
|
||||
|
||||
steps:
|
||||
- name: clean up
|
||||
run: |
|
||||
df -h
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
sudo rm -rf /usr/local/lib/android
|
||||
sudo rm -rf /opt/ghc
|
||||
sudo rm -rf "/usr/local/share/boost"
|
||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||||
df -h
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
id: model_cache
|
||||
id: seg_model_cache
|
||||
with:
|
||||
path: models_eynollah
|
||||
key: ${{ runner.os }}-models
|
||||
- uses: actions/cache@v4
|
||||
id: bin_model_cache
|
||||
with:
|
||||
path: default-2021-03-09
|
||||
key: ${{ runner.os }}-modelbin
|
||||
- name: Download models
|
||||
if: steps.model_cache.outputs.cache-hit != 'true'
|
||||
if: steps.seg_model_cache.outputs.cache-hit != 'true' || steps.bin_model_cache.outputs.cache-hit != 'true'
|
||||
run: make models
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
|
@ -30,9 +44,11 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install .
|
||||
pip install -r requirements-test.txt
|
||||
make install EXTRAS=OCR,plotting
|
||||
make deps-test
|
||||
- name: Test with pytest
|
||||
run: make test
|
||||
- name: Test docker build
|
||||
run: make docker
|
||||
- name: Test standalone CLI
|
||||
run: make smoke-test
|
||||
- name: Test OCR-D CLI
|
||||
run: make ocrd-test
|
||||
|
|
41
Dockerfile
41
Dockerfile
|
@ -4,23 +4,42 @@ FROM $DOCKER_BASE_IMAGE
|
|||
ARG VCS_REF
|
||||
ARG BUILD_DATE
|
||||
LABEL \
|
||||
maintainer="https://ocr-d.de/kontakt" \
|
||||
maintainer="https://ocr-d.de/en/contact" \
|
||||
org.label-schema.vcs-ref=$VCS_REF \
|
||||
org.label-schema.vcs-url="https://github.com/qurator-spk/eynollah" \
|
||||
org.label-schema.build-date=$BUILD_DATE
|
||||
org.label-schema.build-date=$BUILD_DATE \
|
||||
org.opencontainers.image.vendor="DFG-Funded Initiative for Optical Character Recognition Development" \
|
||||
org.opencontainers.image.title="Eynollah" \
|
||||
org.opencontainers.image.description="" \
|
||||
org.opencontainers.image.source="https://github.com/qurator-spk/eynollah" \
|
||||
org.opencontainers.image.documentation="https://github.com/qurator-spk/eynollah/blob/${VCS_REF}/README.md" \
|
||||
org.opencontainers.image.revision=$VCS_REF \
|
||||
org.opencontainers.image.created=$BUILD_DATE \
|
||||
org.opencontainers.image.base.name=ocrd/core-cuda-tf2
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
# set proper locales
|
||||
ENV PYTHONIOENCODING=utf8
|
||||
ENV XDG_DATA_HOME=/usr/local/share
|
||||
ENV LANG=C.UTF-8
|
||||
ENV LC_ALL=C.UTF-8
|
||||
|
||||
WORKDIR /build-eynollah
|
||||
COPY src/ ./src
|
||||
COPY pyproject.toml .
|
||||
COPY requirements.txt .
|
||||
COPY README.md .
|
||||
COPY Makefile .
|
||||
RUN apt-get install -y --no-install-recommends g++
|
||||
RUN make install
|
||||
# avoid HOME/.local/share (hard to predict USER here)
|
||||
# so let XDG_DATA_HOME coincide with fixed system location
|
||||
# (can still be overridden by derived stages)
|
||||
ENV XDG_DATA_HOME /usr/local/share
|
||||
# avoid the need for an extra volume for persistent resource user db
|
||||
# (i.e. XDG_CONFIG_HOME/ocrd/resources.yml)
|
||||
ENV XDG_CONFIG_HOME /usr/local/share/ocrd-resources
|
||||
|
||||
WORKDIR /build/eynollah
|
||||
COPY . .
|
||||
COPY ocrd-tool.json .
|
||||
# prepackage ocrd-tool.json as ocrd-all-tool.json
|
||||
RUN ocrd ocrd-tool ocrd-tool.json dump-tools > $(dirname $(ocrd bashlib filename))/ocrd-all-tool.json
|
||||
# install everything and reduce image size
|
||||
RUN make install EXTRAS=OCR && rm -rf /build/eynollah
|
||||
# smoke test
|
||||
RUN eynollah --help
|
||||
|
||||
WORKDIR /data
|
||||
VOLUME /data
|
||||
|
|
91
Makefile
91
Makefile
|
@ -1,10 +1,20 @@
|
|||
EYNOLLAH_MODELS ?= $(PWD)/models_eynollah
|
||||
export EYNOLLAH_MODELS
|
||||
PYTHON ?= python3
|
||||
PIP ?= pip3
|
||||
EXTRAS ?=
|
||||
|
||||
# DOCKER_BASE_IMAGE = artefakt.dev.sbb.berlin:5000/sbb/ocrd_core:v2.68.0
|
||||
DOCKER_BASE_IMAGE = docker.io/ocrd/core:v2.68.0
|
||||
DOCKER_BASE_IMAGE = docker.io/ocrd/core-cuda-tf2:v3.3.0
|
||||
DOCKER_TAG = ocrd/eynollah
|
||||
|
||||
#SEG_MODEL := https://qurator-data.de/eynollah/2021-04-25/models_eynollah.tar.gz
|
||||
#SEG_MODEL := https://qurator-data.de/eynollah/2022-04-05/models_eynollah_renamed.tar.gz
|
||||
SEG_MODEL := https://qurator-data.de/eynollah/2022-04-05/models_eynollah.tar.gz
|
||||
#SEG_MODEL := https://github.com/qurator-spk/eynollah/releases/download/v0.3.0/models_eynollah.tar.gz
|
||||
#SEG_MODEL := https://github.com/qurator-spk/eynollah/releases/download/v0.3.1/models_eynollah.tar.gz
|
||||
|
||||
BIN_MODEL := https://github.com/qurator-spk/sbb_binarization/releases/download/v0.0.11/saved_model_2021_03_09.zip
|
||||
|
||||
PYTEST_ARGS ?=
|
||||
|
||||
# BEGIN-EVAL makefile-parser --make-help Makefile
|
||||
|
||||
|
@ -12,44 +22,90 @@ help:
|
|||
@echo ""
|
||||
@echo " Targets"
|
||||
@echo ""
|
||||
@echo " models Download and extract models to $(PWD)/models_eynollah"
|
||||
@echo " install Install with pip"
|
||||
@echo " docker Build Docker image"
|
||||
@echo " build Build Python source and binary distribution"
|
||||
@echo " install Install package with pip"
|
||||
@echo " install-dev Install editable with pip"
|
||||
@echo " deps-test Install test dependencies with pip"
|
||||
@echo " models Download and extract models to $(CURDIR)/models_eynollah"
|
||||
@echo " smoke-test Run simple CLI check"
|
||||
@echo " ocrd-test Run OCR-D CLI check"
|
||||
@echo " test Run unit tests"
|
||||
@echo ""
|
||||
@echo " Variables"
|
||||
@echo " EXTRAS comma-separated list of features (like 'OCR,plotting') for 'install' [$(EXTRAS)]"
|
||||
@echo " DOCKER_TAG Docker image tag for 'docker' [$(DOCKER_TAG)]"
|
||||
@echo " PYTEST_ARGS pytest args for 'test' (Set to '-s' to see log output during test execution, '-vv' to see individual tests. [$(PYTEST_ARGS)]"
|
||||
@echo " SEG_MODEL URL of 'models' archive to download for segmentation 'test' [$(SEG_MODEL)]"
|
||||
@echo " BIN_MODEL URL of 'models' archive to download for binarization 'test' [$(BIN_MODEL)]"
|
||||
@echo ""
|
||||
|
||||
# END-EVAL
|
||||
|
||||
|
||||
# Download and extract models to $(PWD)/models_eynollah
|
||||
models: models_eynollah
|
||||
models: models_eynollah default-2021-03-09
|
||||
|
||||
models_eynollah: models_eynollah.tar.gz
|
||||
tar xf models_eynollah.tar.gz
|
||||
tar zxf models_eynollah.tar.gz
|
||||
|
||||
models_eynollah.tar.gz:
|
||||
# wget 'https://qurator-data.de/eynollah/2021-04-25/models_eynollah.tar.gz'
|
||||
# wget 'https://qurator-data.de/eynollah/2022-04-05/models_eynollah_renamed.tar.gz'
|
||||
# wget 'https://qurator-data.de/eynollah/2022-04-05/models_eynollah_renamed_savedmodel.tar.gz'
|
||||
# wget 'https://github.com/qurator-spk/eynollah/releases/download/v0.3.0/models_eynollah.tar.gz'
|
||||
wget 'https://github.com/qurator-spk/eynollah/releases/download/v0.3.1/models_eynollah.tar.gz'
|
||||
wget $(SEG_MODEL)
|
||||
|
||||
default-2021-03-09: $(notdir $(BIN_MODEL))
|
||||
unzip $(notdir $(BIN_MODEL))
|
||||
mkdir $@
|
||||
mv $(basename $(notdir $(BIN_MODEL))) $@
|
||||
|
||||
$(notdir $(BIN_MODEL)):
|
||||
wget $(BIN_MODEL)
|
||||
|
||||
build:
|
||||
$(PIP) install build
|
||||
$(PYTHON) -m build .
|
||||
|
||||
# Install with pip
|
||||
install:
|
||||
pip install .
|
||||
$(PIP) install .$(and $(EXTRAS),[$(EXTRAS)])
|
||||
|
||||
# Install editable with pip
|
||||
install-dev:
|
||||
pip install -e .
|
||||
$(PIP) install -e .$(and $(EXTRAS),[$(EXTRAS)])
|
||||
|
||||
smoke-test:
|
||||
eynollah -i tests/resources/kant_aufklaerung_1784_0020.tif -o . -m $(PWD)/models_eynollah
|
||||
deps-test: models_eynollah
|
||||
$(PIP) install -r requirements-test.txt
|
||||
|
||||
smoke-test: TMPDIR != mktemp -d
|
||||
smoke-test: tests/resources/kant_aufklaerung_1784_0020.tif
|
||||
# layout analysis:
|
||||
eynollah layout -i $< -o $(TMPDIR) -m $(CURDIR)/models_eynollah
|
||||
fgrep -q http://schema.primaresearch.org/PAGE/gts/pagecontent/2019-07-15 $(TMPDIR)/$(basename $(<F)).xml
|
||||
fgrep -c -e TextRegion -e ImageRegion -e SeparatorRegion $(TMPDIR)/$(basename $(<F)).xml
|
||||
# directory mode (skip one, add one):
|
||||
eynollah layout -di $(<D) -o $(TMPDIR) -m $(CURDIR)/models_eynollah
|
||||
test -s $(TMPDIR)/euler_rechenkunst01_1738_0025.xml
|
||||
# binarize:
|
||||
eynollah binarization -m $(CURDIR)/default-2021-03-09 $< $(TMPDIR)/$(<F)
|
||||
test -s $(TMPDIR)/$(<F)
|
||||
@set -x; test "$$(identify -format '%w %h' $<)" = "$$(identify -format '%w %h' $(TMPDIR)/$(<F))"
|
||||
$(RM) -r $(TMPDIR)
|
||||
|
||||
ocrd-test: TMPDIR != mktemp -d
|
||||
ocrd-test: tests/resources/kant_aufklaerung_1784_0020.tif
|
||||
cp $< $(TMPDIR)
|
||||
ocrd workspace -d $(TMPDIR) init
|
||||
ocrd workspace -d $(TMPDIR) add -G OCR-D-IMG -g PHYS_0020 -i OCR-D-IMG_0020 $(<F)
|
||||
ocrd-eynollah-segment -w $(TMPDIR) -I OCR-D-IMG -O OCR-D-SEG -P models $(CURDIR)/models_eynollah
|
||||
result=$$(ocrd workspace -d $(TMPDIR) find -G OCR-D-SEG); \
|
||||
fgrep -q http://schema.primaresearch.org/PAGE/gts/pagecontent/2019-07-15 $(TMPDIR)/$$result && \
|
||||
fgrep -c -e TextRegion -e ImageRegion -e SeparatorRegion $(TMPDIR)/$$result
|
||||
ocrd-sbb-binarize -w $(TMPDIR) -I OCR-D-IMG -O OCR-D-BIN -P model $(CURDIR)/default-2021-03-09
|
||||
ocrd-sbb-binarize -w $(TMPDIR) -I OCR-D-SEG -O OCR-D-SEG-BIN -P model $(CURDIR)/default-2021-03-09 -P operation_level region
|
||||
$(RM) -r $(TMPDIR)
|
||||
|
||||
# Run unit tests
|
||||
test:
|
||||
pytest tests
|
||||
EYNOLLAH_MODELS=$(CURDIR)/models_eynollah $(PYTHON) -m pytest tests --durations=0 --continue-on-collection-errors $(PYTEST_ARGS)
|
||||
|
||||
# Build docker image
|
||||
docker:
|
||||
|
@ -59,3 +115,4 @@ docker:
|
|||
--build-arg BUILD_DATE=$$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
|
||||
-t $(DOCKER_TAG) .
|
||||
|
||||
.PHONY: models build install install-dev test smoke-test ocrd-test docker help
|
||||
|
|
29
README.md
29
README.md
|
@ -83,23 +83,28 @@ If no option is set, the tool performs layout detection of main regions (backgro
|
|||
The best output quality is produced when RGB images are used as input rather than greyscale or binarized images.
|
||||
|
||||
#### Use as OCR-D processor
|
||||
🚧 **Work in progress**
|
||||
|
||||
Eynollah ships with a CLI interface to be used as [OCR-D](https://ocr-d.de) processor.
|
||||
Eynollah ships with a CLI interface to be used as [OCR-D](https://ocr-d.de) [processor](https://ocr-d.de/en/spec/cli).
|
||||
|
||||
In this case, the source image file group with (preferably) RGB images should be used as input like this:
|
||||
|
||||
```
|
||||
ocrd-eynollah-segment -I OCR-D-IMG -O SEG-LINE -P models
|
||||
```
|
||||
|
||||
Any image referenced by `@imageFilename` in PAGE-XML is passed on directly to Eynollah as a processor, so that e.g.
|
||||
ocrd-eynollah-segment -I OCR-D-IMG -O OCR-D-SEG -P models 2022-04-05
|
||||
|
||||
```
|
||||
ocrd-eynollah-segment -I OCR-D-IMG-BIN -O SEG-LINE -P models
|
||||
```
|
||||
|
||||
uses the original (RGB) image despite any binarization that may have occured in previous OCR-D processing steps
|
||||
|
||||
If the input file group is PAGE-XML (from a previous OCR-D workflow step), Eynollah behaves as follows:
|
||||
- existing regions are kept and ignored (i.e. in effect they might overlap segments from Eynollah results)
|
||||
- existing annotation (and respective `AlternativeImage`s) are partially _ignored_:
|
||||
- previous page frame detection (`cropped` images)
|
||||
- previous derotation (`deskewed` images)
|
||||
- previous thresholding (`binarized` images)
|
||||
- if the page-level image nevertheless deviates from the original (`@imageFilename`)
|
||||
(because some other preprocessing step was in effect like `denoised`), then
|
||||
the output PAGE-XML will be based on that as new top-level (`@imageFilename`)
|
||||
|
||||
|
||||
ocrd-eynollah-segment -I OCR-D-XYZ -O OCR-D-SEG -P models 2022-04-05
|
||||
|
||||
Still, in general, it makes more sense to add other workflow steps **after** Eynollah.
|
||||
|
||||
#### Additional documentation
|
||||
Please check the [wiki](https://github.com/qurator-spk/eynollah/wiki).
|
||||
|
|
|
@ -25,9 +25,14 @@ classifiers = [
|
|||
"Topic :: Scientific/Engineering :: Image Processing",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
OCR = ["torch <= 2.0.1", "transformers <= 4.30.2"]
|
||||
plotting = ["matplotlib"]
|
||||
|
||||
[project.scripts]
|
||||
eynollah = "eynollah.cli:main"
|
||||
ocrd-eynollah-segment = "eynollah.ocrd_cli:main"
|
||||
ocrd-sbb-binarize = "eynollah.ocrd_cli_binarization:main"
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/qurator-spk/eynollah"
|
||||
|
@ -35,6 +40,7 @@ Repository = "https://github.com/qurator-spk/eynollah.git"
|
|||
|
||||
[tool.setuptools.dynamic]
|
||||
dependencies = {file = ["requirements.txt"]}
|
||||
optional-dependencies.test = {file = ["requirements-test.txt"]}
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# ocrd includes opencv, numpy, shapely, click
|
||||
ocrd >= 3.0.0b4
|
||||
ocrd >= 3.3.0
|
||||
numpy <1.24.0
|
||||
scikit-learn >= 0.23.2
|
||||
tensorflow < 2.13
|
||||
imutils >= 0.5.3
|
||||
matplotlib
|
||||
setuptools >= 50
|
||||
numba <= 0.58.1
|
||||
loky
|
||||
|
|
|
@ -1,16 +1,87 @@
|
|||
import sys
|
||||
import click
|
||||
from ocrd_utils import initLogging, setOverrideLogLevel, getLogger
|
||||
from eynollah.eynollah import Eynollah
|
||||
from ocrd_utils import initLogging, getLevelName, getLogger
|
||||
from eynollah.eynollah import Eynollah, Eynollah_ocr
|
||||
from eynollah.sbb_binarize import SbbBinarizer
|
||||
|
||||
@click.group()
|
||||
def main():
|
||||
pass
|
||||
|
||||
@main.command()
|
||||
@click.option(
|
||||
"--dir_xml",
|
||||
"-dx",
|
||||
help="directory of GT page-xml files",
|
||||
type=click.Path(exists=True, file_okay=False),
|
||||
)
|
||||
@click.option(
|
||||
"--dir_out_modal_image",
|
||||
"-domi",
|
||||
help="directory where ground truth images would be written",
|
||||
type=click.Path(exists=True, file_okay=False),
|
||||
)
|
||||
@click.option(
|
||||
"--dir_out_classes",
|
||||
"-docl",
|
||||
help="directory where ground truth classes would be written",
|
||||
type=click.Path(exists=True, file_okay=False),
|
||||
)
|
||||
@click.option(
|
||||
"--input_height",
|
||||
"-ih",
|
||||
help="input height",
|
||||
)
|
||||
@click.option(
|
||||
"--input_width",
|
||||
"-iw",
|
||||
help="input width",
|
||||
)
|
||||
@click.option(
|
||||
"--min_area_size",
|
||||
"-min",
|
||||
help="min area size of regions considered for reading order training.",
|
||||
)
|
||||
def machine_based_reading_order(dir_xml, dir_out_modal_image, dir_out_classes, input_height, input_width, min_area_size):
|
||||
xml_files_ind = os.listdir(dir_xml)
|
||||
|
||||
@main.command()
|
||||
@click.option('--patches/--no-patches', default=True, help='by enabling this parameter you let the model to see the image in patches.')
|
||||
@click.option('--model_dir', '-m', type=click.Path(exists=True, file_okay=False), required=True, help='directory containing models for prediction')
|
||||
@click.argument('input_image')
|
||||
@click.argument('output_image')
|
||||
@click.option(
|
||||
"--dir_in",
|
||||
"-di",
|
||||
help="directory of images",
|
||||
type=click.Path(exists=True, file_okay=False),
|
||||
)
|
||||
@click.option(
|
||||
"--dir_out",
|
||||
"-do",
|
||||
help="directory where the binarized images will be written",
|
||||
type=click.Path(exists=True, file_okay=False),
|
||||
)
|
||||
def binarization(patches, model_dir, input_image, output_image, dir_in, dir_out):
|
||||
if not dir_out and (dir_in):
|
||||
print("Error: You used -di but did not set -do")
|
||||
sys.exit(1)
|
||||
elif dir_out and not (dir_in):
|
||||
print("Error: You used -do to write out binarized images but have not set -di")
|
||||
sys.exit(1)
|
||||
SbbBinarizer(model_dir).run(image_path=input_image, use_patches=patches, save=output_image, dir_in=dir_in, dir_out=dir_out)
|
||||
|
||||
|
||||
@click.command()
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.option(
|
||||
"--image",
|
||||
"-i",
|
||||
help="image filename",
|
||||
type=click.Path(exists=True, dir_okay=False),
|
||||
)
|
||||
|
||||
@click.option(
|
||||
"--out",
|
||||
"-o",
|
||||
|
@ -18,6 +89,12 @@ from eynollah.eynollah import Eynollah
|
|||
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",
|
||||
|
@ -140,57 +217,76 @@ from eynollah.eynollah import Eynollah
|
|||
help="if this parameter set to true, this tool would ignore page extraction",
|
||||
)
|
||||
@click.option(
|
||||
"--log-level",
|
||||
"--reading_order_machine_based/--heuristic_reading_order",
|
||||
"-romb/-hro",
|
||||
is_flag=True,
|
||||
help="if this parameter set to true, this tool would apply machine based reading order detection",
|
||||
)
|
||||
@click.option(
|
||||
"--do_ocr",
|
||||
"-ocr/-noocr",
|
||||
is_flag=True,
|
||||
help="if this parameter set to true, this tool will try to do ocr",
|
||||
)
|
||||
@click.option(
|
||||
"--num_col_upper",
|
||||
"-ncu",
|
||||
help="lower limit of columns in document image",
|
||||
)
|
||||
@click.option(
|
||||
"--num_col_lower",
|
||||
"-ncl",
|
||||
help="upper limit of columns in document image",
|
||||
)
|
||||
@click.option(
|
||||
"--skip_layout_and_reading_order",
|
||||
"-slro/-noslro",
|
||||
is_flag=True,
|
||||
help="if this parameter set to true, this tool will ignore layout detection and reading order. It means that textline detection will be done within printspace and contours of textline will be written in xml output file.",
|
||||
)
|
||||
@click.option(
|
||||
"--log_level",
|
||||
"-l",
|
||||
type=click.Choice(['OFF', 'DEBUG', 'INFO', 'WARN', 'ERROR']),
|
||||
help="Override log level globally to this",
|
||||
)
|
||||
def main(
|
||||
image,
|
||||
out,
|
||||
dir_in,
|
||||
model,
|
||||
save_images,
|
||||
save_layout,
|
||||
save_deskewed,
|
||||
save_all,
|
||||
extract_only_images,
|
||||
save_page,
|
||||
enable_plotting,
|
||||
allow_enhancement,
|
||||
curved_line,
|
||||
textline_light,
|
||||
full_layout,
|
||||
tables,
|
||||
right2left,
|
||||
input_binary,
|
||||
allow_scaling,
|
||||
headers_off,
|
||||
light_version,
|
||||
ignore_page_extraction,
|
||||
log_level
|
||||
):
|
||||
if log_level:
|
||||
setOverrideLogLevel(log_level)
|
||||
|
||||
def layout(image, out, overwrite, dir_in, model, save_images, save_layout, save_deskewed, save_all, extract_only_images, save_page, enable_plotting, allow_enhancement, curved_line, textline_light, full_layout, tables, right2left, input_binary, allow_scaling, headers_off, light_version, reading_order_machine_based, do_ocr, num_col_upper, num_col_lower, skip_layout_and_reading_order, ignore_page_extraction, log_level):
|
||||
initLogging()
|
||||
if log_level:
|
||||
getLogger('eynollah').setLevel(getLevelName(log_level))
|
||||
if not enable_plotting and (save_layout or save_deskewed or save_all or save_page or save_images or allow_enhancement):
|
||||
print("Error: You used one of -sl, -sd, -sa, -sp, -si or -ae but did not enable plotting with -ep")
|
||||
sys.exit(1)
|
||||
raise ValueError("Plotting with -sl, -sd, -sa, -sp, -si or -ae also requires -ep")
|
||||
elif enable_plotting and not (save_layout or save_deskewed or save_all or save_page or save_images or allow_enhancement):
|
||||
print("Error: You used -ep to enable plotting but set none of -sl, -sd, -sa, -sp, -si or -ae")
|
||||
sys.exit(1)
|
||||
raise ValueError("Plotting with -ep also requires -sl, -sd, -sa, -sp, -si or -ae")
|
||||
if textline_light and not light_version:
|
||||
print('Error: You used -tll to enable light textline detection but -light is not enabled')
|
||||
sys.exit(1)
|
||||
if extract_only_images and (allow_enhancement or allow_scaling or light_version or curved_line or textline_light or full_layout or tables or right2left or headers_off) :
|
||||
print('Error: You used -eoi which can not be enabled alongside light_version -light or allow_scaling -as or allow_enhancement -ae or curved_line -cl or textline_light -tll or full_layout -fl or tables -tab or right2left -r2l or headers_off -ho')
|
||||
sys.exit(1)
|
||||
raise ValueError("Light textline detection with -tll also requires -light")
|
||||
if light_version and not textline_light:
|
||||
raise ValueError("Light version with -light also requires light textline detection -tll")
|
||||
if extract_only_images and allow_enhancement:
|
||||
raise ValueError("Image extraction with -eoi can not be enabled alongside allow_enhancement -ae")
|
||||
if extract_only_images and allow_scaling:
|
||||
raise ValueError("Image extraction with -eoi can not be enabled alongside allow_scaling -as")
|
||||
if extract_only_images and light_version:
|
||||
raise ValueError("Image extraction with -eoi can not be enabled alongside light_version -light")
|
||||
if extract_only_images and curved_line:
|
||||
raise ValueError("Image extraction with -eoi can not be enabled alongside curved_line -cl")
|
||||
if extract_only_images and textline_light:
|
||||
raise ValueError("Image extraction with -eoi can not be enabled alongside textline_light -tll")
|
||||
if extract_only_images and full_layout:
|
||||
raise ValueError("Image extraction with -eoi can not be enabled alongside full_layout -fl")
|
||||
if extract_only_images and tables:
|
||||
raise ValueError("Image extraction with -eoi can not be enabled alongside tables -tab")
|
||||
if extract_only_images and right2left:
|
||||
raise ValueError("Image extraction with -eoi can not be enabled alongside right2left -r2l")
|
||||
if extract_only_images and headers_off:
|
||||
raise ValueError("Image extraction with -eoi can not be enabled alongside headers_off -ho")
|
||||
if image is None and dir_in is None:
|
||||
raise ValueError("Either a single image -i or a dir_in -di is required")
|
||||
eynollah = Eynollah(
|
||||
model,
|
||||
getLogger('Eynollah'),
|
||||
image_filename=image,
|
||||
logger=getLogger('eynollah'),
|
||||
dir_out=out,
|
||||
dir_in=dir_in,
|
||||
dir_of_cropped_images=save_images,
|
||||
extract_only_images=extract_only_images,
|
||||
dir_of_layout=save_layout,
|
||||
|
@ -209,12 +305,84 @@ def main(
|
|||
headers_off=headers_off,
|
||||
light_version=light_version,
|
||||
ignore_page_extraction=ignore_page_extraction,
|
||||
reading_order_machine_based=reading_order_machine_based,
|
||||
do_ocr=do_ocr,
|
||||
num_col_upper=num_col_upper,
|
||||
num_col_lower=num_col_lower,
|
||||
skip_layout_and_reading_order=skip_layout_and_reading_order,
|
||||
)
|
||||
if dir_in:
|
||||
eynollah.run()
|
||||
eynollah.run(dir_in=dir_in, overwrite=overwrite)
|
||||
else:
|
||||
pcgts = eynollah.run()
|
||||
eynollah.writer.write_pagexml(pcgts)
|
||||
eynollah.run(image_filename=image, overwrite=overwrite)
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.option(
|
||||
"--dir_in",
|
||||
"-di",
|
||||
help="directory of images",
|
||||
type=click.Path(exists=True, file_okay=False),
|
||||
)
|
||||
@click.option(
|
||||
"--out",
|
||||
"-o",
|
||||
help="directory to write output xml data",
|
||||
type=click.Path(exists=True, file_okay=False),
|
||||
required=True,
|
||||
)
|
||||
@click.option(
|
||||
"--dir_xmls",
|
||||
"-dx",
|
||||
help="directory of xmls",
|
||||
type=click.Path(exists=True, file_okay=False),
|
||||
)
|
||||
@click.option(
|
||||
"--model",
|
||||
"-m",
|
||||
help="directory of models",
|
||||
type=click.Path(exists=True, file_okay=False),
|
||||
required=True,
|
||||
)
|
||||
@click.option(
|
||||
"--tr_ocr",
|
||||
"-trocr/-notrocr",
|
||||
is_flag=True,
|
||||
help="if this parameter set to true, transformer ocr will be applied, otherwise cnn_rnn model.",
|
||||
)
|
||||
@click.option(
|
||||
"--export_textline_images_and_text",
|
||||
"-etit/-noetit",
|
||||
is_flag=True,
|
||||
help="if this parameter set to true, images and text in xml will be exported into output dir. This files can be used for training a OCR engine.",
|
||||
)
|
||||
@click.option(
|
||||
"--do_not_mask_with_textline_contour",
|
||||
"-nmtc/-mtc",
|
||||
is_flag=True,
|
||||
help="if this parameter set to true, cropped textline images will not be masked with textline contour.",
|
||||
)
|
||||
@click.option(
|
||||
"--log_level",
|
||||
"-l",
|
||||
type=click.Choice(['OFF', 'DEBUG', 'INFO', 'WARN', 'ERROR']),
|
||||
help="Override log level globally to this",
|
||||
)
|
||||
|
||||
def ocr(dir_in, out, dir_xmls, model, tr_ocr, export_textline_images_and_text, do_not_mask_with_textline_contour, log_level):
|
||||
initLogging()
|
||||
if log_level:
|
||||
getLogger('eynollah').setLevel(getLevelName(log_level))
|
||||
eynollah_ocr = Eynollah_ocr(
|
||||
dir_xmls=dir_xmls,
|
||||
dir_in=dir_in,
|
||||
dir_out=out,
|
||||
dir_models=model,
|
||||
tr_ocr=tr_ocr,
|
||||
export_textline_images_and_text=export_textline_images_and_text,
|
||||
do_not_mask_with_textline_contour=do_not_mask_with_textline_contour,
|
||||
)
|
||||
eynollah_ocr.run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"version": "0.3.1",
|
||||
"git_url": "https://github.com/qurator-spk/eynollah",
|
||||
"dockerhub": "ocrd/eynollah",
|
||||
"tools": {
|
||||
"ocrd-eynollah-segment": {
|
||||
"executable": "ocrd-eynollah-segment",
|
||||
|
@ -12,10 +13,10 @@
|
|||
"parameters": {
|
||||
"models": {
|
||||
"type": "string",
|
||||
"format": "file",
|
||||
"format": "uri",
|
||||
"content-type": "text/directory",
|
||||
"cacheable": true,
|
||||
"description": "Path to directory containing models to be used (See https://qurator-data.de/eynollah)",
|
||||
"description": "Directory containing models to be used (See https://qurator-data.de/eynollah)",
|
||||
"required": true
|
||||
},
|
||||
"dpi": {
|
||||
|
@ -29,11 +30,21 @@
|
|||
"default": true,
|
||||
"description": "Try to detect all element subtypes, including drop-caps and headings"
|
||||
},
|
||||
"tables": {
|
||||
"light_version": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Try to detect table regions"
|
||||
"default": true,
|
||||
"description": "Try to detect all element subtypes in light version (faster+simpler method for main region detection and deskewing)"
|
||||
},
|
||||
"textline_light": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Light version need textline light"
|
||||
},
|
||||
"tables": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Try to detect table regions"
|
||||
},
|
||||
"curved_line": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
|
@ -54,11 +65,6 @@
|
|||
"default": false,
|
||||
"description": "if this parameter set to true, this tool would check that input image need resizing and enhancement or not."
|
||||
},
|
||||
"light_mode": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "lighter and faster but simpler method for main region detection and deskewing"
|
||||
},
|
||||
"textline_light": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
|
@ -85,6 +91,47 @@
|
|||
"path_in_archive": "models_eynollah"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ocrd-sbb-binarize": {
|
||||
"executable": "ocrd-sbb-binarize",
|
||||
"description": "Pixelwise binarization with selectional auto-encoders in Keras",
|
||||
"categories": ["Image preprocessing"],
|
||||
"steps": ["preprocessing/optimization/binarization"],
|
||||
"input_file_grp_cardinality": 1,
|
||||
"output_file_grp_cardinality": 1,
|
||||
"parameters": {
|
||||
"operation_level": {
|
||||
"type": "string",
|
||||
"enum": ["page", "region"],
|
||||
"default": "page",
|
||||
"description": "PAGE XML hierarchy level to operate on"
|
||||
},
|
||||
"model": {
|
||||
"description": "Directory containing HDF5 or SavedModel/ProtoBuf models. Can be an absolute path or a path relative to the OCR-D resource location, the current working directory or the $SBB_BINARIZE_DATA environment variable (if set)",
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"content-type": "text/directory",
|
||||
"required": true
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"url": "https://github.com/qurator-spk/sbb_binarization/releases/download/v0.0.11/saved_model_2020_01_16.zip",
|
||||
"name": "default",
|
||||
"type": "archive",
|
||||
"path_in_archive": "saved_model_2020_01_16",
|
||||
"size": 563147331,
|
||||
"description": "default models provided by github.com/qurator-spk (SavedModel format)"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/qurator-spk/sbb_binarization/releases/download/v0.0.11/saved_model_2021_03_09.zip",
|
||||
"name": "default-2021-03-09",
|
||||
"type": "archive",
|
||||
"path_in_archive": ".",
|
||||
"size": 133230419,
|
||||
"description": "updated default models provided by github.com/qurator-spk (SavedModel format)"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
109
src/eynollah/ocrd_cli_binarization.py
Normal file
109
src/eynollah/ocrd_cli_binarization.py
Normal file
|
@ -0,0 +1,109 @@
|
|||
from typing import Optional
|
||||
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
import cv2
|
||||
from click import command
|
||||
|
||||
from ocrd import Processor, OcrdPageResult, OcrdPageResultImage
|
||||
from ocrd_models.ocrd_page import OcrdPage, AlternativeImageType
|
||||
from ocrd.decorators import ocrd_cli_options, ocrd_cli_wrap_processor
|
||||
|
||||
from .sbb_binarize import SbbBinarizer
|
||||
|
||||
|
||||
def cv2pil(img):
|
||||
return Image.fromarray(img.astype('uint8'))
|
||||
|
||||
def pil2cv(img):
|
||||
# from ocrd/workspace.py
|
||||
color_conversion = cv2.COLOR_GRAY2BGR if img.mode in ('1', 'L') else cv2.COLOR_RGB2BGR
|
||||
pil_as_np_array = np.array(img).astype('uint8') if img.mode == '1' else np.array(img)
|
||||
return cv2.cvtColor(pil_as_np_array, color_conversion)
|
||||
|
||||
class SbbBinarizeProcessor(Processor):
|
||||
# already employs GPU (without singleton process atm)
|
||||
max_workers = 1
|
||||
|
||||
@property
|
||||
def executable(self):
|
||||
return 'ocrd-sbb-binarize'
|
||||
|
||||
def setup(self):
|
||||
"""
|
||||
Set up the model prior to processing.
|
||||
"""
|
||||
# resolve relative path via OCR-D ResourceManager
|
||||
model_path = self.resolve_resource(self.parameter['model'])
|
||||
self.binarizer = SbbBinarizer(model_dir=model_path, logger=self.logger)
|
||||
|
||||
def process_page_pcgts(self, *input_pcgts: Optional[OcrdPage], page_id: Optional[str] = None) -> OcrdPageResult:
|
||||
"""
|
||||
Binarize images with sbb_binarization (based on selectional auto-encoders).
|
||||
|
||||
For each page of the input file group, open and deserialize input PAGE-XML
|
||||
and its respective images. Then iterate over the element hierarchy down to
|
||||
the requested ``operation_level``.
|
||||
|
||||
For each segment element, retrieve a raw (non-binarized) segment image
|
||||
according to the layout annotation (from an existing ``AlternativeImage``,
|
||||
or by cropping into the higher-level images, and deskewing when applicable).
|
||||
|
||||
Pass the image to the binarizer (which runs in fixed-size windows/patches
|
||||
across the image and stitches the results together).
|
||||
|
||||
Serialize the resulting bilevel image as PNG file and add it to the output
|
||||
file group (with file ID suffix ``.IMG-BIN``) along with the output PAGE-XML
|
||||
(referencing it as new ``AlternativeImage`` for the segment element).
|
||||
|
||||
Produce a new PAGE output file by serialising the resulting hierarchy.
|
||||
"""
|
||||
assert input_pcgts
|
||||
assert input_pcgts[0]
|
||||
assert self.parameter
|
||||
oplevel = self.parameter['operation_level']
|
||||
pcgts = input_pcgts[0]
|
||||
result = OcrdPageResult(pcgts)
|
||||
page = pcgts.get_Page()
|
||||
page_image, page_xywh, _ = self.workspace.image_from_page(
|
||||
page, page_id, feature_filter='binarized')
|
||||
|
||||
if oplevel == 'page':
|
||||
self.logger.info("Binarizing on 'page' level in page '%s'", page_id)
|
||||
page_image_bin = cv2pil(self.binarizer.run(image=pil2cv(page_image), use_patches=True))
|
||||
# update PAGE (reference the image file):
|
||||
page_image_ref = AlternativeImageType(comments=page_xywh['features'] + ',binarized,clipped')
|
||||
page.add_AlternativeImage(page_image_ref)
|
||||
result.images.append(OcrdPageResultImage(page_image_bin, '.IMG-BIN', page_image_ref))
|
||||
|
||||
elif oplevel == 'region':
|
||||
regions = page.get_AllRegions(['Text', 'Table'], depth=1)
|
||||
if not regions:
|
||||
self.logger.warning("Page '%s' contains no text/table regions", page_id)
|
||||
for region in regions:
|
||||
region_image, region_xywh = self.workspace.image_from_segment(
|
||||
region, page_image, page_xywh, feature_filter='binarized')
|
||||
region_image_bin = cv2pil(self.binarizer.run(image=pil2cv(region_image), use_patches=True))
|
||||
# update PAGE (reference the image file):
|
||||
region_image_ref = AlternativeImageType(comments=region_xywh['features'] + ',binarized')
|
||||
region.add_AlternativeImage(region_image_ref)
|
||||
result.images.append(OcrdPageResultImage(region_image_bin, region.id + '.IMG-BIN', region_image_ref))
|
||||
|
||||
elif oplevel == 'line':
|
||||
lines = page.get_AllTextLines()
|
||||
if not lines:
|
||||
self.logger.warning("Page '%s' contains no text lines", page_id)
|
||||
for line in lines:
|
||||
line_image, line_xywh = self.workspace.image_from_segment(line, page_image, page_xywh, feature_filter='binarized')
|
||||
line_image_bin = cv2pil(self.binarizer.run(image=pil2cv(line_image), use_patches=True))
|
||||
# update PAGE (reference the image file):
|
||||
line_image_ref = AlternativeImageType(comments=line_xywh['features'] + ',binarized')
|
||||
line.add_AlternativeImage(region_image_ref)
|
||||
result.images.append(OcrdPageResultImage(line_image_bin, line.id + '.IMG-BIN', line_image_ref))
|
||||
|
||||
return result
|
||||
|
||||
@command()
|
||||
@ocrd_cli_options
|
||||
def main(*args, **kwargs):
|
||||
return ocrd_cli_wrap_processor(SbbBinarizeProcessor, *args, **kwargs)
|
|
@ -1,5 +1,8 @@
|
|||
import matplotlib.pyplot as plt
|
||||
import matplotlib.patches as mpatches
|
||||
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
|
||||
|
|
|
@ -2,54 +2,90 @@ from typing import Optional
|
|||
from ocrd_models import OcrdPage
|
||||
from ocrd import Processor, OcrdPageResult
|
||||
|
||||
from .eynollah import Eynollah
|
||||
from .eynollah import Eynollah, EynollahXmlWriter
|
||||
|
||||
class EynollahProcessor(Processor):
|
||||
# already employs background CPU multiprocessing per page
|
||||
# already employs GPU (without singleton process atm)
|
||||
max_workers = 1
|
||||
|
||||
@property
|
||||
def executable(self):
|
||||
return 'ocrd-eynollah-segment'
|
||||
|
||||
def setup(self) -> None:
|
||||
# for caching models
|
||||
self.models = None
|
||||
if self.parameter['textline_light'] and not self.parameter['light_mode']:
|
||||
raise ValueError("Error: You set parameter 'textline_light' to enable light textline detection but parameter 'light_mode' is not enabled")
|
||||
if self.parameter['textline_light'] and not self.parameter['light_version']:
|
||||
raise ValueError("Error: You set parameter 'textline_light' to enable light textline detection, "
|
||||
"but parameter 'light_version' is not enabled")
|
||||
self.eynollah = Eynollah(
|
||||
self.resolve_resource(self.parameter['models']),
|
||||
logger=self.logger,
|
||||
allow_enhancement=self.parameter['allow_enhancement'],
|
||||
curved_line=self.parameter['curved_line'],
|
||||
right2left=self.parameter['right_to_left'],
|
||||
ignore_page_extraction=self.parameter['ignore_page_extraction'],
|
||||
light_version=self.parameter['light_version'],
|
||||
textline_light=self.parameter['textline_light'],
|
||||
full_layout=self.parameter['full_layout'],
|
||||
allow_scaling=self.parameter['allow_scaling'],
|
||||
headers_off=self.parameter['headers_off'],
|
||||
tables=self.parameter['tables'],
|
||||
)
|
||||
self.eynollah.plotter = None
|
||||
|
||||
def shutdown(self):
|
||||
if hasattr(self, 'eynollah'):
|
||||
del self.eynollah
|
||||
|
||||
def process_page_pcgts(self, *input_pcgts: Optional[OcrdPage], page_id: Optional[str] = None) -> OcrdPageResult:
|
||||
"""
|
||||
Performs cropping, region and line segmentation with Eynollah.
|
||||
|
||||
For each page, open and deserialize PAGE input file (from existing
|
||||
PAGE file in the input fileGrp, or generated from image file).
|
||||
Retrieve its respective page-level image (ignoring annotation that
|
||||
already added `binarized`, `cropped` or `deskewed` features).
|
||||
|
||||
Set up Eynollah to detect regions and lines, and add each one to the
|
||||
page, respectively.
|
||||
|
||||
\b
|
||||
- If ``tables``, try to detect table blocks and add them as TableRegion.
|
||||
- If ``full_layout``, then in addition to paragraphs and marginals, also
|
||||
try to detect drop capitals and headings.
|
||||
- If ``ignore_page_extraction``, then attempt no cropping of the page.
|
||||
- If ``curved_line``, then compute contour polygons for text lines
|
||||
instead of simple bounding boxes.
|
||||
|
||||
Produce a new output file by serialising the resulting hierarchy.
|
||||
"""
|
||||
assert input_pcgts
|
||||
assert input_pcgts[0]
|
||||
assert self.parameter
|
||||
pcgts = input_pcgts[0]
|
||||
result = OcrdPageResult(pcgts)
|
||||
page = pcgts.get_Page()
|
||||
# if not('://' in page.imageFilename):
|
||||
# image_filename = next(self.workspace.mets.find_files(local_filename=page.imageFilename)).local_filename
|
||||
# else:
|
||||
# # could be a URL with file:// or truly remote
|
||||
# image_filename = self.workspace.download_file(next(self.workspace.mets.find_files(url=page.imageFilename))).local_filename
|
||||
page_image, _, _ = self.workspace.image_from_page(
|
||||
page, page_id,
|
||||
# avoid any features that would change the coordinate system: cropped,deskewed
|
||||
# (the PAGE builder merely adds regions, so afterwards we would not know which to transform)
|
||||
# also avoid binarization as models usually fare better on grayscale/RGB
|
||||
feature_filter='cropped,deskewed,binarized')
|
||||
eynollah = Eynollah(
|
||||
self.resolve_resource(self.parameter['models']),
|
||||
self.logger,
|
||||
allow_enhancement=self.parameter['allow_enhancement'],
|
||||
curved_line=self.parameter['curved_line'],
|
||||
light_version=self.parameter['light_mode'],
|
||||
right2left=self.parameter['right_to_left'],
|
||||
ignore_page_extraction=self.parameter['ignore_page_extraction'],
|
||||
textline_light=self.parameter['textline_light'],
|
||||
full_layout=self.parameter['full_layout'],
|
||||
allow_scaling=self.parameter['allow_scaling'],
|
||||
headers_off=self.parameter['headers_off'],
|
||||
tables=self.parameter['tables'],
|
||||
override_dpi=self.parameter['dpi'],
|
||||
pcgts=pcgts,
|
||||
image_filename=page.imageFilename,
|
||||
image_pil=page_image
|
||||
if hasattr(page_image, 'filename'):
|
||||
image_filename = page_image.filename
|
||||
else:
|
||||
image_filename = "dummy" # will be replaced by ocrd.Processor.process_page_file
|
||||
result.images.append(OcrdPageResultImage(page_image, '.IMG', page)) # mark as new original
|
||||
# FIXME: mask out already existing regions (incremental segmentation)
|
||||
self.eynollah.cache_images(
|
||||
image_pil=page_image,
|
||||
dpi=self.parameter['dpi'],
|
||||
)
|
||||
if self.models is not None:
|
||||
# reuse loaded models from previous page
|
||||
eynollah.models = self.models
|
||||
eynollah.run()
|
||||
self.models = eynollah.models
|
||||
return OcrdPageResult(pcgts)
|
||||
self.eynollah.writer = EynollahXmlWriter(
|
||||
dir_out=None,
|
||||
image_filename=image_filename,
|
||||
curved_line=self.eynollah.curved_line,
|
||||
textline_light=self.eynollah.textline_light,
|
||||
pcgts=pcgts)
|
||||
self.eynollah.run_single()
|
||||
return result
|
||||
|
|
377
src/eynollah/sbb_binarize.py
Normal file
377
src/eynollah/sbb_binarize.py
Normal file
|
@ -0,0 +1,377 @@
|
|||
"""
|
||||
Tool to load model and binarize a given image.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from glob import glob
|
||||
import os
|
||||
import logging
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import cv2
|
||||
from ocrd_utils import tf_disable_interactive_logs
|
||||
tf_disable_interactive_logs()
|
||||
import tensorflow as tf
|
||||
from tensorflow.keras.models import load_model
|
||||
from tensorflow.python.keras import backend as tensorflow_backend
|
||||
|
||||
|
||||
def resize_image(img_in, input_height, input_width):
|
||||
return cv2.resize(img_in, (input_width, input_height), interpolation=cv2.INTER_NEAREST)
|
||||
|
||||
class SbbBinarizer:
|
||||
|
||||
def __init__(self, model_dir, logger=None):
|
||||
self.model_dir = model_dir
|
||||
self.log = logger if logger else logging.getLogger('SbbBinarizer')
|
||||
|
||||
self.start_new_session()
|
||||
|
||||
self.model_files = glob(self.model_dir+"/*/", recursive = True)
|
||||
|
||||
self.models = []
|
||||
for model_file in self.model_files:
|
||||
self.models.append(self.load_model(model_file))
|
||||
|
||||
def start_new_session(self):
|
||||
config = tf.compat.v1.ConfigProto()
|
||||
config.gpu_options.allow_growth = True
|
||||
|
||||
self.session = tf.compat.v1.Session(config=config) # tf.InteractiveSession()
|
||||
tensorflow_backend.set_session(self.session)
|
||||
|
||||
def end_session(self):
|
||||
tensorflow_backend.clear_session()
|
||||
self.session.close()
|
||||
del self.session
|
||||
|
||||
def load_model(self, model_name):
|
||||
model = load_model(os.path.join(self.model_dir, model_name), compile=False)
|
||||
model_height = model.layers[len(model.layers)-1].output_shape[1]
|
||||
model_width = model.layers[len(model.layers)-1].output_shape[2]
|
||||
n_classes = model.layers[len(model.layers)-1].output_shape[3]
|
||||
return model, model_height, model_width, n_classes
|
||||
|
||||
def predict(self, model_in, img, use_patches, n_batch_inference=5):
|
||||
tensorflow_backend.set_session(self.session)
|
||||
model, model_height, model_width, n_classes = model_in
|
||||
|
||||
img_org_h = img.shape[0]
|
||||
img_org_w = img.shape[1]
|
||||
|
||||
if img.shape[0] < model_height and img.shape[1] >= model_width:
|
||||
img_padded = np.zeros(( model_height, img.shape[1], img.shape[2] ))
|
||||
|
||||
index_start_h = int( abs( img.shape[0] - model_height) /2.)
|
||||
index_start_w = 0
|
||||
|
||||
img_padded [ index_start_h: index_start_h+img.shape[0], :, : ] = img[:,:,:]
|
||||
|
||||
elif img.shape[0] >= model_height and img.shape[1] < model_width:
|
||||
img_padded = np.zeros(( img.shape[0], model_width, img.shape[2] ))
|
||||
|
||||
index_start_h = 0
|
||||
index_start_w = int( abs( img.shape[1] - model_width) /2.)
|
||||
|
||||
img_padded [ :, index_start_w: index_start_w+img.shape[1], : ] = img[:,:,:]
|
||||
|
||||
|
||||
elif img.shape[0] < model_height and img.shape[1] < model_width:
|
||||
img_padded = np.zeros(( model_height, model_width, img.shape[2] ))
|
||||
|
||||
index_start_h = int( abs( img.shape[0] - model_height) /2.)
|
||||
index_start_w = int( abs( img.shape[1] - model_width) /2.)
|
||||
|
||||
img_padded [ index_start_h: index_start_h+img.shape[0], index_start_w: index_start_w+img.shape[1], : ] = img[:,:,:]
|
||||
|
||||
else:
|
||||
index_start_h = 0
|
||||
index_start_w = 0
|
||||
img_padded = np.copy(img)
|
||||
|
||||
|
||||
img = np.copy(img_padded)
|
||||
|
||||
|
||||
|
||||
if use_patches:
|
||||
|
||||
margin = int(0.1 * model_width)
|
||||
|
||||
width_mid = model_width - 2 * margin
|
||||
height_mid = model_height - 2 * margin
|
||||
|
||||
|
||||
img = img / float(255.0)
|
||||
|
||||
img_h = img.shape[0]
|
||||
img_w = img.shape[1]
|
||||
|
||||
prediction_true = np.zeros((img_h, img_w, 3))
|
||||
mask_true = np.zeros((img_h, img_w))
|
||||
nxf = img_w / float(width_mid)
|
||||
nyf = img_h / float(height_mid)
|
||||
|
||||
if nxf > int(nxf):
|
||||
nxf = int(nxf) + 1
|
||||
else:
|
||||
nxf = int(nxf)
|
||||
|
||||
if nyf > int(nyf):
|
||||
nyf = int(nyf) + 1
|
||||
else:
|
||||
nyf = int(nyf)
|
||||
|
||||
|
||||
list_i_s = []
|
||||
list_j_s = []
|
||||
list_x_u = []
|
||||
list_x_d = []
|
||||
list_y_u = []
|
||||
list_y_d = []
|
||||
|
||||
batch_indexer = 0
|
||||
|
||||
img_patch = np.zeros((n_batch_inference, model_height, model_width,3))
|
||||
|
||||
for i in range(nxf):
|
||||
for j in range(nyf):
|
||||
|
||||
if i == 0:
|
||||
index_x_d = i * width_mid
|
||||
index_x_u = index_x_d + model_width
|
||||
elif i > 0:
|
||||
index_x_d = i * width_mid
|
||||
index_x_u = index_x_d + model_width
|
||||
|
||||
if j == 0:
|
||||
index_y_d = j * height_mid
|
||||
index_y_u = index_y_d + model_height
|
||||
elif j > 0:
|
||||
index_y_d = j * height_mid
|
||||
index_y_u = index_y_d + model_height
|
||||
|
||||
if index_x_u > img_w:
|
||||
index_x_u = img_w
|
||||
index_x_d = img_w - model_width
|
||||
if index_y_u > img_h:
|
||||
index_y_u = img_h
|
||||
index_y_d = img_h - model_height
|
||||
|
||||
|
||||
list_i_s.append(i)
|
||||
list_j_s.append(j)
|
||||
list_x_u.append(index_x_u)
|
||||
list_x_d.append(index_x_d)
|
||||
list_y_d.append(index_y_d)
|
||||
list_y_u.append(index_y_u)
|
||||
|
||||
|
||||
img_patch[batch_indexer,:,:,:] = img[index_y_d:index_y_u, index_x_d:index_x_u, :]
|
||||
|
||||
batch_indexer = batch_indexer + 1
|
||||
|
||||
|
||||
|
||||
if batch_indexer == n_batch_inference:
|
||||
|
||||
label_p_pred = model.predict(img_patch,verbose=0)
|
||||
|
||||
seg = np.argmax(label_p_pred, axis=3)
|
||||
|
||||
#print(seg.shape, len(seg), len(list_i_s))
|
||||
|
||||
indexer_inside_batch = 0
|
||||
for i_batch, j_batch in zip(list_i_s, list_j_s):
|
||||
seg_in = seg[indexer_inside_batch,:,:]
|
||||
seg_color = np.repeat(seg_in[:, :, np.newaxis], 3, axis=2)
|
||||
|
||||
index_y_u_in = list_y_u[indexer_inside_batch]
|
||||
index_y_d_in = list_y_d[indexer_inside_batch]
|
||||
|
||||
index_x_u_in = list_x_u[indexer_inside_batch]
|
||||
index_x_d_in = list_x_d[indexer_inside_batch]
|
||||
|
||||
if i_batch == 0 and j_batch == 0:
|
||||
seg_color = seg_color[0 : seg_color.shape[0] - margin, 0 : seg_color.shape[1] - margin, :]
|
||||
prediction_true[index_y_d_in + 0 : index_y_u_in - margin, index_x_d_in + 0 : index_x_u_in - margin, :] = seg_color
|
||||
elif i_batch == nxf - 1 and j_batch == nyf - 1:
|
||||
seg_color = seg_color[margin : seg_color.shape[0] - 0, margin : seg_color.shape[1] - 0, :]
|
||||
prediction_true[index_y_d_in + margin : index_y_u_in - 0, index_x_d_in + margin : index_x_u_in - 0, :] = seg_color
|
||||
elif i_batch == 0 and j_batch == nyf - 1:
|
||||
seg_color = seg_color[margin : seg_color.shape[0] - 0, 0 : seg_color.shape[1] - margin, :]
|
||||
prediction_true[index_y_d_in + margin : index_y_u_in - 0, index_x_d_in + 0 : index_x_u_in - margin, :] = seg_color
|
||||
elif i_batch == nxf - 1 and j_batch == 0:
|
||||
seg_color = seg_color[0 : seg_color.shape[0] - margin, margin : seg_color.shape[1] - 0, :]
|
||||
prediction_true[index_y_d_in + 0 : index_y_u_in - margin, index_x_d_in + margin : index_x_u_in - 0, :] = seg_color
|
||||
elif i_batch == 0 and j_batch != 0 and j_batch != nyf - 1:
|
||||
seg_color = seg_color[margin : seg_color.shape[0] - margin, 0 : seg_color.shape[1] - margin, :]
|
||||
prediction_true[index_y_d_in + margin : index_y_u_in - margin, index_x_d_in + 0 : index_x_u_in - margin, :] = seg_color
|
||||
elif i_batch == nxf - 1 and j_batch != 0 and j_batch != nyf - 1:
|
||||
seg_color = seg_color[margin : seg_color.shape[0] - margin, margin : seg_color.shape[1] - 0, :]
|
||||
prediction_true[index_y_d_in + margin : index_y_u_in - margin, index_x_d_in + margin : index_x_u_in - 0, :] = seg_color
|
||||
elif i_batch != 0 and i_batch != nxf - 1 and j_batch == 0:
|
||||
seg_color = seg_color[0 : seg_color.shape[0] - margin, margin : seg_color.shape[1] - margin, :]
|
||||
prediction_true[index_y_d_in + 0 : index_y_u_in - margin, index_x_d_in + margin : index_x_u_in - margin, :] = seg_color
|
||||
elif i_batch != 0 and i_batch != nxf - 1 and j_batch == nyf - 1:
|
||||
seg_color = seg_color[margin : seg_color.shape[0] - 0, margin : seg_color.shape[1] - margin, :]
|
||||
prediction_true[index_y_d_in + margin : index_y_u_in - 0, index_x_d_in + margin : index_x_u_in - margin, :] = seg_color
|
||||
else:
|
||||
seg_color = seg_color[margin : seg_color.shape[0] - margin, margin : seg_color.shape[1] - margin, :]
|
||||
prediction_true[index_y_d_in + margin : index_y_u_in - margin, index_x_d_in + margin : index_x_u_in - margin, :] = seg_color
|
||||
|
||||
indexer_inside_batch = indexer_inside_batch +1
|
||||
|
||||
|
||||
list_i_s = []
|
||||
list_j_s = []
|
||||
list_x_u = []
|
||||
list_x_d = []
|
||||
list_y_u = []
|
||||
list_y_d = []
|
||||
|
||||
batch_indexer = 0
|
||||
|
||||
img_patch = np.zeros((n_batch_inference, model_height, model_width,3))
|
||||
|
||||
elif i==(nxf-1) and j==(nyf-1):
|
||||
label_p_pred = model.predict(img_patch,verbose=0)
|
||||
|
||||
seg = np.argmax(label_p_pred, axis=3)
|
||||
|
||||
#print(seg.shape, len(seg), len(list_i_s))
|
||||
|
||||
indexer_inside_batch = 0
|
||||
for i_batch, j_batch in zip(list_i_s, list_j_s):
|
||||
seg_in = seg[indexer_inside_batch,:,:]
|
||||
seg_color = np.repeat(seg_in[:, :, np.newaxis], 3, axis=2)
|
||||
|
||||
index_y_u_in = list_y_u[indexer_inside_batch]
|
||||
index_y_d_in = list_y_d[indexer_inside_batch]
|
||||
|
||||
index_x_u_in = list_x_u[indexer_inside_batch]
|
||||
index_x_d_in = list_x_d[indexer_inside_batch]
|
||||
|
||||
if i_batch == 0 and j_batch == 0:
|
||||
seg_color = seg_color[0 : seg_color.shape[0] - margin, 0 : seg_color.shape[1] - margin, :]
|
||||
prediction_true[index_y_d_in + 0 : index_y_u_in - margin, index_x_d_in + 0 : index_x_u_in - margin, :] = seg_color
|
||||
elif i_batch == nxf - 1 and j_batch == nyf - 1:
|
||||
seg_color = seg_color[margin : seg_color.shape[0] - 0, margin : seg_color.shape[1] - 0, :]
|
||||
prediction_true[index_y_d_in + margin : index_y_u_in - 0, index_x_d_in + margin : index_x_u_in - 0, :] = seg_color
|
||||
elif i_batch == 0 and j_batch == nyf - 1:
|
||||
seg_color = seg_color[margin : seg_color.shape[0] - 0, 0 : seg_color.shape[1] - margin, :]
|
||||
prediction_true[index_y_d_in + margin : index_y_u_in - 0, index_x_d_in + 0 : index_x_u_in - margin, :] = seg_color
|
||||
elif i_batch == nxf - 1 and j_batch == 0:
|
||||
seg_color = seg_color[0 : seg_color.shape[0] - margin, margin : seg_color.shape[1] - 0, :]
|
||||
prediction_true[index_y_d_in + 0 : index_y_u_in - margin, index_x_d_in + margin : index_x_u_in - 0, :] = seg_color
|
||||
elif i_batch == 0 and j_batch != 0 and j_batch != nyf - 1:
|
||||
seg_color = seg_color[margin : seg_color.shape[0] - margin, 0 : seg_color.shape[1] - margin, :]
|
||||
prediction_true[index_y_d_in + margin : index_y_u_in - margin, index_x_d_in + 0 : index_x_u_in - margin, :] = seg_color
|
||||
elif i_batch == nxf - 1 and j_batch != 0 and j_batch != nyf - 1:
|
||||
seg_color = seg_color[margin : seg_color.shape[0] - margin, margin : seg_color.shape[1] - 0, :]
|
||||
prediction_true[index_y_d_in + margin : index_y_u_in - margin, index_x_d_in + margin : index_x_u_in - 0, :] = seg_color
|
||||
elif i_batch != 0 and i_batch != nxf - 1 and j_batch == 0:
|
||||
seg_color = seg_color[0 : seg_color.shape[0] - margin, margin : seg_color.shape[1] - margin, :]
|
||||
prediction_true[index_y_d_in + 0 : index_y_u_in - margin, index_x_d_in + margin : index_x_u_in - margin, :] = seg_color
|
||||
elif i_batch != 0 and i_batch != nxf - 1 and j_batch == nyf - 1:
|
||||
seg_color = seg_color[margin : seg_color.shape[0] - 0, margin : seg_color.shape[1] - margin, :]
|
||||
prediction_true[index_y_d_in + margin : index_y_u_in - 0, index_x_d_in + margin : index_x_u_in - margin, :] = seg_color
|
||||
else:
|
||||
seg_color = seg_color[margin : seg_color.shape[0] - margin, margin : seg_color.shape[1] - margin, :]
|
||||
prediction_true[index_y_d_in + margin : index_y_u_in - margin, index_x_d_in + margin : index_x_u_in - margin, :] = seg_color
|
||||
|
||||
indexer_inside_batch = indexer_inside_batch +1
|
||||
|
||||
|
||||
list_i_s = []
|
||||
list_j_s = []
|
||||
list_x_u = []
|
||||
list_x_d = []
|
||||
list_y_u = []
|
||||
list_y_d = []
|
||||
|
||||
batch_indexer = 0
|
||||
|
||||
img_patch = np.zeros((n_batch_inference, model_height, model_width,3))
|
||||
|
||||
|
||||
|
||||
prediction_true = prediction_true[index_start_h: index_start_h+img_org_h, index_start_w: index_start_w+img_org_w,:]
|
||||
prediction_true = prediction_true.astype(np.uint8)
|
||||
|
||||
else:
|
||||
img_h_page = img.shape[0]
|
||||
img_w_page = img.shape[1]
|
||||
img = img / float(255.0)
|
||||
img = resize_image(img, model_height, model_width)
|
||||
|
||||
label_p_pred = model.predict(img.reshape(1, img.shape[0], img.shape[1], img.shape[2]))
|
||||
|
||||
seg = np.argmax(label_p_pred, axis=3)[0]
|
||||
seg_color = np.repeat(seg[:, :, np.newaxis], 3, axis=2)
|
||||
prediction_true = resize_image(seg_color, img_h_page, img_w_page)
|
||||
prediction_true = prediction_true.astype(np.uint8)
|
||||
return prediction_true[:,:,0]
|
||||
|
||||
def run(self, image=None, image_path=None, save=None, use_patches=False, dir_in=None, dir_out=None):
|
||||
print(dir_in,'dir_in')
|
||||
if not dir_in:
|
||||
if (image is not None and image_path is not None) or \
|
||||
(image is None and image_path is None):
|
||||
raise ValueError("Must pass either a opencv2 image or an image_path")
|
||||
if image_path is not None:
|
||||
image = cv2.imread(image_path)
|
||||
img_last = 0
|
||||
for n, (model, model_file) in enumerate(zip(self.models, self.model_files)):
|
||||
self.log.info('Predicting with model %s [%s/%s]' % (model_file, n + 1, len(self.model_files)))
|
||||
|
||||
res = self.predict(model, image, use_patches)
|
||||
|
||||
img_fin = np.zeros((res.shape[0], res.shape[1], 3))
|
||||
res[:, :][res[:, :] == 0] = 2
|
||||
res = res - 1
|
||||
res = res * 255
|
||||
img_fin[:, :, 0] = res
|
||||
img_fin[:, :, 1] = res
|
||||
img_fin[:, :, 2] = res
|
||||
|
||||
img_fin = img_fin.astype(np.uint8)
|
||||
img_fin = (res[:, :] == 0) * 255
|
||||
img_last = img_last + img_fin
|
||||
|
||||
kernel = np.ones((5, 5), np.uint8)
|
||||
img_last[:, :][img_last[:, :] > 0] = 255
|
||||
img_last = (img_last[:, :] == 0) * 255
|
||||
if save:
|
||||
cv2.imwrite(save, img_last)
|
||||
return img_last
|
||||
else:
|
||||
ls_imgs = os.listdir(dir_in)
|
||||
for image_name in ls_imgs:
|
||||
image_stem = image_name.split('.')[0]
|
||||
print(image_name,'image_name')
|
||||
image = cv2.imread(os.path.join(dir_in,image_name) )
|
||||
img_last = 0
|
||||
for n, (model, model_file) in enumerate(zip(self.models, self.model_files)):
|
||||
self.log.info('Predicting with model %s [%s/%s]' % (model_file, n + 1, len(self.model_files)))
|
||||
|
||||
res = self.predict(model, image, use_patches)
|
||||
|
||||
img_fin = np.zeros((res.shape[0], res.shape[1], 3))
|
||||
res[:, :][res[:, :] == 0] = 2
|
||||
res = res - 1
|
||||
res = res * 255
|
||||
img_fin[:, :, 0] = res
|
||||
img_fin[:, :, 1] = res
|
||||
img_fin[:, :, 2] = res
|
||||
|
||||
img_fin = img_fin.astype(np.uint8)
|
||||
img_fin = (res[:, :] == 0) * 255
|
||||
img_last = img_last + img_fin
|
||||
|
||||
kernel = np.ones((5, 5), np.uint8)
|
||||
img_last[:, :][img_last[:, :] > 0] = 255
|
||||
img_last = (img_last[:, :] == 0) * 255
|
||||
|
||||
cv2.imwrite(os.path.join(dir_out,image_stem+'.png'), img_last)
|
File diff suppressed because it is too large
Load diff
|
@ -1,10 +1,10 @@
|
|||
from functools import partial
|
||||
import cv2
|
||||
import numpy as np
|
||||
from shapely import geometry
|
||||
|
||||
from .rotate import rotate_image, rotation_image_new
|
||||
from multiprocessing import Process, Queue, cpu_count
|
||||
from multiprocessing import Pool
|
||||
|
||||
def contours_in_same_horizon(cy_main_hor):
|
||||
X1 = np.zeros((len(cy_main_hor), len(cy_main_hor)))
|
||||
X2 = np.zeros((len(cy_main_hor), len(cy_main_hor)))
|
||||
|
@ -27,37 +27,33 @@ def find_contours_mean_y_diff(contours_main):
|
|||
cy_main = [(M_main[j]["m01"] / (M_main[j]["m00"] + 1e-32)) for j in range(len(M_main))]
|
||||
return np.mean(np.diff(np.sort(np.array(cy_main))))
|
||||
|
||||
|
||||
def get_text_region_boxes_by_given_contours(contours):
|
||||
|
||||
kernel = np.ones((5, 5), np.uint8)
|
||||
boxes = []
|
||||
contours_new = []
|
||||
for jj in range(len(contours)):
|
||||
x, y, w, h = cv2.boundingRect(contours[jj])
|
||||
|
||||
boxes.append([x, y, w, h])
|
||||
box = cv2.boundingRect(contours[jj])
|
||||
boxes.append(box)
|
||||
contours_new.append(contours[jj])
|
||||
|
||||
del contours
|
||||
return boxes, contours_new
|
||||
|
||||
def filter_contours_area_of_image(image, contours, hierarchy, max_area, min_area):
|
||||
found_polygons_early = list()
|
||||
|
||||
found_polygons_early = []
|
||||
for jv,c in enumerate(contours):
|
||||
if len(c) < 3: # A polygon cannot have less than 3 points
|
||||
continue
|
||||
|
||||
polygon = geometry.Polygon([point[0] for point in c])
|
||||
area = polygon.area
|
||||
if area >= min_area * np.prod(image.shape[:2]) and area <= max_area * np.prod(image.shape[:2]) and hierarchy[0][jv][3] == -1: # and hierarchy[0][jv][3]==-1 :
|
||||
found_polygons_early.append(np.array([[point] for point in polygon.exterior.coords], dtype=np.uint))
|
||||
if (area >= min_area * np.prod(image.shape[:2]) and
|
||||
area <= max_area * np.prod(image.shape[:2]) and
|
||||
hierarchy[0][jv][3] == -1):
|
||||
found_polygons_early.append(np.array([[point]
|
||||
for point in polygon.exterior.coords], dtype=np.uint))
|
||||
return found_polygons_early
|
||||
|
||||
def filter_contours_area_of_image_tables(image, contours, hierarchy, max_area, min_area):
|
||||
found_polygons_early = list()
|
||||
|
||||
found_polygons_early = []
|
||||
for jv,c in enumerate(contours):
|
||||
if len(c) < 3: # A polygon cannot have less than 3 points
|
||||
continue
|
||||
|
@ -68,48 +64,59 @@ def filter_contours_area_of_image_tables(image, contours, hierarchy, max_area, m
|
|||
##print(np.prod(thresh.shape[:2]))
|
||||
# Check that polygon has area greater than minimal area
|
||||
# print(hierarchy[0][jv][3],hierarchy )
|
||||
if area >= min_area * np.prod(image.shape[:2]) and area <= max_area * np.prod(image.shape[:2]): # and hierarchy[0][jv][3]==-1 :
|
||||
if (area >= min_area * np.prod(image.shape[:2]) and
|
||||
area <= max_area * np.prod(image.shape[:2]) and
|
||||
# hierarchy[0][jv][3]==-1
|
||||
True):
|
||||
# print(c[0][0][1])
|
||||
found_polygons_early.append(np.array([[point] for point in polygon.exterior.coords], dtype=np.int32))
|
||||
found_polygons_early.append(np.array([[point]
|
||||
for point in polygon.exterior.coords], dtype=np.int32))
|
||||
return found_polygons_early
|
||||
|
||||
def find_new_features_of_contours(contours_main):
|
||||
|
||||
areas_main = np.array([cv2.contourArea(contours_main[j]) for j in range(len(contours_main))])
|
||||
M_main = [cv2.moments(contours_main[j]) for j in range(len(contours_main))]
|
||||
cx_main = [(M_main[j]["m10"] / (M_main[j]["m00"] + 1e-32)) for j in range(len(M_main))]
|
||||
cy_main = [(M_main[j]["m01"] / (M_main[j]["m00"] + 1e-32)) for j in range(len(M_main))]
|
||||
areas_main = np.array([cv2.contourArea(contours_main[j])
|
||||
for j in range(len(contours_main))])
|
||||
M_main = [cv2.moments(contours_main[j])
|
||||
for j in range(len(contours_main))]
|
||||
cx_main = [(M_main[j]["m10"] / (M_main[j]["m00"] + 1e-32))
|
||||
for j in range(len(M_main))]
|
||||
cy_main = [(M_main[j]["m01"] / (M_main[j]["m00"] + 1e-32))
|
||||
for j in range(len(M_main))]
|
||||
try:
|
||||
x_min_main = np.array([np.min(contours_main[j][:, 0, 0]) for j in range(len(contours_main))])
|
||||
|
||||
argmin_x_main = np.array([np.argmin(contours_main[j][:, 0, 0]) for j in range(len(contours_main))])
|
||||
|
||||
x_min_from_argmin = np.array([contours_main[j][argmin_x_main[j], 0, 0] for j in range(len(contours_main))])
|
||||
y_corr_x_min_from_argmin = np.array([contours_main[j][argmin_x_main[j], 0, 1] for j in range(len(contours_main))])
|
||||
|
||||
x_max_main = np.array([np.max(contours_main[j][:, 0, 0]) for j in range(len(contours_main))])
|
||||
|
||||
y_min_main = np.array([np.min(contours_main[j][:, 0, 1]) for j in range(len(contours_main))])
|
||||
y_max_main = np.array([np.max(contours_main[j][:, 0, 1]) for j in range(len(contours_main))])
|
||||
x_min_main = np.array([np.min(contours_main[j][:, 0, 0])
|
||||
for j in range(len(contours_main))])
|
||||
argmin_x_main = np.array([np.argmin(contours_main[j][:, 0, 0])
|
||||
for j in range(len(contours_main))])
|
||||
x_min_from_argmin = np.array([contours_main[j][argmin_x_main[j], 0, 0]
|
||||
for j in range(len(contours_main))])
|
||||
y_corr_x_min_from_argmin = np.array([contours_main[j][argmin_x_main[j], 0, 1]
|
||||
for j in range(len(contours_main))])
|
||||
x_max_main = np.array([np.max(contours_main[j][:, 0, 0])
|
||||
for j in range(len(contours_main))])
|
||||
y_min_main = np.array([np.min(contours_main[j][:, 0, 1])
|
||||
for j in range(len(contours_main))])
|
||||
y_max_main = np.array([np.max(contours_main[j][:, 0, 1])
|
||||
for j in range(len(contours_main))])
|
||||
except:
|
||||
x_min_main = np.array([np.min(contours_main[j][:, 0]) for j in range(len(contours_main))])
|
||||
|
||||
argmin_x_main = np.array([np.argmin(contours_main[j][:, 0]) for j in range(len(contours_main))])
|
||||
|
||||
x_min_from_argmin = np.array([contours_main[j][argmin_x_main[j], 0] for j in range(len(contours_main))])
|
||||
y_corr_x_min_from_argmin = np.array([contours_main[j][argmin_x_main[j], 1] for j in range(len(contours_main))])
|
||||
|
||||
x_max_main = np.array([np.max(contours_main[j][:, 0]) for j in range(len(contours_main))])
|
||||
|
||||
y_min_main = np.array([np.min(contours_main[j][:, 1]) for j in range(len(contours_main))])
|
||||
y_max_main = np.array([np.max(contours_main[j][:, 1]) for j in range(len(contours_main))])
|
||||
|
||||
x_min_main = np.array([np.min(contours_main[j][:, 0])
|
||||
for j in range(len(contours_main))])
|
||||
argmin_x_main = np.array([np.argmin(contours_main[j][:, 0])
|
||||
for j in range(len(contours_main))])
|
||||
x_min_from_argmin = np.array([contours_main[j][argmin_x_main[j], 0]
|
||||
for j in range(len(contours_main))])
|
||||
y_corr_x_min_from_argmin = np.array([contours_main[j][argmin_x_main[j], 1]
|
||||
for j in range(len(contours_main))])
|
||||
x_max_main = np.array([np.max(contours_main[j][:, 0])
|
||||
for j in range(len(contours_main))])
|
||||
y_min_main = np.array([np.min(contours_main[j][:, 1])
|
||||
for j in range(len(contours_main))])
|
||||
y_max_main = np.array([np.max(contours_main[j][:, 1])
|
||||
for j in range(len(contours_main))])
|
||||
# dis_x=np.abs(x_max_main-x_min_main)
|
||||
|
||||
return cx_main, cy_main, x_min_main, x_max_main, y_min_main, y_max_main, y_corr_x_min_from_argmin
|
||||
def find_features_of_contours(contours_main):
|
||||
|
||||
|
||||
def find_features_of_contours(contours_main):
|
||||
areas_main=np.array([cv2.contourArea(contours_main[j]) for j in range(len(contours_main))])
|
||||
M_main=[cv2.moments(contours_main[j]) for j in range(len(contours_main))]
|
||||
cx_main=[(M_main[j]['m10']/(M_main[j]['m00']+1e-32)) for j in range(len(M_main))]
|
||||
|
@ -120,14 +127,15 @@ def find_features_of_contours(contours_main):
|
|||
y_min_main=np.array([np.min(contours_main[j][:,0,1]) for j in range(len(contours_main))])
|
||||
y_max_main=np.array([np.max(contours_main[j][:,0,1]) for j in range(len(contours_main))])
|
||||
|
||||
|
||||
return y_min_main, y_max_main
|
||||
|
||||
def return_parent_contours(contours, hierarchy):
|
||||
contours_parent = [contours[i] for i in range(len(contours)) if hierarchy[0][i][3] == -1]
|
||||
contours_parent = [contours[i]
|
||||
for i in range(len(contours))
|
||||
if hierarchy[0][i][3] == -1]
|
||||
return contours_parent
|
||||
|
||||
def return_contours_of_interested_region(region_pre_p, pixel, min_area=0.0002):
|
||||
|
||||
# pixels of images are identified by 5
|
||||
if len(region_pre_p.shape) == 3:
|
||||
cnts_images = (region_pre_p[:, :, 0] == pixel) * 1
|
||||
|
@ -139,80 +147,16 @@ def return_contours_of_interested_region(region_pre_p, pixel, min_area=0.0002):
|
|||
ret, thresh = cv2.threshold(imgray, 0, 255, 0)
|
||||
|
||||
contours_imgs, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
|
||||
contours_imgs = return_parent_contours(contours_imgs, hierarchy)
|
||||
contours_imgs = filter_contours_area_of_image_tables(thresh, contours_imgs, hierarchy, max_area=1, min_area=min_area)
|
||||
|
||||
contours_imgs = filter_contours_area_of_image_tables(thresh, contours_imgs, hierarchy,
|
||||
max_area=1, min_area=min_area)
|
||||
return contours_imgs
|
||||
|
||||
def do_work_of_contours_in_image(queue_of_all_params, contours_per_process, indexes_r_con_per_pro, img, slope_first):
|
||||
cnts_org_per_each_subprocess = []
|
||||
index_by_text_region_contours = []
|
||||
for mv in range(len(contours_per_process)):
|
||||
index_by_text_region_contours.append(indexes_r_con_per_pro[mv])
|
||||
|
||||
img_copy = np.zeros(img.shape)
|
||||
img_copy = cv2.fillPoly(img_copy, pts=[contours_per_process[mv]], color=(1, 1, 1))
|
||||
|
||||
img_copy = rotation_image_new(img_copy, -slope_first)
|
||||
|
||||
img_copy = img_copy.astype(np.uint8)
|
||||
imgray = cv2.cvtColor(img_copy, cv2.COLOR_BGR2GRAY)
|
||||
ret, thresh = cv2.threshold(imgray, 0, 255, 0)
|
||||
|
||||
cont_int, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
|
||||
cont_int[0][:, 0, 0] = cont_int[0][:, 0, 0] + np.abs(img_copy.shape[1] - img.shape[1])
|
||||
cont_int[0][:, 0, 1] = cont_int[0][:, 0, 1] + np.abs(img_copy.shape[0] - img.shape[0])
|
||||
|
||||
|
||||
cnts_org_per_each_subprocess.append(cont_int[0])
|
||||
|
||||
queue_of_all_params.put([ cnts_org_per_each_subprocess, index_by_text_region_contours])
|
||||
|
||||
|
||||
def get_textregion_contours_in_org_image_multi(cnts, img, slope_first):
|
||||
|
||||
num_cores = cpu_count()
|
||||
queue_of_all_params = Queue()
|
||||
|
||||
processes = []
|
||||
nh = np.linspace(0, len(cnts), num_cores + 1)
|
||||
indexes_by_text_con = np.array(range(len(cnts)))
|
||||
for i in range(num_cores):
|
||||
contours_per_process = cnts[int(nh[i]) : int(nh[i + 1])]
|
||||
indexes_text_con_per_process = indexes_by_text_con[int(nh[i]) : int(nh[i + 1])]
|
||||
|
||||
processes.append(Process(target=do_work_of_contours_in_image, args=(queue_of_all_params, contours_per_process, indexes_text_con_per_process, img,slope_first )))
|
||||
for i in range(num_cores):
|
||||
processes[i].start()
|
||||
cnts_org = []
|
||||
all_index_text_con = []
|
||||
for i in range(num_cores):
|
||||
list_all_par = queue_of_all_params.get(True)
|
||||
contours_for_sub_process = list_all_par[0]
|
||||
indexes_for_sub_process = list_all_par[1]
|
||||
for j in range(len(contours_for_sub_process)):
|
||||
cnts_org.append(contours_for_sub_process[j])
|
||||
all_index_text_con.append(indexes_for_sub_process[j])
|
||||
for i in range(num_cores):
|
||||
processes[i].join()
|
||||
|
||||
print(all_index_text_con)
|
||||
return cnts_org
|
||||
def loop_contour_image(index_l, cnts,img, slope_first):
|
||||
def do_work_of_contours_in_image(contour, index_r_con, img, slope_first):
|
||||
img_copy = np.zeros(img.shape)
|
||||
img_copy = cv2.fillPoly(img_copy, pts=[cnts[index_l]], color=(1, 1, 1))
|
||||
img_copy = cv2.fillPoly(img_copy, pts=[contour], color=(1, 1, 1))
|
||||
|
||||
# plt.imshow(img_copy)
|
||||
# plt.show()
|
||||
|
||||
# print(img.shape,'img')
|
||||
img_copy = rotation_image_new(img_copy, -slope_first)
|
||||
##print(img_copy.shape,'img_copy')
|
||||
# plt.imshow(img_copy)
|
||||
# plt.show()
|
||||
|
||||
img_copy = img_copy.astype(np.uint8)
|
||||
imgray = cv2.cvtColor(img_copy, cv2.COLOR_BGR2GRAY)
|
||||
ret, thresh = cv2.threshold(imgray, 0, 255, 0)
|
||||
|
@ -221,20 +165,20 @@ def loop_contour_image(index_l, cnts,img, slope_first):
|
|||
|
||||
cont_int[0][:, 0, 0] = cont_int[0][:, 0, 0] + np.abs(img_copy.shape[1] - img.shape[1])
|
||||
cont_int[0][:, 0, 1] = cont_int[0][:, 0, 1] + np.abs(img_copy.shape[0] - img.shape[0])
|
||||
# print(np.shape(cont_int[0]))
|
||||
return cont_int[0]
|
||||
|
||||
def get_textregion_contours_in_org_image_multi2(cnts, img, slope_first):
|
||||
return cont_int[0], index_r_con
|
||||
|
||||
cnts_org = []
|
||||
# print(cnts,'cnts')
|
||||
with Pool(cpu_count()) as p:
|
||||
cnts_org = p.starmap(loop_contour_image, [(index_l,cnts, img,slope_first) for index_l in range(len(cnts))])
|
||||
|
||||
return cnts_org
|
||||
def get_textregion_contours_in_org_image_multi(cnts, img, slope_first, map=map):
|
||||
if not len(cnts):
|
||||
return [], []
|
||||
results = map(partial(do_work_of_contours_in_image,
|
||||
img=img,
|
||||
slope_first=slope_first,
|
||||
),
|
||||
cnts, range(len(cnts)))
|
||||
return tuple(zip(*results))
|
||||
|
||||
def get_textregion_contours_in_org_image(cnts, img, slope_first):
|
||||
|
||||
cnts_org = []
|
||||
# print(cnts,'cnts')
|
||||
for i in range(len(cnts)):
|
||||
|
@ -255,7 +199,6 @@ def get_textregion_contours_in_org_image(cnts, img, slope_first):
|
|||
ret, thresh = cv2.threshold(imgray, 0, 255, 0)
|
||||
|
||||
cont_int, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
|
||||
cont_int[0][:, 0, 0] = cont_int[0][:, 0, 0] + np.abs(img_copy.shape[1] - img.shape[1])
|
||||
cont_int[0][:, 0, 1] = cont_int[0][:, 0, 1] + np.abs(img_copy.shape[0] - img.shape[0])
|
||||
# print(np.shape(cont_int[0]))
|
||||
|
@ -263,45 +206,57 @@ def get_textregion_contours_in_org_image(cnts, img, slope_first):
|
|||
|
||||
return cnts_org
|
||||
|
||||
def get_textregion_contours_in_org_image_light(cnts, img, slope_first):
|
||||
|
||||
h_o = img.shape[0]
|
||||
w_o = img.shape[1]
|
||||
|
||||
img = cv2.resize(img, (int(img.shape[1]/3.), int(img.shape[0]/3.)), interpolation=cv2.INTER_NEAREST)
|
||||
##cnts = list( (np.array(cnts)/2).astype(np.int16) )
|
||||
#cnts = cnts/2
|
||||
cnts = [(i/ 3).astype(np.int32) for i in cnts]
|
||||
def get_textregion_contours_in_org_image_light_old(cnts, img, slope_first):
|
||||
zoom = 3
|
||||
img = cv2.resize(img, (img.shape[1] // zoom,
|
||||
img.shape[0] // zoom),
|
||||
interpolation=cv2.INTER_NEAREST)
|
||||
cnts_org = []
|
||||
#print(cnts,'cnts')
|
||||
for i in range(len(cnts)):
|
||||
for cnt in cnts:
|
||||
img_copy = np.zeros(img.shape)
|
||||
img_copy = cv2.fillPoly(img_copy, pts=[cnts[i]], color=(1, 1, 1))
|
||||
img_copy = cv2.fillPoly(img_copy, pts=[(cnt / zoom).astype(int)], color=(1, 1, 1))
|
||||
|
||||
# plt.imshow(img_copy)
|
||||
# plt.show()
|
||||
|
||||
# print(img.shape,'img')
|
||||
img_copy = rotation_image_new(img_copy, -slope_first)
|
||||
##print(img_copy.shape,'img_copy')
|
||||
# plt.imshow(img_copy)
|
||||
# plt.show()
|
||||
|
||||
img_copy = img_copy.astype(np.uint8)
|
||||
img_copy = rotation_image_new(img_copy, -slope_first).astype(np.uint8)
|
||||
imgray = cv2.cvtColor(img_copy, cv2.COLOR_BGR2GRAY)
|
||||
ret, thresh = cv2.threshold(imgray, 0, 255, 0)
|
||||
|
||||
cont_int, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
|
||||
cont_int[0][:, 0, 0] = cont_int[0][:, 0, 0] + np.abs(img_copy.shape[1] - img.shape[1])
|
||||
cont_int[0][:, 0, 1] = cont_int[0][:, 0, 1] + np.abs(img_copy.shape[0] - img.shape[0])
|
||||
# print(np.shape(cont_int[0]))
|
||||
cnts_org.append(cont_int[0]*3)
|
||||
cnts_org.append(cont_int[0] * zoom)
|
||||
|
||||
return cnts_org
|
||||
|
||||
def return_contours_of_interested_textline(region_pre_p, pixel):
|
||||
def do_back_rotation_and_get_cnt_back(contour_par, index_r_con, img, slope_first):
|
||||
img_copy = np.zeros(img.shape)
|
||||
img_copy = cv2.fillPoly(img_copy, pts=[contour_par], color=(1, 1, 1))
|
||||
|
||||
img_copy = rotation_image_new(img_copy, -slope_first).astype(np.uint8)
|
||||
imgray = cv2.cvtColor(img_copy, cv2.COLOR_BGR2GRAY)
|
||||
ret, thresh = cv2.threshold(imgray, 0, 255, 0)
|
||||
|
||||
cont_int, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
cont_int[0][:, 0, 0] = cont_int[0][:, 0, 0] + np.abs(img_copy.shape[1] - img.shape[1])
|
||||
cont_int[0][:, 0, 1] = cont_int[0][:, 0, 1] + np.abs(img_copy.shape[0] - img.shape[0])
|
||||
# print(np.shape(cont_int[0]))
|
||||
return cont_int[0], index_r_con
|
||||
|
||||
def get_textregion_contours_in_org_image_light(cnts, img, slope_first, map=map):
|
||||
if not len(cnts):
|
||||
return []
|
||||
img = cv2.resize(img, (int(img.shape[1]/6), int(img.shape[0]/6)), interpolation=cv2.INTER_NEAREST)
|
||||
##cnts = list( (np.array(cnts)/2).astype(np.int16) )
|
||||
#cnts = cnts/2
|
||||
cnts = [(i/6).astype(int) for i in cnts]
|
||||
results = map(partial(do_back_rotation_and_get_cnt_back,
|
||||
img=img,
|
||||
slope_first=slope_first,
|
||||
),
|
||||
cnts, range(len(cnts)))
|
||||
contours, indexes = tuple(zip(*results))
|
||||
return [i*6 for i in contours]
|
||||
|
||||
def return_contours_of_interested_textline(region_pre_p, pixel):
|
||||
# pixels of images are identified by 5
|
||||
if len(region_pre_p.shape) == 3:
|
||||
cnts_images = (region_pre_p[:, :, 0] == pixel) * 1
|
||||
|
@ -314,11 +269,11 @@ def return_contours_of_interested_textline(region_pre_p, pixel):
|
|||
contours_imgs, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
|
||||
contours_imgs = return_parent_contours(contours_imgs, hierarchy)
|
||||
contours_imgs = filter_contours_area_of_image_tables(thresh, contours_imgs, hierarchy, max_area=1, min_area=0.000000003)
|
||||
contours_imgs = filter_contours_area_of_image_tables(
|
||||
thresh, contours_imgs, hierarchy, max_area=1, min_area=0.000000003)
|
||||
return contours_imgs
|
||||
|
||||
def return_contours_of_image(image):
|
||||
|
||||
if len(image.shape) == 2:
|
||||
image = np.repeat(image[:, :, np.newaxis], 3, axis=2)
|
||||
image = image.astype(np.uint8)
|
||||
|
@ -330,7 +285,6 @@ def return_contours_of_image(image):
|
|||
return contours, hierarchy
|
||||
|
||||
def return_contours_of_interested_region_by_min_size(region_pre_p, pixel, min_size=0.00003):
|
||||
|
||||
# pixels of images are identified by 5
|
||||
if len(region_pre_p.shape) == 3:
|
||||
cnts_images = (region_pre_p[:, :, 0] == pixel) * 1
|
||||
|
@ -342,14 +296,13 @@ def return_contours_of_interested_region_by_min_size(region_pre_p, pixel, min_si
|
|||
ret, thresh = cv2.threshold(imgray, 0, 255, 0)
|
||||
|
||||
contours_imgs, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
|
||||
contours_imgs = return_parent_contours(contours_imgs, hierarchy)
|
||||
contours_imgs = filter_contours_area_of_image_tables(thresh, contours_imgs, hierarchy, max_area=1, min_area=min_size)
|
||||
contours_imgs = filter_contours_area_of_image_tables(
|
||||
thresh, contours_imgs, hierarchy, max_area=1, min_area=min_size)
|
||||
|
||||
return contours_imgs
|
||||
|
||||
def return_contours_of_interested_region_by_size(region_pre_p, pixel, min_area, max_area):
|
||||
|
||||
# pixels of images are identified by 5
|
||||
if len(region_pre_p.shape) == 3:
|
||||
cnts_images = (region_pre_p[:, :, 0] == pixel) * 1
|
||||
|
@ -362,9 +315,11 @@ def return_contours_of_interested_region_by_size(region_pre_p, pixel, min_area,
|
|||
contours_imgs, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
|
||||
contours_imgs = return_parent_contours(contours_imgs, hierarchy)
|
||||
contours_imgs = filter_contours_area_of_image_tables(thresh, contours_imgs, hierarchy, max_area=max_area, min_area=min_area)
|
||||
contours_imgs = filter_contours_area_of_image_tables(
|
||||
thresh, contours_imgs, hierarchy, max_area=max_area, min_area=min_area)
|
||||
|
||||
img_ret = np.zeros((region_pre_p.shape[0], region_pre_p.shape[1], 3))
|
||||
img_ret = cv2.fillPoly(img_ret, pts=contours_imgs, color=(1, 1, 1))
|
||||
|
||||
return img_ret[:, :, 0]
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ from .contour import (
|
|||
find_new_features_of_contours,
|
||||
return_contours_of_image,
|
||||
return_parent_contours,
|
||||
return_contours_of_interested_region,
|
||||
)
|
||||
|
||||
def adhere_drop_capital_region_into_corresponding_textline(
|
||||
|
@ -17,6 +18,7 @@ def adhere_drop_capital_region_into_corresponding_textline(
|
|||
all_found_textline_polygons_h,
|
||||
kernel=None,
|
||||
curved_line=False,
|
||||
textline_light=False,
|
||||
):
|
||||
# print(np.shape(all_found_textline_polygons),np.shape(all_found_textline_polygons[3]),'all_found_textline_polygonsshape')
|
||||
# print(all_found_textline_polygons[3])
|
||||
|
@ -76,7 +78,7 @@ def adhere_drop_capital_region_into_corresponding_textline(
|
|||
# region_with_intersected_drop=region_with_intersected_drop/3
|
||||
region_with_intersected_drop = region_with_intersected_drop.astype(np.uint8)
|
||||
# print(np.unique(img_con_all_copy[:,:,0]))
|
||||
if curved_line:
|
||||
if curved_line or textline_light:
|
||||
|
||||
if len(region_with_intersected_drop) > 1:
|
||||
sum_pixels_of_intersection = []
|
||||
|
@ -114,12 +116,17 @@ def adhere_drop_capital_region_into_corresponding_textline(
|
|||
img_textlines = cv2.fillPoly(img_textlines, pts=[polygons_of_drop_capitals[i_drop]], color=(255, 255, 255))
|
||||
|
||||
img_textlines = img_textlines.astype(np.uint8)
|
||||
imgray = cv2.cvtColor(img_textlines, cv2.COLOR_BGR2GRAY)
|
||||
ret, thresh = cv2.threshold(imgray, 0, 255, 0)
|
||||
|
||||
contours_combined = return_contours_of_interested_region(img_textlines, 255, 0)
|
||||
|
||||
#plt.imshow(img_textlines)
|
||||
#plt.show()
|
||||
|
||||
#imgray = cv2.cvtColor(img_textlines, cv2.COLOR_BGR2GRAY)
|
||||
#ret, thresh = cv2.threshold(imgray, 0, 255, 0)
|
||||
|
||||
contours_combined, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
#contours_combined, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
|
||||
# print(len(contours_combined),'len textlines mixed')
|
||||
areas_cnt_text = np.array([cv2.contourArea(contours_combined[j]) for j in range(len(contours_combined))])
|
||||
|
||||
contours_biggest = contours_combined[np.argmax(areas_cnt_text)]
|
||||
|
@ -130,8 +137,13 @@ def adhere_drop_capital_region_into_corresponding_textline(
|
|||
# contours_biggest[:,0,1]=contours_biggest[:,0,1]#-all_box_coord[int(region_final)][0]
|
||||
|
||||
# contours_biggest=contours_biggest.reshape(np.shape(contours_biggest)[0],np.shape(contours_biggest)[2])
|
||||
|
||||
all_found_textline_polygons[int(region_final)][arg_min] = contours_biggest
|
||||
|
||||
if len(contours_combined)==1:
|
||||
all_found_textline_polygons[int(region_final)][arg_min] = contours_biggest
|
||||
elif len(contours_combined)==2:
|
||||
all_found_textline_polygons[int(region_final)].insert(arg_min, polygons_of_drop_capitals[i_drop] )
|
||||
else:
|
||||
pass
|
||||
|
||||
except:
|
||||
# print('gordun1')
|
||||
|
@ -167,14 +179,13 @@ def adhere_drop_capital_region_into_corresponding_textline(
|
|||
|
||||
img_textlines = img_textlines.astype(np.uint8)
|
||||
|
||||
# plt.imshow(img_textlines)
|
||||
# plt.show()
|
||||
imgray = cv2.cvtColor(img_textlines, cv2.COLOR_BGR2GRAY)
|
||||
ret, thresh = cv2.threshold(imgray, 0, 255, 0)
|
||||
|
||||
contours_combined = return_contours_of_interested_region(img_textlines, 255, 0)
|
||||
##imgray = cv2.cvtColor(img_textlines, cv2.COLOR_BGR2GRAY)
|
||||
##ret, thresh = cv2.threshold(imgray, 0, 255, 0)
|
||||
|
||||
contours_combined, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
##contours_combined, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
|
||||
# print(len(contours_combined),'len textlines mixed')
|
||||
areas_cnt_text = np.array([cv2.contourArea(contours_combined[j]) for j in range(len(contours_combined))])
|
||||
|
||||
contours_biggest = contours_combined[np.argmax(areas_cnt_text)]
|
||||
|
@ -186,7 +197,12 @@ def adhere_drop_capital_region_into_corresponding_textline(
|
|||
# print(np.shape(contours_biggest),'contours_biggest')
|
||||
# print(np.shape(all_found_textline_polygons[int(region_final)][arg_min]))
|
||||
##contours_biggest=contours_biggest.reshape(np.shape(contours_biggest)[0],np.shape(contours_biggest)[2])
|
||||
all_found_textline_polygons[int(region_final)][arg_min] = contours_biggest
|
||||
if len(contours_combined)==1:
|
||||
all_found_textline_polygons[int(region_final)][arg_min] = contours_biggest
|
||||
elif len(contours_combined)==2:
|
||||
all_found_textline_polygons[int(region_final)].insert(arg_min, polygons_of_drop_capitals[i_drop] )
|
||||
else:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
|
||||
|
@ -215,10 +231,11 @@ def adhere_drop_capital_region_into_corresponding_textline(
|
|||
img_textlines = cv2.fillPoly(img_textlines, pts=[polygons_of_drop_capitals[i_drop]], color=(255, 255, 255))
|
||||
|
||||
img_textlines = img_textlines.astype(np.uint8)
|
||||
imgray = cv2.cvtColor(img_textlines, cv2.COLOR_BGR2GRAY)
|
||||
ret, thresh = cv2.threshold(imgray, 0, 255, 0)
|
||||
contours_combined = return_contours_of_interested_region(img_textlines, 255, 0)
|
||||
#imgray = cv2.cvtColor(img_textlines, cv2.COLOR_BGR2GRAY)
|
||||
#ret, thresh = cv2.threshold(imgray, 0, 255, 0)
|
||||
|
||||
contours_combined, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
#contours_combined, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
|
||||
# print(len(contours_combined),'len textlines mixed')
|
||||
areas_cnt_text = np.array([cv2.contourArea(contours_combined[j]) for j in range(len(contours_combined))])
|
||||
|
@ -231,7 +248,12 @@ def adhere_drop_capital_region_into_corresponding_textline(
|
|||
contours_biggest[:, 0, 1] = contours_biggest[:, 0, 1] # -all_box_coord[int(region_final)][0]
|
||||
|
||||
##contours_biggest=contours_biggest.reshape(np.shape(contours_biggest)[0],np.shape(contours_biggest)[2])
|
||||
all_found_textline_polygons[int(region_final)][arg_min] = contours_biggest
|
||||
if len(contours_combined)==1:
|
||||
all_found_textline_polygons[int(region_final)][arg_min] = contours_biggest
|
||||
elif len(contours_combined)==2:
|
||||
all_found_textline_polygons[int(region_final)].insert(arg_min, polygons_of_drop_capitals[i_drop] )
|
||||
else:
|
||||
pass
|
||||
# all_found_textline_polygons[int(region_final)][arg_min]=contours_biggest
|
||||
|
||||
except:
|
||||
|
@ -320,10 +342,12 @@ def adhere_drop_capital_region_into_corresponding_textline(
|
|||
img_textlines = cv2.fillPoly(img_textlines, pts=[polygons_of_drop_capitals[i_drop]], color=(255, 255, 255))
|
||||
|
||||
img_textlines = img_textlines.astype(np.uint8)
|
||||
imgray = cv2.cvtColor(img_textlines, cv2.COLOR_BGR2GRAY)
|
||||
ret, thresh = cv2.threshold(imgray, 0, 255, 0)
|
||||
contours_combined = return_contours_of_interested_region(img_textlines, 255, 0)
|
||||
|
||||
#imgray = cv2.cvtColor(img_textlines, cv2.COLOR_BGR2GRAY)
|
||||
#ret, thresh = cv2.threshold(imgray, 0, 255, 0)
|
||||
|
||||
contours_combined, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
#contours_combined, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
|
||||
# print(len(contours_combined),'len textlines mixed')
|
||||
areas_cnt_text = np.array([cv2.contourArea(contours_combined[j]) for j in range(len(contours_combined))])
|
||||
|
@ -336,8 +360,12 @@ def adhere_drop_capital_region_into_corresponding_textline(
|
|||
contours_biggest[:, 0, 1] = contours_biggest[:, 0, 1] - all_box_coord[int(region_final)][0]
|
||||
|
||||
contours_biggest = contours_biggest.reshape(np.shape(contours_biggest)[0], np.shape(contours_biggest)[2])
|
||||
|
||||
all_found_textline_polygons[int(region_final)][arg_min] = contours_biggest
|
||||
if len(contours_combined)==1:
|
||||
all_found_textline_polygons[int(region_final)][arg_min] = contours_biggest
|
||||
elif len(contours_combined)==2:
|
||||
all_found_textline_polygons[int(region_final)].insert(arg_min, polygons_of_drop_capitals[i_drop] )
|
||||
else:
|
||||
pass
|
||||
|
||||
except:
|
||||
# print('gordun1')
|
||||
|
@ -375,10 +403,12 @@ def adhere_drop_capital_region_into_corresponding_textline(
|
|||
img_textlines = cv2.fillPoly(img_textlines, pts=[polygons_of_drop_capitals[i_drop]], color=(255, 255, 255))
|
||||
|
||||
img_textlines = img_textlines.astype(np.uint8)
|
||||
imgray = cv2.cvtColor(img_textlines, cv2.COLOR_BGR2GRAY)
|
||||
ret, thresh = cv2.threshold(imgray, 0, 255, 0)
|
||||
contours_combined = return_contours_of_interested_region(img_textlines, 255, 0)
|
||||
|
||||
#imgray = cv2.cvtColor(img_textlines, cv2.COLOR_BGR2GRAY)
|
||||
#ret, thresh = cv2.threshold(imgray, 0, 255, 0)
|
||||
|
||||
contours_combined, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
#contours_combined, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
|
||||
# print(len(contours_combined),'len textlines mixed')
|
||||
areas_cnt_text = np.array([cv2.contourArea(contours_combined[j]) for j in range(len(contours_combined))])
|
||||
|
@ -391,7 +421,12 @@ def adhere_drop_capital_region_into_corresponding_textline(
|
|||
contours_biggest[:, 0, 1] = contours_biggest[:, 0, 1] - all_box_coord[int(region_final)][0]
|
||||
|
||||
contours_biggest = contours_biggest.reshape(np.shape(contours_biggest)[0], np.shape(contours_biggest)[2])
|
||||
all_found_textline_polygons[int(region_final)][arg_min] = contours_biggest
|
||||
if len(contours_combined)==1:
|
||||
all_found_textline_polygons[int(region_final)][arg_min] = contours_biggest
|
||||
elif len(contours_combined)==2:
|
||||
all_found_textline_polygons[int(region_final)].insert(arg_min, polygons_of_drop_capitals[i_drop] )
|
||||
else:
|
||||
pass
|
||||
# all_found_textline_polygons[int(region_final)][arg_min]=contours_biggest
|
||||
|
||||
except:
|
||||
|
|
|
@ -2,13 +2,11 @@ import numpy as np
|
|||
import cv2
|
||||
from scipy.signal import find_peaks
|
||||
from scipy.ndimage import gaussian_filter1d
|
||||
|
||||
|
||||
from .contour import find_new_features_of_contours, return_contours_of_interested_region
|
||||
from .resize import resize_image
|
||||
from .rotate import rotate_image
|
||||
|
||||
def get_marginals(text_with_lines, text_regions, num_col, slope_deskew, kernel=None):
|
||||
def get_marginals(text_with_lines, text_regions, num_col, slope_deskew, light_version=False, kernel=None):
|
||||
mask_marginals=np.zeros((text_with_lines.shape[0],text_with_lines.shape[1]))
|
||||
mask_marginals=mask_marginals.astype(np.uint8)
|
||||
|
||||
|
@ -49,27 +47,14 @@ def get_marginals(text_with_lines, text_regions, num_col, slope_deskew, kernel=N
|
|||
if thickness_along_y_percent>=14:
|
||||
|
||||
text_with_lines_y_rev=-1*text_with_lines_y[:]
|
||||
#print(text_with_lines_y)
|
||||
#print(text_with_lines_y_rev)
|
||||
|
||||
|
||||
|
||||
|
||||
#plt.plot(text_with_lines_y)
|
||||
#plt.show()
|
||||
|
||||
|
||||
text_with_lines_y_rev=text_with_lines_y_rev-np.min(text_with_lines_y_rev)
|
||||
|
||||
#plt.plot(text_with_lines_y_rev)
|
||||
#plt.show()
|
||||
sigma_gaus=1
|
||||
region_sum_0= gaussian_filter1d(text_with_lines_y, sigma_gaus)
|
||||
|
||||
region_sum_0_rev=gaussian_filter1d(text_with_lines_y_rev, sigma_gaus)
|
||||
|
||||
#plt.plot(region_sum_0_rev)
|
||||
#plt.show()
|
||||
region_sum_0_updown=region_sum_0[len(region_sum_0)::-1]
|
||||
|
||||
first_nonzero=(next((i for i, x in enumerate(region_sum_0) if x), None))
|
||||
|
@ -78,43 +63,17 @@ def get_marginals(text_with_lines, text_regions, num_col, slope_deskew, kernel=N
|
|||
|
||||
last_nonzero=len(region_sum_0)-last_nonzero
|
||||
|
||||
##img_sum_0_smooth_rev=-region_sum_0
|
||||
|
||||
|
||||
mid_point=(last_nonzero+first_nonzero)/2.
|
||||
|
||||
|
||||
one_third_right=(last_nonzero-mid_point)/3.0
|
||||
one_third_left=(mid_point-first_nonzero)/3.0
|
||||
|
||||
#img_sum_0_smooth_rev=img_sum_0_smooth_rev-np.min(img_sum_0_smooth_rev)
|
||||
|
||||
|
||||
|
||||
|
||||
peaks, _ = find_peaks(text_with_lines_y_rev, height=0)
|
||||
|
||||
|
||||
peaks=np.array(peaks)
|
||||
|
||||
|
||||
#print(region_sum_0[peaks])
|
||||
##plt.plot(region_sum_0)
|
||||
##plt.plot(peaks,region_sum_0[peaks],'*')
|
||||
##plt.show()
|
||||
#print(first_nonzero,last_nonzero,peaks)
|
||||
peaks=peaks[(peaks>first_nonzero) & ((peaks<last_nonzero))]
|
||||
|
||||
#print(first_nonzero,last_nonzero,peaks)
|
||||
|
||||
|
||||
#print(region_sum_0[peaks]<10)
|
||||
####peaks=peaks[region_sum_0[peaks]<25 ]
|
||||
|
||||
#print(region_sum_0[peaks])
|
||||
peaks=peaks[region_sum_0[peaks]<min_textline_thickness ]
|
||||
#print(peaks)
|
||||
#print(first_nonzero,last_nonzero,one_third_right,one_third_left)
|
||||
|
||||
|
||||
if num_col==1:
|
||||
peaks_right=peaks[peaks>mid_point]
|
||||
|
@ -137,9 +96,6 @@ def get_marginals(text_with_lines, text_regions, num_col, slope_deskew, kernel=N
|
|||
|
||||
|
||||
|
||||
|
||||
#print(point_left,point_right)
|
||||
#print(text_regions.shape)
|
||||
if point_right>=mask_marginals.shape[1]:
|
||||
point_right=mask_marginals.shape[1]-1
|
||||
|
||||
|
@ -148,10 +104,8 @@ def get_marginals(text_with_lines, text_regions, num_col, slope_deskew, kernel=N
|
|||
except:
|
||||
mask_marginals[:,:]=1
|
||||
|
||||
#print(mask_marginals.shape,point_left,point_right,'nadosh')
|
||||
mask_marginals_rotated=rotate_image(mask_marginals,-slope_deskew)
|
||||
|
||||
#print(mask_marginals_rotated.shape,'nadosh')
|
||||
mask_marginals_rotated_sum=mask_marginals_rotated.sum(axis=0)
|
||||
|
||||
mask_marginals_rotated_sum[mask_marginals_rotated_sum!=0]=1
|
||||
|
@ -167,73 +121,92 @@ def get_marginals(text_with_lines, text_regions, num_col, slope_deskew, kernel=N
|
|||
if max_point_of_right_marginal>=text_regions.shape[1]:
|
||||
max_point_of_right_marginal=text_regions.shape[1]-1
|
||||
|
||||
if light_version:
|
||||
text_regions_org = np.copy(text_regions)
|
||||
text_regions[text_regions[:,:]==1]=4
|
||||
|
||||
pixel_img=4
|
||||
min_area_text=0.00001
|
||||
|
||||
polygon_mask_marginals_rotated = return_contours_of_interested_region(mask_marginals,1,min_area_text)
|
||||
|
||||
polygon_mask_marginals_rotated = polygon_mask_marginals_rotated[0]
|
||||
|
||||
#print(np.min(index_x_interest) ,np.max(index_x_interest),'minmaxnew')
|
||||
#print(mask_marginals_rotated.shape,text_regions.shape,'mask_marginals_rotated')
|
||||
#plt.imshow(mask_marginals)
|
||||
#plt.show()
|
||||
polygons_of_marginals=return_contours_of_interested_region(text_regions,pixel_img,min_area_text)
|
||||
|
||||
#plt.imshow(mask_marginals_rotated)
|
||||
#plt.show()
|
||||
cx_text_only,cy_text_only ,x_min_text_only,x_max_text_only, y_min_text_only ,y_max_text_only,y_cor_x_min_main=find_new_features_of_contours(polygons_of_marginals)
|
||||
|
||||
text_regions[(mask_marginals_rotated[:,:]!=1) & (text_regions[:,:]==1)]=4
|
||||
text_regions[(text_regions[:,:]==4)]=1
|
||||
|
||||
#plt.imshow(text_regions)
|
||||
#plt.show()
|
||||
marginlas_should_be_main_text=[]
|
||||
|
||||
pixel_img=4
|
||||
min_area_text=0.00001
|
||||
polygons_of_marginals=return_contours_of_interested_region(text_regions,pixel_img,min_area_text)
|
||||
x_min_marginals_left=[]
|
||||
x_min_marginals_right=[]
|
||||
|
||||
cx_text_only,cy_text_only ,x_min_text_only,x_max_text_only, y_min_text_only ,y_max_text_only,y_cor_x_min_main=find_new_features_of_contours(polygons_of_marginals)
|
||||
for i in range(len(cx_text_only)):
|
||||
results = cv2.pointPolygonTest(polygon_mask_marginals_rotated, (cx_text_only[i], cy_text_only[i]), False)
|
||||
|
||||
text_regions[(text_regions[:,:]==4)]=1
|
||||
if results == -1:
|
||||
marginlas_should_be_main_text.append(polygons_of_marginals[i])
|
||||
|
||||
marginlas_should_be_main_text=[]
|
||||
|
||||
x_min_marginals_left=[]
|
||||
x_min_marginals_right=[]
|
||||
|
||||
for i in range(len(cx_text_only)):
|
||||
text_regions_org=cv2.fillPoly(text_regions_org, pts =marginlas_should_be_main_text, color=(4,4))
|
||||
text_regions = np.copy(text_regions_org)
|
||||
|
||||
|
||||
x_width_mar=abs(x_min_text_only[i]-x_max_text_only[i])
|
||||
y_height_mar=abs(y_min_text_only[i]-y_max_text_only[i])
|
||||
#print(x_width_mar,y_height_mar,y_height_mar/x_width_mar,'y_height_mar')
|
||||
if x_width_mar>16 and y_height_mar/x_width_mar<18:
|
||||
marginlas_should_be_main_text.append(polygons_of_marginals[i])
|
||||
if x_min_text_only[i]<(mid_point-one_third_left):
|
||||
x_min_marginals_left_new=x_min_text_only[i]
|
||||
if len(x_min_marginals_left)==0:
|
||||
x_min_marginals_left.append(x_min_marginals_left_new)
|
||||
else:
|
||||
|
||||
text_regions[(mask_marginals_rotated[:,:]!=1) & (text_regions[:,:]==1)]=4
|
||||
|
||||
pixel_img=4
|
||||
min_area_text=0.00001
|
||||
|
||||
polygons_of_marginals=return_contours_of_interested_region(text_regions,pixel_img,min_area_text)
|
||||
|
||||
cx_text_only,cy_text_only ,x_min_text_only,x_max_text_only, y_min_text_only ,y_max_text_only,y_cor_x_min_main=find_new_features_of_contours(polygons_of_marginals)
|
||||
|
||||
text_regions[(text_regions[:,:]==4)]=1
|
||||
|
||||
marginlas_should_be_main_text=[]
|
||||
|
||||
x_min_marginals_left=[]
|
||||
x_min_marginals_right=[]
|
||||
|
||||
for i in range(len(cx_text_only)):
|
||||
x_width_mar=abs(x_min_text_only[i]-x_max_text_only[i])
|
||||
y_height_mar=abs(y_min_text_only[i]-y_max_text_only[i])
|
||||
|
||||
if x_width_mar>16 and y_height_mar/x_width_mar<18:
|
||||
marginlas_should_be_main_text.append(polygons_of_marginals[i])
|
||||
if x_min_text_only[i]<(mid_point-one_third_left):
|
||||
x_min_marginals_left_new=x_min_text_only[i]
|
||||
if len(x_min_marginals_left)==0:
|
||||
x_min_marginals_left.append(x_min_marginals_left_new)
|
||||
else:
|
||||
x_min_marginals_left[0]=min(x_min_marginals_left[0],x_min_marginals_left_new)
|
||||
else:
|
||||
x_min_marginals_left[0]=min(x_min_marginals_left[0],x_min_marginals_left_new)
|
||||
else:
|
||||
x_min_marginals_right_new=x_min_text_only[i]
|
||||
if len(x_min_marginals_right)==0:
|
||||
x_min_marginals_right.append(x_min_marginals_right_new)
|
||||
else:
|
||||
x_min_marginals_right[0]=min(x_min_marginals_right[0],x_min_marginals_right_new)
|
||||
x_min_marginals_right_new=x_min_text_only[i]
|
||||
if len(x_min_marginals_right)==0:
|
||||
x_min_marginals_right.append(x_min_marginals_right_new)
|
||||
else:
|
||||
x_min_marginals_right[0]=min(x_min_marginals_right[0],x_min_marginals_right_new)
|
||||
|
||||
if len(x_min_marginals_left)==0:
|
||||
x_min_marginals_left=[0]
|
||||
if len(x_min_marginals_right)==0:
|
||||
x_min_marginals_right=[text_regions.shape[1]-1]
|
||||
if len(x_min_marginals_left)==0:
|
||||
x_min_marginals_left=[0]
|
||||
if len(x_min_marginals_right)==0:
|
||||
x_min_marginals_right=[text_regions.shape[1]-1]
|
||||
|
||||
|
||||
text_regions=cv2.fillPoly(text_regions, pts =marginlas_should_be_main_text, color=(4,4))
|
||||
|
||||
|
||||
#print(x_min_marginals_left[0],x_min_marginals_right[0],'margo')
|
||||
|
||||
#print(marginlas_should_be_main_text,'marginlas_should_be_main_text')
|
||||
text_regions=cv2.fillPoly(text_regions, pts =marginlas_should_be_main_text, color=(4,4))
|
||||
|
||||
#print(np.unique(text_regions))
|
||||
|
||||
#text_regions[:,:int(x_min_marginals_left[0])][text_regions[:,:int(x_min_marginals_left[0])]==1]=0
|
||||
#text_regions[:,int(x_min_marginals_right[0]):][text_regions[:,int(x_min_marginals_right[0]):]==1]=0
|
||||
|
||||
text_regions[:,:int(min_point_of_left_marginal)][text_regions[:,:int(min_point_of_left_marginal)]==1]=0
|
||||
text_regions[:,int(max_point_of_right_marginal):][text_regions[:,int(max_point_of_right_marginal):]==1]=0
|
||||
#text_regions[:,:int(x_min_marginals_left[0])][text_regions[:,:int(x_min_marginals_left[0])]==1]=0
|
||||
#text_regions[:,int(x_min_marginals_right[0]):][text_regions[:,int(x_min_marginals_right[0]):]==1]=0
|
||||
|
||||
|
||||
text_regions[:,:int(min_point_of_left_marginal)][text_regions[:,:int(min_point_of_left_marginal)]==1]=0
|
||||
text_regions[:,int(max_point_of_right_marginal):][text_regions[:,int(max_point_of_right_marginal):]==1]=0
|
||||
|
||||
###text_regions[:,0:point_left][text_regions[:,0:point_left]==1]=4
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from contextlib import nullcontext
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
from ocrd_models import OcrdExif
|
||||
|
@ -17,12 +18,13 @@ def pil2cv(img):
|
|||
def check_dpi(img):
|
||||
try:
|
||||
if isinstance(img, Image.Image):
|
||||
pil_image = img
|
||||
pil_image = nullcontext(img)
|
||||
elif isinstance(img, str):
|
||||
pil_image = Image.open(img)
|
||||
else:
|
||||
pil_image = cv2pil(img)
|
||||
exif = OcrdExif(pil_image)
|
||||
pil_image = nullcontext(cv2pil(img))
|
||||
with pil_image:
|
||||
exif = OcrdExif(pil_image)
|
||||
resolution = exif.resolution
|
||||
if resolution == 1:
|
||||
raise Exception()
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -72,7 +72,7 @@ def order_and_id_of_texts(found_polygons_text_region, found_polygons_text_region
|
|||
|
||||
index_of_types_2 = index_of_types[kind_of_texts == 2]
|
||||
indexes_sorted_2 = indexes_sorted[kind_of_texts == 2]
|
||||
|
||||
|
||||
counter = EynollahIdCounter(region_idx=ref_point)
|
||||
for idx_textregion, _ in enumerate(found_polygons_text_region):
|
||||
id_of_texts.append(counter.next_region_id)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# pylint: disable=import-error
|
||||
from pathlib import Path
|
||||
import os.path
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
from .utils.xml import create_page_xml, xml_reading_order
|
||||
from .utils.counter import EynollahIdCounter
|
||||
|
||||
|
@ -12,6 +12,7 @@ from ocrd_models.ocrd_page import (
|
|||
CoordsType,
|
||||
PcGtsType,
|
||||
TextLineType,
|
||||
TextEquivType,
|
||||
TextRegionType,
|
||||
ImageRegionType,
|
||||
TableRegionType,
|
||||
|
@ -27,6 +28,7 @@ class EynollahXmlWriter():
|
|||
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.textline_light = textline_light
|
||||
self.pcgts = pcgts
|
||||
|
@ -59,6 +61,7 @@ class EynollahXmlWriter():
|
|||
coords = CoordsType()
|
||||
textline = TextLineType(id=counter.next_line_id, Coords=coords)
|
||||
marginal_region.add_TextLine(textline)
|
||||
marginal_region.set_orientation(-slopes_marginals[marginal_idx])
|
||||
points_co = ''
|
||||
for l in range(len(all_found_textline_polygons_marginals[marginal_idx][j])):
|
||||
if not (self.curved_line or self.textline_light):
|
||||
|
@ -93,12 +96,15 @@ class EynollahXmlWriter():
|
|||
points_co += ' '
|
||||
coords.set_points(points_co[:-1])
|
||||
|
||||
def serialize_lines_in_region(self, text_region, all_found_textline_polygons, region_idx, page_coord, all_box_coord, slopes, counter):
|
||||
def serialize_lines_in_region(self, text_region, all_found_textline_polygons, region_idx, page_coord, all_box_coord, slopes, counter, ocr_all_textlines_textregion):
|
||||
self.logger.debug('enter serialize_lines_in_region')
|
||||
for j in range(len(all_found_textline_polygons[region_idx])):
|
||||
coords = CoordsType()
|
||||
textline = TextLineType(id=counter.next_line_id, Coords=coords)
|
||||
if ocr_all_textlines_textregion:
|
||||
textline.set_TextEquiv( [ TextEquivType(Unicode=ocr_all_textlines_textregion[j]) ] )
|
||||
text_region.add_TextLine(textline)
|
||||
text_region.set_orientation(-slopes[region_idx])
|
||||
region_bboxes = all_box_coord[region_idx]
|
||||
points_co = ''
|
||||
for idx_contour_textline, contour_textline in enumerate(all_found_textline_polygons[region_idx][j]):
|
||||
|
@ -133,14 +139,36 @@ class EynollahXmlWriter():
|
|||
points_co += str(int((contour_textline[0][1] + region_bboxes[0]+page_coord[0])/self.scale_y))
|
||||
points_co += ' '
|
||||
coords.set_points(points_co[:-1])
|
||||
|
||||
def serialize_lines_in_dropcapital(self, text_region, all_found_textline_polygons, region_idx, page_coord, all_box_coord, slopes, counter, ocr_all_textlines_textregion):
|
||||
self.logger.debug('enter serialize_lines_in_region')
|
||||
for j in range(1):
|
||||
coords = CoordsType()
|
||||
textline = TextLineType(id=counter.next_line_id, Coords=coords)
|
||||
if ocr_all_textlines_textregion:
|
||||
textline.set_TextEquiv( [ TextEquivType(Unicode=ocr_all_textlines_textregion[j]) ] )
|
||||
text_region.add_TextLine(textline)
|
||||
#region_bboxes = all_box_coord[region_idx]
|
||||
points_co = ''
|
||||
for idx_contour_textline, contour_textline in enumerate(all_found_textline_polygons[j]):
|
||||
if len(contour_textline) == 2:
|
||||
points_co += str(int((contour_textline[0] + page_coord[2]) / self.scale_x))
|
||||
points_co += ','
|
||||
points_co += str(int((contour_textline[1] + page_coord[0]) / self.scale_y))
|
||||
else:
|
||||
points_co += str(int((contour_textline[0][0] + page_coord[2]) / self.scale_x))
|
||||
points_co += ','
|
||||
points_co += str(int((contour_textline[0][1] + page_coord[0])/self.scale_y))
|
||||
|
||||
points_co += ' '
|
||||
coords.set_points(points_co[:-1])
|
||||
|
||||
def write_pagexml(self, pcgts):
|
||||
out_fname = os.path.join(self.dir_out, self.image_filename_stem) + ".xml"
|
||||
self.logger.info("output filename: '%s'", out_fname)
|
||||
with open(out_fname, 'w') as f:
|
||||
self.logger.info("output filename: '%s'", self.output_filename)
|
||||
with open(self.output_filename, 'w') as f:
|
||||
f.write(to_xml(pcgts))
|
||||
|
||||
def build_pagexml_no_full_layout(self, found_polygons_text_region, page_coord, order_of_texts, id_of_texts, all_found_textline_polygons, all_box_coord, found_polygons_text_region_img, found_polygons_marginals, all_found_textline_polygons_marginals, all_box_coord_marginals, slopes, slopes_marginals, cont_page, polygons_lines_to_be_written_in_xml, found_polygons_tables):
|
||||
def build_pagexml_no_full_layout(self, found_polygons_text_region, page_coord, order_of_texts, id_of_texts, all_found_textline_polygons, all_box_coord, found_polygons_text_region_img, found_polygons_marginals, all_found_textline_polygons_marginals, all_box_coord_marginals, slopes, slopes_marginals, cont_page, polygons_lines_to_be_written_in_xml, found_polygons_tables, ocr_all_textlines):
|
||||
self.logger.debug('enter build_pagexml_no_full_layout')
|
||||
|
||||
# create the file structure
|
||||
|
@ -159,7 +187,11 @@ class EynollahXmlWriter():
|
|||
Coords=CoordsType(points=self.calculate_polygon_coords(found_polygons_text_region[mm], page_coord)),
|
||||
)
|
||||
page.add_TextRegion(textregion)
|
||||
self.serialize_lines_in_region(textregion, all_found_textline_polygons, mm, page_coord, all_box_coord, slopes, counter)
|
||||
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, all_box_coord, slopes, counter, ocr_textlines)
|
||||
|
||||
for mm in range(len(found_polygons_marginals)):
|
||||
marginal = TextRegionType(id=counter.next_region_id, type_='marginalia',
|
||||
|
@ -209,7 +241,7 @@ class EynollahXmlWriter():
|
|||
|
||||
return pcgts
|
||||
|
||||
def build_pagexml_full_layout(self, found_polygons_text_region, found_polygons_text_region_h, page_coord, order_of_texts, id_of_texts, all_found_textline_polygons, all_found_textline_polygons_h, all_box_coord, all_box_coord_h, found_polygons_text_region_img, found_polygons_tables, found_polygons_drop_capitals, found_polygons_marginals, all_found_textline_polygons_marginals, all_box_coord_marginals, slopes, slopes_h, slopes_marginals, cont_page, polygons_lines_to_be_written_in_xml):
|
||||
def build_pagexml_full_layout(self, found_polygons_text_region, found_polygons_text_region_h, page_coord, order_of_texts, id_of_texts, all_found_textline_polygons, all_found_textline_polygons_h, all_box_coord, all_box_coord_h, found_polygons_text_region_img, found_polygons_tables, found_polygons_drop_capitals, found_polygons_marginals, all_found_textline_polygons_marginals, all_box_coord_marginals, slopes, slopes_h, slopes_marginals, cont_page, polygons_lines_to_be_written_in_xml, ocr_all_textlines):
|
||||
self.logger.debug('enter build_pagexml_full_layout')
|
||||
|
||||
# create the file structure
|
||||
|
@ -226,14 +258,24 @@ class EynollahXmlWriter():
|
|||
textregion = TextRegionType(id=counter.next_region_id, type_='paragraph',
|
||||
Coords=CoordsType(points=self.calculate_polygon_coords(found_polygons_text_region[mm], page_coord)))
|
||||
page.add_TextRegion(textregion)
|
||||
self.serialize_lines_in_region(textregion, all_found_textline_polygons, mm, page_coord, all_box_coord, slopes, counter)
|
||||
|
||||
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, all_box_coord, slopes, counter, ocr_textlines)
|
||||
|
||||
self.logger.debug('len(found_polygons_text_region_h) %s', len(found_polygons_text_region_h))
|
||||
for mm in range(len(found_polygons_text_region_h)):
|
||||
textregion = TextRegionType(id=counter.next_region_id, type_='header',
|
||||
Coords=CoordsType(points=self.calculate_polygon_coords(found_polygons_text_region_h[mm], page_coord)))
|
||||
page.add_TextRegion(textregion)
|
||||
self.serialize_lines_in_region(textregion, all_found_textline_polygons_h, mm, page_coord, all_box_coord_h, slopes_h, counter)
|
||||
|
||||
if ocr_all_textlines:
|
||||
ocr_textlines = ocr_all_textlines[mm]
|
||||
else:
|
||||
ocr_textlines = None
|
||||
self.serialize_lines_in_region(textregion, all_found_textline_polygons_h, mm, page_coord, all_box_coord_h, slopes_h, counter, ocr_textlines)
|
||||
|
||||
for mm in range(len(found_polygons_marginals)):
|
||||
marginal = TextRegionType(id=counter.next_region_id, type_='marginalia',
|
||||
|
@ -242,8 +284,12 @@ class EynollahXmlWriter():
|
|||
self.serialize_lines_in_marginal(marginal, all_found_textline_polygons_marginals, mm, page_coord, all_box_coord_marginals, slopes_marginals, counter)
|
||||
|
||||
for mm in range(len(found_polygons_drop_capitals)):
|
||||
page.add_TextRegion(TextRegionType(id=counter.next_region_id, type_='drop-capital',
|
||||
Coords=CoordsType(points=self.calculate_polygon_coords(found_polygons_drop_capitals[mm], page_coord))))
|
||||
dropcapital = TextRegionType(id=counter.next_region_id, type_='drop-capital',
|
||||
Coords=CoordsType(points=self.calculate_polygon_coords(found_polygons_drop_capitals[mm], page_coord)))
|
||||
page.add_TextRegion(dropcapital)
|
||||
###all_box_coord_drop = None
|
||||
###slopes_drop = None
|
||||
###self.serialize_lines_in_dropcapital(dropcapital, [found_polygons_drop_capitals[mm]], mm, page_coord, all_box_coord_drop, slopes_drop, counter, ocr_all_textlines_textregion=None)
|
||||
|
||||
for mm in range(len(found_polygons_text_region_img)):
|
||||
page.add_ImageRegion(ImageRegionType(id=counter.next_region_id, Coords=CoordsType(points=self.calculate_polygon_coords(found_polygons_text_region_img[mm], page_coord))))
|
||||
|
|
BIN
tests/resources/euler_rechenkunst01_1738_0025.tif
Normal file
BIN
tests/resources/euler_rechenkunst01_1738_0025.tif
Normal file
Binary file not shown.
|
@ -2,7 +2,7 @@ from os import environ
|
|||
from pathlib import Path
|
||||
from ocrd_utils import pushd_popd
|
||||
from tests.base import CapturingTestCase as TestCase, main
|
||||
from eynollah.cli import main as eynollah_cli
|
||||
from eynollah.cli import layout as eynollah_cli
|
||||
|
||||
testdir = Path(__file__).parent.resolve()
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue