Files
ocrmypdf/tests/spoof/tesseract_cache.py
T

199 lines
6.9 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.
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
2018-12-30 00:47:12 -08:00
Assumes Tesseract 4.0.0-alpha or higher.
2016-08-26 15:04:08 -07:00
"""
2018-06-23 01:47:53 -07:00
import argparse
import json
2018-12-30 01:28:15 -08:00
import os
2018-06-23 01:47:53 -07:00
import platform
import re
2018-12-30 01:28:15 -08:00
import shutil
import subprocess
import sys
from pathlib import Path
2018-06-23 01:47:53 -07:00
2018-03-24 22:17:36 -07:00
__version__ = subprocess.check_output(
2018-12-30 01:27:49 -08:00
['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(
2018-12-30 01:27:49 -08:00
prog='tesseract-cache', description='cache output of tesseract'
)
2018-03-24 22:17:36 -07:00
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
2018-12-30 01:27:49 -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():
2018-12-30 01:27:49 -08:00
if any(
opt in sys.argv[1:]
for opt in ('--print-parameters', '--list-langs', '--version')
):
2018-03-24 22:17:36 -07:00
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]
if '_OCRMYPDF_TEST_INFILE' not in os.environ:
real_tesseract() # test not properly set up
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-05-01 15:52:00 -07:00
cache_disabled = os.environ.get('_OCRMYPDF_CACHE_DISABLED', False)
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
elif arg == '-c' or arg.startswith('textonly'):
pass
2018-03-24 22:17:36 -07:00
else:
yield arg
argv_slug = '__'.join(slugs())
argv_slug = argv_slug.replace('/', '___')
2018-06-23 01:51:19 -07:00
2018-03-24 22:17:36 -07:00
cache_folder = Path(CACHE_ROOT) / Path(source).stem / argv_slug
2018-06-23 01:51:19 -07:00
cache_folder.mkdir(parents=True, exist_ok=True)
2018-03-24 22:17:36 -07:00
2018-12-31 15:00:02 -08:00
print(f"Tesseract cache folder {cache_folder} - ", end='', file=sys.stderr)
2018-03-24 22:17:36 -07:00
2018-05-01 15:52:00 -07:00
if (cache_folder / 'stderr.bin').exists() and not cache_disabled:
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-12-30 01:27:49 -08: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(
2018-12-30 01:27:49 -08:00
['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))
2018-12-30 01:27:49 -08:00
def clean_sys_argv():
for arg in sys.argv[1:]:
2018-12-30 01:27:49 -08:00
yield re.sub(r'.*/com.github.ocrmypdf[^/]+[/](.*)', r'$TMPDIR/\1', arg)
manifest['args'] = list(clean_sys_argv())
2018-06-23 01:53:04 -07:00
# pylint: disable=E1101
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()