Compare commits

...
29 Commits
Author SHA1 Message Date
James R. Barlow 7b94129d9e v4.5 notes 2017-02-14 13:03:48 -08:00
James R. Barlow d1a0065ef8 Create test case for Form XObjects 2017-02-14 12:51:15 -08:00
James R. Barlow 5a817370fd Warn more strongly about —pdf-renderer tesseract until fix is widely propagated 2017-02-14 11:33:07 -08:00
James R. Barlow ab0a210763 Update dockerfile.tess4 yet again
Installing Tess4 PPA over Tess3 proved too much pain, so sever the link
between this and the jbarlow83/ocrmypdf image, starting each from
scratch. Also the complete set of language packs proves too much - the
build seems likely to fail when trying to install so many.
2017-02-14 11:32:05 -08:00
James R. Barlow 9f800736bc Fix running_in_docker() check failing on newer Docker
This test has to work to ensure spoof/tesseract_cache.py has a writable
directory to put cache into. Otherwise those tests fail.
2017-02-13 02:16:06 -08:00
James R. Barlow c9a83afad6 Improve batch processing examples 2017-02-13 02:14:32 -08:00
James R. Barlow 5e14274f10 pageinfo: learn to extract image information from Form XObjects 2017-02-11 16:48:59 -08:00
James R. Barlow 167470b4bd Re-fix Dockerfile.tess4
[ci skip]
2017-02-10 15:43:49 -08:00
James R. Barlow f06d3c2ec2 Fix tesseract 3.04 on tesseract 4 on image
[skip ci]
2017-02-10 08:38:22 -08:00
James R. Barlow 74c99a8a77 v4.4.2 release notes 2017-02-06 21:56:55 -08:00
James R. Barlow 0e4d312ee2 Adjust Travis deploy to PyPI settings
-only on master branch
-only Python 3.6 build uploads, so the others don’t compete
-don’t upload docs to PyPI
2017-02-06 21:27:59 -08:00
James R. Barlow 589f19559d Rewrite Dockerfiles to use ubuntu 16.10 base system
Debian now has a few disadvantages:
-there is no convenient PPA for Debian tesseract 4.0, but there is for
Ubuntu
-Ubuntu sets locale to UTF-8 automatically removing the need to do this

All three ocrmypdf docker images are now based on a common Ubuntu
16.10 image, derived from the one used to build ocrmypdf-tess4.
-polyglot now differs from -tess4 only by opting into the tess4 PPA.

