Files
ocrmypdf/tests/spoof/tesseract_cache.py
T

198 lines
7.0 KiB
Python
Raw Normal View History

2015-12-17 12:52:12 -08:00
#!/usr/bin/env python3
2017-05-14 23:38:28 -07:00
# © 2016 James R. Barlow: github.com/jbarlow83
2018-03-14 14:40:48 -07:00
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2017-05-14 23:38:28 -07:00
2018-03-24 22:17:36 -07:00
from pathlib import Path
2015-12-17 12:52:12 -08:00
import sys
import os
import shutil
import subprocess
2018-03-24 22:17:36 -07:00
import argparse
import json
import platform
import re
2015-12-17 12:52:12 -08:00
2016-08-26 15:04:08 -07:00
"""Cache output of tesseract to speed up test suite
2018-03-24 22:17:36 -07:00
The cache is keyed by an environment variable that slips the input test file
from tests/resources/ to us. The input arguments are slugged into a hideous
filename that more or less represents them literally. Joined together, this
becomes the name of the cache folder. A few name files like stdout, stderr,
hocr, pdf, describe the output to reproduce.
2017-05-14 23:54:09 -07:00
2018-03-24 22:17:36 -07:00
Changes to tests/resources/ or image processing algorithms don't trigger a
cache miss. By design, an input image that varies according to platform
differences (e.g. JPEG decoders are allowed to produce differing outputs,
and in practice they do) will still be a cache hit. By design, an
invocation of tesseract with the same parameters from a different test case
will be a hit. It's fragile.
2016-08-26 15:04:08 -07:00
2018-03-24 22:17:36 -07:00
The tests/cache/manifest.jsonl is a JSON lines file that contains
information about the system that produced the results used when cache was
generated. This mainly a log to answer questions about how the files
were produced.
2016-08-26 15:04:08 -07:00
2018-03-24 22:17:36 -07:00
For performance reasons, especially the slow performance of Tesseract on
machines with AVX2, the cache is now bundled.
2016-08-26 15:04:08 -07:00
2018-03-24 22:17:36 -07:00
Certain operations are not cached and routed to tesseract directly.
2016-08-26 15:04:08 -07:00
2017-05-14 23:54:09 -07:00
Assumes Tesseract 3.04 or higher.
2016-08-26 15:04:08 -07:00
Will fail on Tesseract 3.02.02 in "hocr" mode because it doesn't produce
2017-05-14 23:54:09 -07:00
the incorrect file extension. Will fail on 3.03 because that has no sidecar
text support. Will fail to replicate a 3.04 bug if wrong parameter order is
given.
2016-08-26 15:04:08 -07:00
"""
2018-03-24 22:17:36 -07:00
if '_OCRMYPDF_SAVE_PATH' in os.environ:
os.environ['PATH'] = os.environ['_OCRMYPDF_SAVE_PATH']
__version__ = subprocess.check_output(
['tesseract', '--version'],
stderr=subprocess.STDOUT).decode()
2016-08-26 15:04:08 -07:00
2015-12-17 12:52:12 -08:00
2018-03-24 22:17:36 -07:00
parser = argparse.ArgumentParser(
prog='tesseract-cache', description='cache output of tesseract')
parser.add_argument('-l', '--language', action='append')
parser.add_argument('imagename')
parser.add_argument('outputbase')
parser.add_argument('configfiles', nargs='*')
parser.add_argument('--user-words', type=str)
parser.add_argument('--user-patterns', type=str)
parser.add_argument('-c', action='append')
parser.add_argument('--psm', type=int)
parser.add_argument('--oem', type=int)
TESTS_ROOT = Path(__file__).resolve().parent.parent
CACHE_ROOT = TESTS_ROOT / 'cache'
2015-12-17 12:52:12 -08:00
def real_tesseract():
tess_args = ['tesseract'] + sys.argv[1:]
os.execvp("tesseract", tess_args)
return # Not reachable
2015-12-17 12:52:12 -08:00
2018-03-24 22:17:36 -07:00
def main():
if any(opt in sys.argv[1:] for opt in (
'--print-parameters', '--list-langs', '--version')):
real_tesseract() # jump into real tesseract, replacing this process
2015-12-17 12:52:12 -08:00
# Convert non-standard but supported -psm to --psm
sys.argv = ['--psm' if arg == '-psm' else arg for arg in sys.argv]
2018-03-24 22:17:36 -07:00
source = os.environ['_OCRMYPDF_TEST_INFILE'] # required
args = parser.parse_args()
2015-12-17 12:52:12 -08:00
2018-03-24 22:17:36 -07:00
if args.imagename == 'stdin':
real_tesseract()
2018-03-24 22:17:36 -07:00
def slugs():
yield '' # so we don't start with a '-' which makes rm difficult
for arg in sys.argv[1:]:
if arg == args.imagename:
yield Path(args.imagename).name
elif arg == args.outputbase:
yield Path(args.outputbase).name
else:
yield arg
argv_slug = '__'.join(slugs())
argv_slug = argv_slug.replace('/', '___')
cache_folder = Path(CACHE_ROOT) / Path(source).stem / argv_slug
cache_folder.mkdir(parents=True, exist_ok=True)
print("Tesseract cache folder {} - ".format(cache_folder), end='',
file=sys.stderr)
2018-03-25 00:00:12 -07:00
if (cache_folder / 'stderr.bin').exists():
2015-12-17 12:52:12 -08:00
# Cache hit
2018-03-24 22:17:36 -07:00
print("HIT", file=sys.stderr)
# Replicate stdout/err
2018-03-25 00:00:12 -07:00
sys.stdout.buffer.write((cache_folder / 'stdout.bin').read_bytes())
sys.stderr.buffer.write((cache_folder / 'stderr.bin').read_bytes())
2018-03-24 22:17:36 -07:00
if args.outputbase != 'stdout':
if not args.configfiles:
args.configfiles.append('txt')
for configfile in args.configfiles:
# cp cache -> output
tessfile = args.outputbase + '.' + configfile
2018-03-25 00:00:12 -07:00
shutil.copy(str(cache_folder / configfile) + '.bin',
tessfile)
2015-12-17 12:52:12 -08:00
sys.exit(0)
# Cache miss
2018-03-24 22:17:36 -07:00
print("MISS", file=sys.stderr)
2015-12-17 12:52:12 -08:00
# Call tesseract
2018-03-24 22:17:36 -07:00
print(sys.argv[1:])
p = subprocess.run(
['tesseract'] + sys.argv[1:],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2018-03-24 22:17:36 -07:00
sys.stdout.buffer.write(p.stdout)
sys.stderr.buffer.write(p.stderr)
if p.returncode != 0:
# Do not cache errors or crashes
print("Tesseract error", file=sys.stderr)
return p.returncode
2018-03-25 00:00:12 -07:00
(cache_folder / 'stdout.bin').write_bytes(p.stdout)
2018-03-24 22:17:36 -07:00
if args.outputbase != 'stdout':
if not args.configfiles:
args.configfiles.append('txt')
for configfile in args.configfiles:
if configfile not in ('hocr', 'pdf', 'txt'):
continue
# cp pwd/{outputbase}.{configfile} -> {cache}/{configfile}
tessfile = args.outputbase + '.' + configfile
2018-03-25 00:00:12 -07:00
shutil.copy(tessfile, str(cache_folder / configfile) + '.bin')
2018-03-24 22:17:36 -07:00
2018-03-25 00:00:12 -07:00
(cache_folder / 'stderr.bin').write_bytes(p.stderr)
2018-03-24 22:17:36 -07:00
manifest = {}
manifest['tesseract_version'] = __version__.replace('\n', ' ')
manifest['platform'] = platform.platform()
manifest['python'] = platform.python_version()
manifest['argv_slug'] = argv_slug
manifest['sourcefile'] = str(Path(source).relative_to(TESTS_ROOT))
def clean_sys_argv():
for arg in sys.argv[1:]:
yield re.sub(r'.*/com.github.ocrmypdf[^/]+[/](.*)',
r'$TMPDIR/\1', arg)
manifest['args'] = list(clean_sys_argv())
2018-03-24 22:17:36 -07:00
with (Path(CACHE_ROOT) / 'manifest.jsonl').open('a') as f:
json.dump(manifest, f)
f.write('\n')
f.flush()
2015-12-17 12:52:12 -08:00
if __name__ == '__main__':
main()