Both Ubuntu 16.10 and Debian stretch use tesseract 3.04.01 now making
the sharp.ttf patch unnecessary. /etc/apt/sources has been unused for a
while now both have newer Ghostscripts.
2017-02-06 14:39:29 -08:00
James R. Barlow f28bc25dc0 Configure travis to handle deployment to PyPI; also lint .travis.yml 2017-02-06 13:50:53 -08:00
James R. Barlow a0657ad937 Prevent use of —pdf-renderer tess4 on tesseract 3 2017-02-06 13:49:43 -08:00
James R. Barlow 5b8d88af4c Suggest use of aliases to hide docker run 2017-01-30 15:08:02 -08:00
James R. Barlow fa82b50340 Adding missing file Dockerfile.tess4 2017-01-29 18:34:01 -08:00
James R. Barlow 005216bc57 Support ocrmypdf-tess4 2017-01-29 18:26:52 -08:00
James R. Barlow e748fdcf6f v4.4.1 release notes 2017-01-28 22:23:35 -08:00
James R. Barlow 8c17c9918e Add documentation and test cases for —tesseract-config
This parameter has existed for along time but never really got any
attention.
2017-01-28 22:06:51 -08:00
James R. Barlow ea0dd99d0b More documentation updates 2017-01-28 15:35:59 -08:00
James R. Barlow e0cc67afae docs: suggest —oem 1 2017-01-28 14:58:25 -08:00
James R. Barlow 04f9cbe364 Describe how to use tesseract 4.0 while 3.04 is installed 2017-01-27 18:13:59 -08:00
James R. Barlow 99afebd033 tesseract jobs_limit(2)
At least on macOS with my quadcore performance improves with two
tesseracts in parallel (20% gain). Hard to say how this will affect
Linux, but stepping up to 2 jobs seems justifiable.
2017-01-27 18:13:12 -08:00
James R. Barlow a6feacc810 travis: fix ‘pip install’ by moving working code out of the way 2017-01-27 14:33:23 -08:00
James R. Barlow 65e4b1672f cffi: verbose=True 2017-01-27 14:17:13 -08:00
James R. Barlow 46cc0dd190 Revert "Do we need to exclude ocrmypdf.lib?"
This reverts commit 678b9fb603.
2017-01-27 13:51:30 -08:00
James R. Barlow 678b9fb603 Do we need to exclude ocrmypdf.lib? 2017-01-27 13:49:11 -08:00
James R. Barlow 49ab0c1f0b setup.py: cffi is definitely needed in setup_requires 2017-01-27 13:43:47 -08:00
James R. Barlow ab490a7736 Experiment: update *requirements.txt, use more current travis build steps
Perhaps this works around the pip/setup.py asymmetry that broke the
4.4 release.
2017-01-27 13:13:14 -08:00
32 changed files with 748 additions and 346 deletions
+5 -2
View File
@@ -11,11 +11,14 @@
*/*/*/*/*/*/*.pyc
*/*/*/*/*/*/*/*.pyc
.cache/
.eggs/
.github/
.git/
.ipynb_checkpoints/
.ruffus_history.sqlite
bin/
build/
docs/
dist/
htmlcov/
include/
@@ -27,5 +30,5 @@ tests/cache/
tests/output/
tests/resources/private/
tmp/
venv-3.4/
venv-3.5/
venv*/
wheelhouse/
+47 -38
View File
@@ -1,46 +1,55 @@
sudo: required
dist: trusty
language: python
cache: pip
cache:
directories:
- $HOME/.ccache
- packages
- tests/cache
pip: true
directories:
- packages
- tests/cache
python:
- "3.4"
- "3.5"
- "3.6"
before_cache:
- rm -f $HOME/.cache/pip/log/debug.log
before_install:
# Ubuntu packages
- sudo add-apt-repository ppa:vshn/ghostscript -y # for ghostscript 9.16 (trusty has 9.10)
- sudo add-apt-repository ppa:heyarje/libav-11 -y # for libav11, which is for unpaper
- sudo apt-get update -qq # must go after all add-apt-repo
- sudo apt-get install -y ghostscript tesseract-ocr tesseract-ocr-deu tesseract-ocr-eng tesseract-ocr-fra qpdf poppler-utils libavformat56 libavcodec56 libavutil54 libffi-dev
# pip
- pip install --upgrade pip
# Download, make and install unpaper (using ccache)
- mkdir -p packages
- "[ -f packages/unpaper_6.1-1.deb ] || wget -q https://dl.dropboxusercontent.com/u/28971240/unpaper_6.1-1.deb -O packages/unpaper_6.1-1.deb"
- sudo dpkg -i packages/unpaper_6.1-1.deb
install:
- pip install -r requirements.txt
- pip install -r test_requirements.txt
script:
- python setup.py clean
- python setup.py install
- python setup.py test
- '3.4'
- '3.5'
- '3.6'
os:
- linux
- linux
before_cache:
- rm -f $HOME/.cache/pip/log/debug.log
before_install:
- sudo add-apt-repository ppa:vshn/ghostscript -y
- sudo add-apt-repository ppa:heyarje/libav-11 -y
- sudo apt-get update -qq
- sudo apt-get install -y ghostscript tesseract-ocr tesseract-ocr-deu tesseract-ocr-eng
tesseract-ocr-fra qpdf poppler-utils libavformat56 libavcodec56 libavutil54 libffi-dev
- pip install --upgrade pip
- mkdir -p packages
- "[ -f packages/unpaper_6.1-1.deb ] || wget -q https://dl.dropboxusercontent.com/u/28971240/unpaper_6.1-1.deb
-O packages/unpaper_6.1-1.deb"
- sudo dpkg -i packages/unpaper_6.1-1.deb
install:
- pip install .
- pip install -r requirements.txt -r test_requirements.txt
script:
- mv ocrmypdf dont_import_this_ocrmypdf
- pytest
- mv dont_import_this_ocrmypdf ocrmypdf
# See https://www.appneta.com/blog/pypi-deployment-with-travis-ci/ for
# steps to set up testpypi deploy for untagged builds if desired
deploy:
provider: pypi
user: ocrmypdf-travis
password:
secure: DTFOmmNL6olA0+yXvp4u9jXZlZeqrJsJ0526jzqf4a3gZ6jnGTq5UI6WzRsslSyoMMfXKtHQebqHM6ogSgCZinyZ3ufHJo8fn9brxbEc2gsiWkbj5o3bGwdWMT1vNNE7XW0VCpw87rZ1EEwjl4FJHFudMlPR1yfU5+uq0k0PACo=
distributions: "sdist bdist_wheel"
on:
branch: master
tags: true
condition: $TRAVIS_PYTHON_VERSION = "3.6"
skip_upload_docs: true
+41 -78
View File
@@ -1,91 +1,54 @@
# OCRmyPDF
#
# VERSION 3.2
FROM debian:stretch
# VERSION 4.4.2
FROM ubuntu:16.10
MAINTAINER James R. Barlow <jim@purplerock.ca>
# Add unprivileged user
RUN apt-get update && apt-get install -y --no-install-recommends \
software-properties-common python-software-properties \
python3-wheel \
python3-reportlab \
python3-venv \
ghostscript \
qpdf \
poppler-utils \
unpaper \
libffi-dev \
tesseract-ocr \
tesseract-ocr-eng \
tesseract-ocr-fra \
tesseract-ocr-spa \
tesseract-ocr-deu
RUN python3 -m venv --system-site-packages /appenv
# This installs the latest binary wheel instead of the code in the current
# folder. Installing from source will fail, apparently because cffi needs
# build-essentials (gcc) to do a source installation
# (i.e. "pip install ."). It's unclear to me why this is the case.
RUN . /appenv/bin/activate; \
pip install --upgrade pip \
&& pip install ocrmypdf
# Now copy the application in, mainly to get the test suite.
# Do this now to make the best use of Docker cache.
COPY . /application
RUN . /appenv/bin/activate; \
pip install -r /application/test_requirements.txt
# Remove the junk, including the source version of application since it was
# already installed
RUN rm -rf /tmp/* /var/tmp/* /root/* /application/ocrmypdf \
&& apt-get autoremove -y \
&& apt-get autoclean -y
RUN useradd docker \
&& mkdir /home/docker \
&& chown docker:docker /home/docker
# Update system and install our dependencies
# If this command takes too Docker hub's automated build will timeout,
# so try it in portions
RUN apt-get update && apt-get install -y --no-install-recommends \
locales \
python3 \
python3-pip \
python3-venv \
python3-reportlab \
python3-pil \
python3-wheel
RUN apt-get install -y --no-install-recommends \
unpaper \
qpdf \
poppler-utils \
tesseract-ocr \
tesseract-ocr-deu tesseract-ocr-spa tesseract-ocr-eng tesseract-ocr-fra
RUN apt-get install -qy --no-install-recommends \
libffi-dev \
libpython3-dev \
gcc
# Install Ghostscript from Debian sid to work around JPEG 2000 issue in
# Debian stretch libgs9 or gs 9.16~dfsg-2.1
COPY ./share/etc-apt-sources.list /etc/apt/sources.list
RUN apt-get update && apt-get install -y ghostscript/sid
# Enforce UTF-8
# Borrowed from https://index.docker.io/u/crosbymichael/python/
RUN dpkg-reconfigure locales && \
locale-gen C.UTF-8 && \
/usr/sbin/update-locale LANG=C.UTF-8
ENV LC_ALL C.UTF-8
# Set up a Python virtualenv and take all of the system packages, so we can
# rely on the platform packages rather than importing GCC and compiling them
RUN pyvenv /appenv \
&& pyvenv --system-site-packages /appenv
COPY . /application/
# Replace stock Tesseract 3.04.00 font with improved sharp2.ttf that resolves
# issues in many PDF viewers.
# Discussion is in https://github.com/tesseract-ocr/tesseract/issues/182
COPY ./share/sharp2.ttf /usr/share/tesseract-ocr/tessdata/pdf.ttf
RUN chmod 644 /usr/share/tesseract-ocr/tessdata/pdf.ttf
# Set this here to force a docker version, allowing non-tagged versions to
# be built
# ENV SETUPTOOLS_SCM_PRETEND_VERSION=v3.3.0
# Install application and dependencies
# In this arrangement Pillow and reportlab will be provided by the system
# Even though ocrmypdf is locally present, pull from PyPI because
# Dockerhub and setuptools_scm clash
RUN . /appenv/bin/activate; \
pip install --upgrade pip \
&& pip install ocrmypdf \
&& pip install --no-cache-dir -r /application/test_requirements.txt
# Remove the junk
RUN apt-get remove -qy gcc
RUN apt-get autoremove -y && apt-get clean -y
RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /root/*
USER docker
WORKDIR /home/docker
ENV OCRMYPDF_TEST_OUTPUT=/tmp/test-output
ENV OCRMYPDF_SHARP_TTF=1
# Must use array form of ENTRYPOINT
# Non-array form does not append other arguments, because that is "intuitive"
ENTRYPOINT ["/application/docker-wrapper.sh"]
ENTRYPOINT ["/application/docker-wrapper.sh"]
+5 -2
View File
@@ -1,14 +1,17 @@
# OCRmyPDF polyglot
#
# VERSION 3.2
# VERSION 4.4.2
FROM jbarlow83/ocrmypdf:latest
MAINTAINER James R. Barlow <jim@purplerock.ca>
# Update system and install our dependencies
USER root
# Update system and install our dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
tesseract-ocr-all
RUN apt-get autoremove -y && apt-get clean -y
USER docker
# Must use array form of ENTRYPOINT
+64
View File
@@ -0,0 +1,64 @@
# OCRmyPDF
#
# VERSION 4.4.2
FROM ubuntu:16.10
MAINTAINER James R. Barlow <jim@purplerock.ca>
RUN apt-get update && apt-get install -y --no-install-recommends \
software-properties-common python-software-properties \
python3-wheel \
python3-reportlab \
python3-venv \
ghostscript \
qpdf \
poppler-utils \
unpaper \
libffi-dev
RUN add-apt-repository ppa:alex-p/tesseract-ocr
RUN apt-get update \
&& apt-get autoremove -y \
&& apt-get install -y --no-install-recommends \
tesseract-ocr \
tesseract-ocr-eng \
tesseract-ocr-fra \
tesseract-ocr-deu \
tesseract-ocr-spa \
tesseract-ocr-por \
tesseract-ocr-ara \
tesseract-ocr-rus \
tesseract-ocr-chi-sim
RUN python3 -m venv --system-site-packages /appenv
# This installs the latest binary wheel instead of the code in the current
# folder. Installing from source will fail, apparently because cffi needs
# build-essentials (gcc) to do a source installation
# (i.e. "pip install ."). It's unclear to me why this is the case.
RUN . /appenv/bin/activate; \
pip install --upgrade pip \
&& pip install ocrmypdf
# Now copy the application in, mainly to get the test suite.
# Do this now to make the best use of Docker cache.
COPY . /application
RUN . /appenv/bin/activate; \
pip install -r /application/test_requirements.txt
# Remove the junk, including the source version of application since it was
# already installed
RUN rm -rf /tmp/* /var/tmp/* /root/* /application/ocrmypdf \
&& apt-get autoremove -y \
&& apt-get autoclean -y
RUN useradd docker \
&& mkdir /home/docker \
&& chown docker:docker /home/docker
USER docker
WORKDIR /home/docker
# Must use array form of ENTRYPOINT
# Non-array form does not append other arguments, because that is "intuitive"
ENTRYPOINT ["/application/docker-wrapper.sh"]
-1
View File
@@ -36,7 +36,6 @@ recursive-exclude docs/_build *
# support files
recursive-include ocrmypdf/data *
recursive-include share *
include *.py
exclude tasks.py
+92 -64
View File
@@ -3,14 +3,42 @@ RELEASE NOTES
OCRmyPDF uses `semantic versioning <http://semver.org/>`_.
v4.4:
=====
v4.5
====
- Exotic PDFs containing "Form XObjects" are now supported (issue #134; PDF reference manual 8.10), and images they contain are taken into account when determining the resolution for rasterizing
- The Tesseract 4 Docker image no longer includes all languages, because it took so long to build something would tend to fail
- OCRmyPDF now warns about using ``--pdf-renderer tesseract`` with Tesseract 3.04 or lower due to issues with Ghostscript corrupting the OCR text in these cases
v4.4.2
======
- The Docker images (ocrmypdf, ocrmypdf-polyglot, ocrmypdf-tess4) are now based on Ubuntu 16.10 instead of Debian stretch
+ This makes supporting the Tesseract 4 image easier
+ This could be a disruptive change for any Docker users who built customized these images with their own changes, and made those changes in a way that depends on Debian and not Ubuntu
- OCRmyPDF now prevents running the Tesseract 4 renderer with Tesseract 3.04, which was permitted in v4.4 and v4.4.1 but will not work
v4.4.1
======
- To prevent a `TIFF output error <https://github.com/python-pillow/Pillow/issues/2206>`_ caused by img2pdf >= 0.2.1 and Pillow <= 3.4.2, dependencies have been tightened
- The Tesseract 4.00 simultaneous process limit was increased from 1 to 2, since it was observed that 1 lowers performance
- Documentation improvements to describe the ``--tesseract-config`` feature
- Added test cases and fixed error handling for ``--tesseract-config``
- Tweaks to setup.py to deal with issues in the v4.4 release
v4.4
====
- Tesseract 4.00 is now supported on an experimental basis.
+ A new rendering option ``--pdf-renderer tess4`` exploits Tesseract 4's new text-only output PDF mode. See the documentation on PDF Renderers for details.
+ The ``--tesseract-oem`` argument allows control over the Tesseract 4 OCR
engine mode (tesseract's ``--oem``). Use ``--tesseract-oem 2`` to enforce the new LSTM mode.
+ The ``--tesseract-oem`` argument allows control over the Tesseract 4 OCR engine mode (tesseract's ``--oem``). Use ``--tesseract-oem 2`` to enforce the new LSTM mode.
+ Fixed poor performance with Tesseract 4.00 on Linux
- Fixed an issue that caused corruption of output to stdout in some cases
@@ -22,33 +50,33 @@ v4.4:
+ However, OCRmyPDF's dependency "ruffus" is not re-entrant, so no Python API is available. Scripts should continue to use the command line interface.
v4.3.5:
=======
v4.3.5
======
- Update documentation to confirm Python 3.6.0 compatibility. No code changes were needed, so many earlier versions are likely supported.
v4.3.4:
=======
v4.3.4
======
- Fixed "decimal.InvalidOperation: quantize result has too many digits" for high DPI images
v4.3.3:
=======
v4.3.3
======
- Fixed PDF/A creation with Ghostscript 9.20 properly
- Fixed an exception on inline stencil masks with a missing optional parameter
v4.3.2:
=======
v4.3.2
======
- Fixed a PDF/A creation issue with Ghostscript 9.20 (note: this fix did not actually work)
v4.3.1:
=======
v4.3.1
======
- Fixed an issue where pages produced by the "hocr" renderer after a Tesseract timeout would be rotated incorrectly if the input page was rotated with a /Rotate marker
- Fixed a file handle leak in LeptonicaErrorTrap that would cause a "too many open files" error for files around hundred pages of pages long when ``--deskew`` or ``--remove-background`` or other Leptonica based image processing features were in use, depending on the system value of ``ulimit -n``
@@ -58,8 +86,8 @@ v4.3.1:
- Tesseract caching in test cases is now more cautious about false cache hits and reproducing exact output, not that any problems were observed
v4.3:
=====
v4.3
====
- New feature ``--remove-background`` to detect and erase the background of color and grayscale images
- Better documentation
@@ -69,21 +97,21 @@ v4.3:
+ This does not improve performance since temporary files are still used for buffering
+ Some output validation is disabled in this mode
v4.2.5:
=======
v4.2.5
======
- Fixed an issue (#100) with PDFs that omit the optional /BitsPerComponent parameter on images
- Removed non-free file milk.pdf
v4.2.4:
=======
v4.2.4
======
- Fixed an error (#90) caused by PDFs that use stencil masks properly
- Fixed handling of PDFs that try to draw images or stencil masks without properly setting up the graphics state (such images are now ignored for the purposes of calculating DPI)
v4.2.3:
=======
v4.2.3
======
- Fixed an issue with PDFs that store page rotation (/Rotate) in an indirect object
- Integrated a few fixes to simplify downstream packaging (Debian)
@@ -96,21 +124,21 @@ v4.2.3:
- Deprecated the OCRmyPDF.sh shell script
v4.2.2:
=======
v4.2.2
======
- Improvements to documentation
v4.2.1:
=======
v4.2.1
======
- Fixed an issue where PDF pages that contained stencil masks would report an incorrect DPI and cause Ghostscript to abort
- Implemented stdin streaming
v4.2:
=====
v4.2
====
- ocrmypdf will now try to convert single image files to PDFs if they are provided as input (#15)
@@ -142,14 +170,14 @@ v4.2:
- Ghostscript now runs in "safer" mode where possible
v4.1.4:
=======
v4.1.4
======
- Bug fix: monochrome images with an ICC profile attached were incorrectly converted to full color images if lossless reconstruction was not possible due to other settings; consequence was increased file size for these images
v4.1.3:
=======
v4.1.3
======
- More helpful error message for PDFs with version 4 security handler
- Update usage instructions for Windows/Docker users
@@ -157,50 +185,50 @@ v4.1.3:
- Add a few leptonica wrapper functions (no effect on most users)
v4.1.2:
=======
v4.1.2
======
- Replace IEC sRGB ICC profile with Debian's sRGB (from icc-profiles-free) which is more compatible with the MIT license
- More helpful error message for an error related to certain types of malformed PDFs
v4.1:
=====
v4.1
====
- ``--rotate-pages`` now only rotates pages when reasonably confidence in the orientation. This behavior can be adjusted with the new argument ``--rotate-pages-threshold``
- Fixed problems in error checking if ``unpaper`` is uninstalled or missing at run-time
- Fixed problems with "RethrownJobError" errors during error handling that suppressed the useful error messages
v4.0.7:
=======
v4.0.7
======
- Minor correction to Ghostscript output settings
v4.0.6:
=======
v4.0.6
======
- Update install instructions
- Provide a sRGB profile instead of using Ghostscript's
v4.0.5:
=======
v4.0.5
======
- Remove some verbose debug messages from v4.0.4
- Fixed temporary that wasn't being deleted
- DPI is now calculated correctly for cropped images, along with other image transformations
- Inline images are now checked during DPI calculation instead of rejecting the image
v4.0.4:
=======
v4.0.4
======
Released with verbose debug message turned on. Do not use. Skip to v4.0.5.
v4.0.3:
=======
v4.0.3
======
New features
------------
@@ -217,8 +245,8 @@ Fixes
- Docker: fix blank JPEG2000 issue by insisting on Ghostscript versions that have this fixed
v4.0.2:
=======
v4.0.2
======
Fixes
-----
@@ -229,8 +257,8 @@ Fixes
- Fixed use of chmod on Docker that broke most test cases
v4.0.1:
=======
v4.0.1
======
Fixes
-----
@@ -238,8 +266,8 @@ Fixes
- Fixed a KeyError if tesseract fails to find page orientation information
v4.0:
=====
v4.0
====
New features
------------
@@ -273,8 +301,8 @@ Changes
to correct the problem.
v3.2.1:
=======
v3.2.1
======
Changes
-------
@@ -283,8 +311,8 @@ Changes
- Tweaked the Dockerfiles
v3.2:
=====
v3.2
====
New features
------------
@@ -305,16 +333,16 @@ Changes
v3.1.1:
=======
v3.1.1
======
Changes
-------
- Fixed bug that caused incorrect page size and DPI calculations on documents with mixed page sizes
v3.1:
=====
v3.1
====
Changes
-------
@@ -331,8 +359,8 @@ Changes
Currently it always chooses the 'hocrtransform' renderer but that behavior may change.
- Set up Travis CI automatic integration testing
v3.0:
=====
v3.0
====
New features
------------
@@ -481,8 +509,8 @@ Notes and known issues
images almost never contain inline images.
v2.2-stable (2014-09-29):
=========================
v2.2-stable (2014-09-29)
========================
OCRmyPDF versions 1 and 2 were implemented as shell scripts. OCRmyPDF 3.0+ is a fork that gradually replaced all shell scripts with Python while maintaining the existing command line arguments. No one is maintaining old versions.
+127
View File
@@ -0,0 +1,127 @@
Advanced features
=================
Control of OCR options
----------------------
OCRmyPDF provides many features to control the behavior of the OCR engine, Tesseract.
When OCR is skipped
"""""""""""""""""""
If a page in a PDF seems to have text, by default OCRmyPDF will exit without modifying the PDF. This is to ensure that PDFs that were previously OCRed or were "born digital" rather than scanned are not processed.
If ``--skip-text`` is issued, then no OCR will be performed on pages that already have text. The page will be copied to the output. This may be useful for documents that contain both "born digital" and scanned content, or to use OCRmyPDF to normalize and convert to PDF/A regardless of their contents.
If ``--force-ocr`` is issued, then all pages will be rasterized to images, discarding any hidden OCR text, and rasterizing any printable text. This is useful for redoing OCR, for fixing OCR text with a damaged character map (text is selectable but not searchable), and destroying redacted information.
Time and image size limits
""""""""""""""""""""""""""
By default, OCRmyPDF permits tesseract to run for only three minutes (180 seconds) per page. This is usually more than enough time to find all text on a reasonably sized page with modern hardware.
If a page is skipped, it will be inserted without OCR. If preprocessing was requested, the preprocessed image layer will be inserted.
If you want to adjust the amount of time spent on OCR, change ``--tesseract-timeout``. You can also automatically skip images that exceed a certain number of megapixels with ``--skip-big``. (A 300 DPI, 8.5×11" page is 8.4 megapixels.)
.. code-block:: bash
# Allow 300 seconds for OCR; skip any page larger than 50 megapixels
ocrmypdf --tesseract-timeout 300 --skip-big 50 bigfile.pdf output.pdf
Overriding default tesseract
""""""""""""""""""""""""""""
OCRmyPDF checks the environment variable ``OCRMYPDF_TESSERACT`` for the full path *to the tesseract executable* first.
For example, if you are testing tesseract 4.00 and don't wish to disturb your tesseract 3.04 installation, you can launch OCRmyPDF as follows:
.. code-block:: bash
env \
OCRMYPDF_TESSERACT=/home/user/src/tesseract4/api/tesseract \
TESSDATA_PREFIX=/home/user/src/tesseract4 \
ocrmypdf --pdf-renderer tess4 --tesseract-oem 2 input.pdf output.pdf
* ``TESSDATA_PREFIX`` directs tesseract 4.0 to use LSTM training data. This is a tesseract environment variable.
* ``--pdf-renderer tess4`` takes advantage of new tesseract 4.0 PDF renderer in OCRmyPDF. (Tesseract 4.0 only.)
* ``--tesseract-oem 1`` requests tesseract 4.0's new LSTM engine. (Tesseract 4.0 only.)
Overriding other support programs
"""""""""""""""""""""""""""""""""
In addition to tesseract, OCRmyPDF uses the following external binaries:
* ``gs`` (Ghostscript)
* ``unpaper``
* ``qpdf``
In each case OCRmyPDF will check the environment variable ``OCRMYPDF_{program}`` before asking the system to find ``{program}`` on the PATH. For example, you could redirect OCRmyPDF to ``OCRMYPDF_GS`` to override Ghostscript.
Changing tesseract configuration variables
""""""""""""""""""""""""""""""""""""""""""
You can override tesseract's default `control parameters <https://github.com/tesseract-ocr/tesseract/wiki/ControlParams>`_ with a configuration file.
As an example, this configuration will disable Tesseract's dictionary for current language. Normally the dictionary is helpful for interpolating words that are unclear, but it may interfere with OCR if the document does not contain many words (for example, a list of part numbers).
Create a file named "no-dict.cfg" with these contents:
::
load_system_dawg 0
language_model_penalty_non_dict_word 0
language_model_penalty_non_freq_dict_word 0
then run ocrmypdf as follows (along with any other desired arguments):
.. code-block:: bash
ocrmypdf --tesseract-config no-dict.cfg input.pdf output.pdf
.. warning::
Some combinations of control parameters will break Tesseract or break assumptions that OCRmyPDF makes about Tesseract's output.
Changing the PDF renderer
-------------------------
rasterizing
Converting a PDF to an image for display.
rendering
Creating a new PDF from other data (such as an existing PDF).
OCRmyPDF has three PDF renderers: ``hocr``, ``tesseract`` and ``tess4``. The renderer may be selected using ``--pdf-renderer``. The default is ``auto`` which lets OCRmyPDF select the renderer to use. Currently, ``auto`` always selects ``hocr``.
The ``hocr`` renderer
"""""""""""""""""""""
The ``hocr`` renderer is the default because it works in most cases. In this mode the whole PDF is rasterized, the raster image is run through OCR to generate a .hocr file, which is an HTML-like file that specifies the location of all identified words.
The .hocr file is then rendered as a PDF and merged with the image layer.
The image layer is copied from the original PDF page if possible, avoiding potentially lossy transcoding or loss of other PDF information. If preprocessing is specified, then the image layer is a new PDF.
This is the only option for tesseract 3.02 and older.
The ``tesseract`` renderer
""""""""""""""""""""""""""
The tesseract renderer uses tesseract's capability to produce a PDF directly. In version 3, tesseract automatically combined the image layer and text, meaning that this mode *always* transcodes and loses potentially loses image quality and other PDF information.
It does a much better job on non-Latin text.
In a future release this will become the "tess3" renderer and ultimately will be dropped.
The ``tess4`` renderer
""""""""""""""""""""""
The tess4 renderer uses tesseract 4.00 alpha's text-only PDF feature added in January 2017. This combines the advantages of the tesseract and hocr renderers, transcoding the image layer only if required by preprocessing options.
Ghostscript PDF/A still sometimes inserts spaces between words when the tess4 renderer is used, affecting search quality. ``--output-pdf pdf`` may be used to avoid this issue.
+20 -2
View File
@@ -10,11 +10,29 @@ Consider using the excellent `GNU Parallel <https://www.gnu.org/software/paralle
Both ``parallel`` and ``ocrmypdf`` will try to use all available processors. To maximize parallelism without overloading your system with processes, consider using ``parallel -j 2`` to limit parallel to running two jobs at once.
This command will run all ocrmypdf all files named ``*.pdf`` in the current directory and write them to the previous created ``output/`` folder.
This command will run all ocrmypdf all files named ``*.pdf`` in the current directory and write them to the previous created ``output/`` folder. It will not search subdirectories.
The ``--tag`` argument tells parallel to print the filename as a prefix whenever a message is printed, so that one can trace any errors to the file that produced them.
.. code-block:: bash
parallel -j 2 ocrmypdf '{}' 'output/{}' ::: *.pdf
parallel --tag -j 2 ocrmypdf '{}' 'output/{}' ::: *.pdf
Directory trees
---------------
This will walk through a directory tree and run OCR on all files in place, printing the output in a way that makes
.. code-block:: bash
find . --printf '%p' -name '*.pdf' -exec ocrmypdf '{}' '{}' \;
This only runs one ``ocrmypdf`` process at a time. This variation uses ``find`` to create a directory list and ``parallel`` to parallelize runs of ``ocrmypdf``, again updating files in place.
.. code-block:: bash
find . -name '*.pdf' | parallel --tag -j 2 ocrmypdf '{}' '{}'
Sample script
"""""""""""""
+14 -12
View File
@@ -68,10 +68,16 @@ Produce PDF and text file containing OCR text
This produces a file named "output.pdf" and a companion text file named "output.txt". The ``pdftotext`` program from
`Poppler <https://poppler.freedesktop.org/>`_ is used to extract text from the finished PDF.
.. code-block:: bash
ocrmypdf input.pdf - | tee output.pdf | pdftotext - output.txt
.. note::
To get pdftotext, Debian/Ubuntu users may ``apt-get install poppler-utils``
and macOS users may ``brew install poppler`` respectively.
OCR images, not PDFs
--------------------
@@ -111,6 +117,14 @@ OCRmyPDF perform some image processing on each page of a PDF, if desired. The s
* ``--clean-final`` uses unpaper to clean up pages before OCR and inserts the page into the final output. You will want to review each page to ensure that unpaper did not remove something important.
.. note::
In many cases image processing will rasterize PDF pages as images, potentially losing quality.
.. warning::
``--clean-final`` and ``-remove-background`` may leave undesirable visual artifacts in some images where their algorithms have shortcomings. Files should be visually reviewed after using these options.
OCR and correct document skew (crooked scan)
""""""""""""""""""""""""""""""""""""""""""""
@@ -127,16 +141,4 @@ Image processing commands can be combined. The order in which options are given
ocrmypdf --deskew --clean --rotate-pages input.pdf output.pdf
Control of OCR options
----------------------
By default, OCRmyPDF permits tesseract to run for only three minutes (180 seconds) per page. This is usually more than enough time to find all text on a reasonably sized page with modern hardware. A skipped page will be inserted into the output without any OCR text.
If you want to adjust the amount of time spent on OCR, change ``--tesseract-timeout``. You can also automatically skip images that exceed a certain number of megapixels.
.. code-block:: bash
# Allow 300 seconds for OCR; skip any page larger than 50 megapixels
ocrmypdf --tesseract-timeout 300 --skip-big 50 bigfile.pdf output.pdf
+1 -1
View File
@@ -20,8 +20,8 @@ Contents:
installation
languages
cookbook
advanced
batch
renderers
security
errors
+35 -15
View File
@@ -27,7 +27,7 @@ successfully, your system is ready to download and execute the image:
docker run hello-world
OCRmyPDF will use all available CPU cores. By default, the VirtualBox machine instance on Windows and OS X has only a single CPU core enabled. Use the VirtualBox Manager to determine the name of your Docker engine host, and then follow these optional steps to enable multiple CPUs:
OCRmyPDF will use all available CPU cores. By default, the VirtualBox machine instance on Windows and macOS has only a single CPU core enabled. Use the VirtualBox Manager to determine the name of your Docker engine host, and then follow these optional steps to enable multiple CPUs:
.. code-block:: bash
@@ -37,28 +37,34 @@ OCRmyPDF will use all available CPU cores. By default, the VirtualBox machine i
docker-machine start "yourVM"
eval $(docker-machine env "yourVM")
Assuming you have a Docker engine running somewhere, you can run these commands to download
the image:
Assuming you have a Docker engine running, you can download one of the three available images:
+-----------------------------+---------------------------------------------+---------------------------------------------------------------------------------+
| Image name | Download command | Notes |
+-----------------------------+---------------------------------------------+---------------------------------------------------------------------------------+
| ocrmypdf | ``docker pull jbarlow83/ocrmypdf`` | Latest ocrmypdf with Tesseract 3.04. Includes English, French, German, Spanish. |
+-----------------------------+---------------------------------------------+---------------------------------------------------------------------------------+
| ocrmypdf-polyglot | ``docker pull jbarlow83/ocrmypdf-polyglot`` | As above, with all available language packs. |
+-----------------------------+---------------------------------------------+---------------------------------------------------------------------------------+
| ocrmypdf-tess4 | ``docker pull jbarlow83/ocrmypdf-tess4`` | Latest ocrmypdf with Tesseract 4.00.00alpha and English, French, German, |
| | | Spanish, Portuguese, Chinese Simplified, Arabic and Russian (the top 8). |
+-----------------------------+---------------------------------------------+---------------------------------------------------------------------------------+
For example:
.. code-block:: bash
docker pull jbarlow83/ocrmypdf
docker pull jbarlow83/ocrmypdf-tess4
Then tag it to give a more convenient name, just ocrmypdf:
.. code-block:: bash
docker tag jbarlow83/ocrmypdf ocrmypdf
docker tag jbarlow83/ocrmypdf-tess4 ocrmypdf
.. _docker-polyglot:
This image contains language packs for English, French, Spanish and German. The alternative "polyglot" image provides `all available language packs <https://github.com/tesseract-ocr/tesseract/blob/master/doc/tesseract.1.asc#languages>`_:
.. code-block:: bash
# Alternative step: If you need all language packs
docker pull jbarlow83/ocrmypdf-polyglot
docker tag jbarlow83/ocrmypdf-polyglot ocrmypdf
The alternative "polyglot" image provides `all available language packs <https://github.com/tesseract-ocr/tesseract/blob/master/doc/tesseract.1.asc#languages>`_.
You can then run ocrmypdf using the command:
@@ -82,11 +88,25 @@ In this worked example, the current working directory contains an input file cal
Note that ``ocrmypdf`` has its own separate ``-v VERBOSITYLEVEL`` argument to control debug verbosity. All Docker arguments should before the ``ocrmypdf`` image name and all arguments to ``ocrmypdf`` should be listed after.
For convenience, a shell alias can hide the docker command:
Installing on macOS (formerly Mac OS X)
---------------------------------------
.. code-block:: bash
These instructions probably work on all macOS supported by Homebrew. OCRmyPDF is known to work on Yosemite and El Capitan, and regularly tested on El Capitan.
alias ocrmypdf='docker run --rm -v "$(pwd):/home/docker" ocrmypdf'
ocrmypdf --version # runs docker version
Or in the wonderful `fish shell <https://fishshell.com/>`_:
.. code-block:: fish
alias ocrmypdf 'docker run --rm -v (pwd):/home/docker ocrmypdf'
funcsave ocrmypdf
Installing on macOS
-------------------
These instructions probably work on all macOS supported by Homebrew.
If it's not already present, `install Homebrew <http://brew.sh/>`_.
-40
View File
@@ -1,40 +0,0 @@
PDF Renderers
=============
rasterizing
Converting a PDF to an image for display.
rendering
Creating a new PDF from other data (such as an existing PDF).
OCRmyPDF has three PDF renderers: ``hocr``, ``tesseract`` and ``tess4``. The renderer may be selected using ``--pdf-renderer``. The default is ``auto`` which lets OCRmyPDF select the renderer to use. Currently it always uses ``hocr``.
The hocr renderer
-----------------
The ``hocr`` renderer is the default because it works in most cases. In this mode the whole PDF is rasterized, the raster image is run through OCR to generate a .hocr file, which is an HTML-like file that specifies the location of all identified words.
The .hocr file is then rendered as a PDF and merged with the image layer.
The image layer is copied from the original PDF page if possible, avoiding potentially lossy transcoding or loss of other PDF information. If preprocessing is specified, then the image layer is a new PDF.
This is the only option for tesseract 3.02 and older.
The tesseract renderer
----------------------
The tesseract renderer uses tesseract's capability to produce a PDF directly. In version 3, tesseract automatically combined the image layer and text, meaning that this mode always transcodes and loses potentially loses quality and other PDF information.
It does a much better job on non-Latin text.
In a future release this will become the "tess3" renderer and ultimately will be dropped.
The tess4 renderer
------------------
The tess4 renderer uses tesseract 4.00 alpha's text-only PDF feature added in January 2017. This combines the advantages of the tesseract and hocr renderers, transcoding the image layer only if required by preprocessing options.
Ghostscript PDF/A still sometimes inserts spaces between words when the tess4 renderer is used, affecting search quality. ``--output-pdf pdf`` may be used to avoid this issue.
+14 -11
View File
@@ -207,7 +207,7 @@ advanced = parser.add_argument_group(
"Advanced options for power users")
advanced.add_argument(
'--tesseract-config', action='append', metavar='CFG', default=[],
help="additional Tesseract configuration files")
help="additional Tesseract configuration files -- see documentation")
advanced.add_argument(
'--tesseract-pagesegmode', action='store', type=int, metavar='PSM',
choices=range(0, 14),
@@ -272,13 +272,15 @@ def check_options_output(options, log):
if options.pdf_renderer == 'auto':
options.pdf_renderer = 'hocr'
if options.pdf_renderer == 'tesseract' and \
tesseract.version() < '3.04.01' and \
os.environ.get('OCRMYPDF_SHARP_TTF', '') != '1':
log.warning(
"Your version of tesseract has problems with PDF output."
" Some PDF viewers will fail to find searchable text.\n"
"--pdf-renderer=tesseract is not recommended.")
if options.pdf_renderer in ('tesseract', 'tess4'):
if tesseract.version() < '3.05':
log.warning(
"tesseract < 3.05 may corrupt PDF output. "
"--pdf-renderer=tesseract is not recommend.")
elif tesseract.version() == '4.00.00alpha':
log.warning(
"tesseract 4.00.00alpha may corrupt PDF output. "
"--pdf-renderer={tesseract,tess4} is not recommend.")
if options.debug_rendering and options.pdf_renderer == 'tesseract':
log.info(
@@ -333,6 +335,10 @@ def check_options_advanced(options, log):
if options.tesseract_oem and not tesseract.v4():
log.warning(
"--tesseract-oem requires Tesseract 4.x -- argument ignored")
if options.pdf_renderer == 'tess4' and not tesseract.has_textonly_pdf():
raise MissingDependencyError(
"--pdf-renderer tess4 requires Tesseract 4.x "
"commit 3d9fb3b or later")
def check_options(options, log):
@@ -350,9 +356,6 @@ def check_options(options, log):
sys.exit(ExitCode.missing_dependency)
# ----------
# Logging
+5
View File
@@ -14,6 +14,7 @@ class ExitCode(IntEnum):
already_done_ocr = 6
child_process_error = 7
encrypted_pdf = 8
invalid_config = 9
other_error = 15
ctrl_c = 130
@@ -52,3 +53,7 @@ class SubprocessOutputError(ExitCodeException):
class EncryptedPdfError(ExitCodeException):
exit_code = ExitCode.encrypted_pdf
class TesseractConfigError(ExitCodeException):
exit_code = ExitCode.invalid_config
+9 -1
View File
@@ -6,7 +6,7 @@ import os
import re
import shutil
from functools import lru_cache
from ..exceptions import MissingDependencyError
from ..exceptions import MissingDependencyError, TesseractConfigError
from ..helpers import page_number
from . import get_program
from collections import namedtuple
@@ -186,6 +186,8 @@ def tesseract_log_output(log, stdout, input_file):
log.warning(prefix + "unsure about page orientation")
elif 'error' in line.lower() or 'exception' in line.lower():
log.error(prefix + line.strip())
elif 'read_params_file' in line.lower():
log.error(prefix + line.strip())
else:
log.info(prefix + line.strip())
@@ -236,6 +238,8 @@ def generate_hocr(input_file, output_hocr, language: list, engine_mode,
_generate_null_hocr(output_hocr, input_file)
except CalledProcessError as e:
tesseract_log_output(log, e.output, input_file)
if 'read_params_file: parameter not found' in e.output:
raise TesseractConfigError() from e
if 'Image too large' in e.output:
_generate_null_hocr(output_hocr, input_file)
return
@@ -243,6 +247,7 @@ def generate_hocr(input_file, output_hocr, language: list, engine_mode,
raise e from e
else:
tesseract_log_output(log, stdout, input_file)
if os.path.exists(badxml + '.html'):
# Tesseract 3.02 appends suffix ".html" on its own (.badxml.html)
shutil.move(badxml + '.html', badxml)
@@ -305,6 +310,9 @@ def generate_pdf(input_image, skip_pdf, output_pdf, language: list,
shutil.copy(skip_pdf, output_pdf)
except CalledProcessError as e:
tesseract_log_output(log, e.output, input_image)
if 'read_params_file: parameter not found' in e.output:
raise TesseractConfigError() from e
if 'Image too large' in e.output:
shutil.copy(skip_pdf, output_pdf)
return
+5 -5
View File
@@ -1,8 +1,8 @@
from cffi import FFI
ffi = FFI()
ffi.set_source("ocrmypdf.lib._leptonica", None)
ffi.cdef("""
ffibuilder = FFI()
ffibuilder.set_source("ocrmypdf.lib._leptonica", None)
ffibuilder.cdef("""
typedef signed char l_int8;
typedef unsigned char l_uint8;
typedef short l_int16;
@@ -56,7 +56,7 @@ typedef struct Box BOX;
""")
ffi.cdef("""
ffibuilder.cdef("""
PIX * pixRead ( const char *filename );
PIX * pixScale ( PIX *pixs, l_float32 scalex, l_float32 scaley );
l_int32 pixFindSkew ( PIX *pixs, l_float32 *pangle, l_float32 *pconf );
@@ -189,4 +189,4 @@ void lept_free(void *ptr);
if __name__ == '__main__':
ffi.compile()
ffibuilder.compile(verbose=True)
+130 -37
View File
@@ -89,13 +89,13 @@ def _is_unit_square(shorthand):
pairwise = zip(values, UNIT_SQUARE)
return all([isclose(a, b, rel_tol=1e-3) for a, b in pairwise])
RasterSettings = namedtuple('RasterSettings',
XobjectSettings = namedtuple('XobjectSettings',
['name', 'shorthand', 'stack_depth'])
InlineSettings = namedtuple('InlineSettings',
['settings', 'shorthand', 'stack_depth'])
ContentsInfo = namedtuple('ContentsInfo', ['raster_settings', 'inline_images'])
ContentsInfo = namedtuple('ContentsInfo', ['xobject_settings', 'inline_images'])
def _normalize_stack(operations):
@@ -114,7 +114,7 @@ def _normalize_stack(operations):
yield (operands, command)
def _interpret_contents(contentstream):
def _interpret_contents(contentstream, initial_shorthand=UNIT_SQUARE):
"""Interpret the PDF content stream
The stack represents the state of the PDF graphics stack. We are only
@@ -139,8 +139,8 @@ def _interpret_contents(contentstream):
operations = contentstream.operations
stack = []
ctm = _matrix_from_shorthand(UNIT_SQUARE)
image_raster_settings = []
ctm = _matrix_from_shorthand(initial_shorthand)
xobject_settings = []
inline_images = []
for n, op in enumerate(_normalize_stack(operations)):
@@ -161,10 +161,10 @@ def _interpret_contents(contentstream):
_matrix_from_shorthand(operands), ctm)
elif command == b'Do':
image_name = operands[0]
raster = RasterSettings(
settings = XobjectSettings(
name=image_name, shorthand=_shorthand_from_matrix(ctm),
stack_depth=len(stack))
image_raster_settings.append(raster)
xobject_settings.append(settings)
elif command == b'INLINE IMAGE':
settings = operands['settings']
inline = InlineSettings(
@@ -173,7 +173,7 @@ def _interpret_contents(contentstream):
inline_images.append(inline)
return ContentsInfo(
raster_settings=image_raster_settings,
xobject_settings=xobject_settings,
inline_images=inline_images)
@@ -241,8 +241,8 @@ def _get_dpi(ctm_shorthand, image_size):
return (dpi_w, dpi_h)
def _find_page_inline_images(page, pageinfo, contentsinfo):
"Find inline images on the page"
def _find_inline_images(contentsinfo):
"Find inline images in the contentstream"
for n, inline in enumerate(contentsinfo.inline_images):
image = {}
@@ -272,20 +272,44 @@ def _find_page_inline_images(page, pageinfo, contentsinfo):
yield image
def _find_page_regular_images(page, pageinfo, contentsinfo):
"Find images stored in XObject resources"
def _image_xobjects(container):
"""Search for all XObject-based images in the container
try:
page['/Resources']['/XObject']
except KeyError:
Usually the container is a page, but it could also be a Form XObject
that contains images. Filter out the Form XObjects which are dealt with
elsewhere.
Generate a sequence of tuples (image, xobj container), where container,
where xobj is the name of the object and image is the object itself,
since the object does not know its own name.
"""
if '/Resources' not in container:
return
for xobj in page['/Resources']['/XObject']:
# PyPDF2 returns the keys as an iterator
pdfimage = page['/Resources']['/XObject'][xobj]
if pdfimage['/Subtype'] != '/Image':
continue
resources = container['/Resources']
if '/XObject' not in resources:
return
for xobj in resources['/XObject']:
candidate = resources['/XObject'][xobj]
if candidate['/Subtype'] == '/Image':
image = candidate
yield (image, xobj)
def _find_regular_images(container, contentsinfo):
"""Find images stored in the container's /Resources /XObject
Usually the container is a page, but it could also be a Form XObject
that contains images.
Generates images with their DPI at time of drawing.
"""
for pdfimage, xobj in _image_xobjects(container):
image = {}
image['name'] = str(xobj)
image['name'] = xobj
image['width'] = pdfimage['/Width']
image['height'] = pdfimage['/Height']
if '/BitsPerComponent' in pdfimage:
@@ -329,12 +353,12 @@ def _find_page_regular_images(page, pageinfo, contentsinfo):
image['dpi_w'] = image['dpi_h'] = 0
for raster in contentsinfo.raster_settings:
for xobj in contentsinfo.xobject_settings:
# Loop in case the same image is display multiple times on a page
if raster.name != image['name']:
if xobj.name != image['name']:
continue
if raster.stack_depth == 0 and _is_unit_square(raster.shorthand):
if xobj.stack_depth == 0 and _is_unit_square(xobj.shorthand):
# At least one PDF in the wild (and test suite) draws an image
# when the graphics stack depth is 0, meaning that the image
# gets drawn into a square of 1x1 PDF units (or 1/72",
@@ -343,7 +367,7 @@ def _find_page_regular_images(page, pageinfo, contentsinfo):
continue
dpi_w, dpi_h = _get_dpi(
raster.shorthand, (image['width'], image['height']))
xobj.shorthand, (image['width'], image['height']))
# When image is used multiple times take the highest DPI it is
# rendered at
@@ -358,9 +382,85 @@ def _find_page_regular_images(page, pageinfo, contentsinfo):
yield image
def _find_page_images(page, pageinfo, contentsinfo):
yield from _find_page_inline_images(page, pageinfo, contentsinfo)
yield from _find_page_regular_images(page, pageinfo, contentsinfo)
def _find_form_xobject_images(pdf, container, contentsinfo):
"""Find any images that are in Form XObjects in the container
The container may be a page, or a parent Form XObject.
"""
if '/Resources' not in container:
return
resources = container['/Resources']
if '/XObject' not in resources:
return
for xobj in resources['/XObject']:
candidate = resources['/XObject'][xobj]
if candidate['/Subtype'] != '/Form':
continue
form_xobject = candidate
for settings in contentsinfo.xobject_settings:
if settings.name != xobj:
continue
# Find images once for each time this Form XObject is drawn.
# This could be optimized to cache the multiple drawing events
# but in practice both Form XObjects and multiple drawing of the
# same object are both very rare.
ctm_shorthand = settings.shorthand
yield from _find_images(pdf, form_xobject, ctm_shorthand)
def _find_images(pdf, container, shorthand=None):
"""Find all individual instances of images drawn in the container
Usually the container is a page, but it may also be a Form XObject.
On a typical page images are stored inline or as regular images
in an XObject.
Form XObjects may include inline images, XObject images,
and recursively, other Form XObjects; and also vector drawing commands.
Every instance of an image being drawn somewhere is flattened and
treated as a unique image, since if the same image is drawn multiple times
on one page it may be drawn at differing resolutions, and our objective
is to find the resolution at which the page can be rastered without
downsampling.
"""
if container.get('/Type') == '/Page':
# For a /Page the content stream is attached to the page's /Contents
page = container
contentstream = pypdf.pdf.ContentStream(page.getContents(), pdf)
initial_shorthand = shorthand or UNIT_SQUARE
elif container.get('/Type') == '/XObject' and \
container['/Subtype'] == '/Form':
# For a Form XObject that content stream is attached to the XObject
contentstream = pypdf.pdf.ContentStream(container, pdf)
# Set the CTM to the state it was when the "Do" operator was
# encountered that is drawing this instance of the Form XObject
ctm = _matrix_from_shorthand(shorthand or UNIT_SQUARE)
# A Form XObject may provide its own matrix to map form space into
# user space. Get this if one exists
form_matrix = _matrix_from_shorthand(
container.get('/Matrix', UNIT_SQUARE))
# Concatenate form matrix with CTM to ensure CTM is correct for
# drawing this instance of the XObject
ctm = matrix_mult(form_matrix, ctm)
initial_shorthand = _shorthand_from_matrix(ctm)
else:
return
contentsinfo = _interpret_contents(contentstream, initial_shorthand)
yield from _find_inline_images(contentsinfo)
yield from _find_regular_images(container, contentsinfo)
yield from _find_form_xobject_images(pdf, container, contentsinfo)
def _page_has_text(pdf, page):
@@ -405,15 +505,8 @@ def _pdf_get_pageinfo(infile, pageno: int):
except KeyError:
pageinfo['rotate'] = 0
try:
contentstream = pypdf.pdf.ContentStream(page.getContents(), pdf)
except AttributeError as e:
return pageinfo
contentsinfo = _interpret_contents(contentstream)
pageinfo['images'] = [im for im in _find_page_images(
page, pageinfo, contentsinfo)]
pageinfo['images'] = [im for im in
_find_images(pdf, page)]
if pageinfo['images']:
xres = max(image['dpi_w'] for image in pageinfo['images'])
yres = max(image['dpi_h'] for image in pageinfo['images'])
+3 -3
View File
@@ -948,7 +948,7 @@ def build_pipeline(options, work_folder, log, context):
task_ocr_tesseract_hocr.graphviz(fillcolor='"#00cc66"')
task_ocr_tesseract_hocr.active_if(options.pdf_renderer == 'hocr')
if tesseract.v4():
task_ocr_tesseract_hocr.jobs_limit(1) # Uses multi-core on its own
task_ocr_tesseract_hocr.jobs_limit(2) # Uses multi-core on its own
task_select_visible_page_image = main_pipeline.collate(
task_func=select_visible_page_image,
@@ -1001,7 +1001,7 @@ def build_pipeline(options, work_folder, log, context):
task_ocr_tesseract_textonly_pdf.graphviz(fillcolor='"#ff69b4"')
task_ocr_tesseract_textonly_pdf.active_if(options.pdf_renderer == 'tess4')
if tesseract.v4():
task_ocr_tesseract_textonly_pdf.jobs_limit(1)
task_ocr_tesseract_textonly_pdf.jobs_limit(2)
task_combine_layers = main_pipeline.collate(
task_func=combine_layers,
@@ -1024,7 +1024,7 @@ def build_pipeline(options, work_folder, log, context):
task_ocr_tesseract_and_render_pdf.graphviz(fillcolor='"#66ccff"')
task_ocr_tesseract_and_render_pdf.active_if(options.pdf_renderer == 'tesseract')
if tesseract.v4():
task_ocr_tesseract_and_render_pdf.jobs_limit(1) # Uses multi-core
task_ocr_tesseract_and_render_pdf.jobs_limit(2) # Uses multi-core
# PDF/A
task_generate_postscript_stub = main_pipeline.transform(
+2 -2
View File
@@ -2,8 +2,8 @@
# setup.py lists a separate set of requirements that are looser to simplify
# installation
ruffus == 2.6.3
Pillow == 3.4.2
Pillow == 4.0.0
reportlab == 3.3.0
PyPDF2 == 1.26
img2pdf == 0.2.1
img2pdf == 0.2.3
cffi == 1.9.1
+8 -10
View File
@@ -9,7 +9,7 @@ if sys.version_info < (3, 4):
print("Python 3.4 or newer is required", file=sys.stderr)
sys.exit(1)
from setuptools import setup # nopep8
from setuptools import setup, find_packages # nopep8
from subprocess import STDOUT, check_output, CalledProcessError # nopep8
from collections.abc import Mapping # nopep8
import re # nopep8
@@ -192,7 +192,7 @@ setup(
author='James R. Barlow',
author_email='jim@purplerock.ca',
license='MIT',
packages=['ocrmypdf', 'ocrmypdf.exec'],
packages=find_packages(exclude=["tests", "tests.*"]),
keywords=['PDF', 'OCR', 'optical character recognition', 'PDF/A', 'scanning'],
classifiers=[
"Programming Language :: Python :: 3",
@@ -214,22 +214,20 @@ setup(
"Topic :: Text Processing :: Linguistic",
],
setup_requires=[
'setuptools_scm',
'cffi>=1.5.0',
'pytest-runner',
'pytest-helpers-namespace'
'setuptools_scm', # so that version will work
'cffi>=1.9.1' # to build the leptonica module
],
use_scm_version={'version_scheme': 'post-release'},
cffi_modules=[
'ocrmypdf/lib/compile_leptonica.py:ffi'
'ocrmypdf/lib/compile_leptonica.py:ffibuilder'
],
install_requires=[
'ruffus==2.6.3', # pinned - ocrmypdf implements a 2.6.3 workaround
'Pillow>=3.1.1', # Pillow is pretty stable
'Pillow>=4.0.0', # Pillow < 4 has BytesIO/TIFF bug w/img2pdf 0.2.3
'reportlab>=3.2.0', # oldest released version with sane image handling
'PyPDF2>=1.26', # pure Python, so track HEAD closely
'img2pdf>=0.2.1', # pure Python, so track HEAD closely
'cffi>=1.5.0' # oldest version ever tested
'img2pdf>=0.2.3', # pure Python, so track HEAD closely
'cffi>=1.9.1' # must be a setup and install requirement
],
tests_require=tests_require,
entry_points={
-4
View File
@@ -1,4 +0,0 @@
deb http://httpredir.debian.org/debian stretch main
deb http://httpredir.debian.org/debian stretch-updates main
deb http://security.debian.org stretch/updates main
deb http://ftp.de.debian.org/debian sid main contrib non-free
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -1,2 +1,2 @@
pytest >= 2.8
pytest >= 3.0
pytest-helpers-namespace
+3 -2
View File
@@ -24,8 +24,9 @@ def is_linux():
@pytest.helpers.register
def running_in_docker():
# Docker creates a file named /.dockerinit
return os.path.exists('/.dockerinit')
# Docker creates a file named /.dockerenv (newer versions) or
# /.dockerinit (older) -- this is undocumented, not an offical test
return os.path.exists('/.dockerenv') or os.path.exists('/.dockerinit')
TESTS_ROOT = os.path.abspath(os.path.dirname(__file__))
+3
View File
@@ -64,6 +64,9 @@ under the terms of the license in LICENSE.rst.
* - epson.pdf
- @lowesjam
- a linearized PDF containing some unusual indirect objects, created by an Epson printer; printout of a Wikipedia article (CC BY-SA)
* - formxobject.pdf
- @jbarlow83
- hand-crafted exotic PDF containing an image inside a Form XObject
* - francais.pdf
- @jbarlow83
- a page containing French accents (diacritics)
Binary file not shown.
+21 -6
View File
@@ -37,6 +37,7 @@ def real_tesseract():
def main():
operation = sys.argv[-1]
# For anything unexpected operation, defer to real tesseract binary
# Currently this includes all use of "--tesseract-config"
if operation != 'hocr' and operation != 'pdf' and operation != 'stdout':
real_tesseract()
return # Not reachable
@@ -52,6 +53,11 @@ def main():
['tesseract', '--version'],
stderr=subprocess.STDOUT)
if b'4.00.00alpha' in tess_version:
# Tesseract 4.x alpha is a moving target, don't cache it
real_tesseract()
return
m.update(tess_version)
# Insert this source file into the hash function, to ensure that any
@@ -65,12 +71,21 @@ def main():
lang = sys.argv[sys.argv.index('-l') + 1]
m.update(lang.encode())
except ValueError:
pass
try:
psm = sys.argv[sys.argv.index('-psm') + 1]
m.update(psm.encode())
except ValueError:
pass
m.update(b'default-lang')
psm_arg = ''
if '--psm' in sys.argv:
psm_arg = '--psm'
elif '-psm' in sys.argv:
psm_arg = '-psm'
if psm_arg:
try:
psm = sys.argv[sys.argv.index(psm_arg) + 1]
m.update(psm.encode())
except ValueError:
m.update(b'default-psm')
else:
m.update(b'default-psm')
if operation == 'stdout' and psm != '0':
real_tesseract()
+58
View File
@@ -682,6 +682,10 @@ def test_overlay(spoof_tesseract_noop, resources, outpdf):
env=spoof_tesseract_noop)
@pytest.mark.skipif(
os.getuid() == 0 or os.geteuid() == 0,
reason="root can write to anything"
)
def test_destination_not_writable(spoof_tesseract_noop, resources, outdir):
protected_file = outdir / 'protected.pdf'
protected_file.touch()
@@ -690,3 +694,57 @@ def test_destination_not_writable(spoof_tesseract_noop, resources, outdir):
resources / 'jbig2.pdf', protected_file,
env=spoof_tesseract_noop)
assert p.returncode == ExitCode.file_access_error, "Expected error"
def test_tesseract_config_valid(resources, outdir):
cfg_file = outdir / 'test.cfg'
with cfg_file.open('w') as f:
f.write('''\
load_system_dawg 0
language_model_penalty_non_dict_word 0
language_model_penalty_non_freq_dict_word 0
''')
check_ocrmypdf(
resources / 'ccitt.pdf', outdir / 'out.pdf',
'--tesseract-config', str(cfg_file))
@pytest.mark.parametrize('renderer', [
'hocr',
'tesseract',
])
def test_tesseract_config_notfound(renderer, resources, outdir):
cfg_file = outdir / 'nofile.cfg'
p, out, err = run_ocrmypdf(
resources / 'ccitt.pdf', outdir / 'out.pdf',
'--pdf-renderer', renderer,
'--tesseract-config', str(cfg_file))
assert "Can't open" in err, "No error message about missing config file"
assert p.returncode == ExitCode.ok
@pytest.mark.parametrize('renderer', [
'hocr',
'tesseract',
])
def test_tesseract_config_invalid(renderer, resources, outdir):
cfg_file = outdir / 'test.cfg'
with cfg_file.open('w') as f:
f.write('''\
THIS FILE IS INVALID
''')
p, out, err = run_ocrmypdf(
resources / 'ccitt.pdf', outdir / 'out.pdf',
'--pdf-renderer', renderer,
'--tesseract-config', str(cfg_file))
assert "parameter not found" in err, "No error message"
assert p.returncode == ExitCode.invalid_config
def test_form_xobject(spoof_tesseract_noop, resources, outpdf):
check_ocrmypdf(resources / 'formxobject.pdf', outpdf,
'--force-ocr',
env=spoof_tesseract_noop)
+7
View File
@@ -101,3 +101,10 @@ def test_jpeg(resources, outdir):
assert pdfimage['enc'] == 'jpeg'
assert (pdfimage['dpi_w'] - 150) < 1e-5
def test_form_xobject(resources):
filename = resources / 'formxobject.pdf'
pdfinfo = pageinfo.pdf_get_all_pageinfo(str(filename))
pdfimage = pdfinfo[0]['images'][0]
assert pdfimage['width'] == 50
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env python3
# © 2017 James R. Barlow: github.com/jbarlow83
import pytest
from ocrmypdf.exceptions import ExitCode
from ocrmypdf.exec import tesseract
# Skip all tests in this file if not tesseract 3
pytestmark = pytest.mark.skipif(tesseract.v4(),
reason="tesseract 3.x required")
def test_textonly_pdf_on_tess3(resources, no_outpdf):
p, _, _ = pytest.helpers.run_ocrmypdf(
resources / 'linn.pdf',
no_outpdf, '--pdf-renderer', 'tess4')
assert p.returncode == ExitCode.missing_dependency
def test_oem_on_tess3(resources, no_outpdf):
p, _, err = pytest.helpers.run_ocrmypdf(
resources / 'aspect.pdf',
no_outpdf, '--tesseract-oem', '1')
assert p.returncode == ExitCode.ok
assert 'argument ignored' in err
-9
View File
@@ -1,17 +1,8 @@
#!/usr/bin/env python3
# © 2017 James R. Barlow: github.com/jbarlow83
from subprocess import Popen, PIPE, check_output, check_call, DEVNULL
import os
import shutil
from contextlib import suppress
import sys
import pytest
from ocrmypdf.pageinfo import pdf_get_all_pageinfo
import PyPDF2 as pypdf
from ocrmypdf.exceptions import ExitCode
from ocrmypdf import leptonica
from ocrmypdf.pdfa import file_claims_pdfa
from ocrmypdf.exec import tesseract