|
| 1 | +# -*- mode: python ; coding: utf-8 -*- |
| 2 | +from pathlib import Path |
| 3 | +import importlib.util |
| 4 | +import os |
| 5 | +import sys |
| 6 | + |
| 7 | +from PyInstaller.utils.hooks import collect_all |
| 8 | +from PyInstaller.utils.hooks import copy_metadata |
| 9 | + |
| 10 | +# PyInstaller exposes SPECPATH as the directory containing this spec file, |
| 11 | +# not the spec filename itself. Taking `.parent` here points one directory |
| 12 | +# above the project when the spec is invoked directly. |
| 13 | +ROOT = Path(SPECPATH).resolve() |
| 14 | +paddleocr_spec = importlib.util.find_spec('paddleocr') |
| 15 | +if paddleocr_spec is None or not paddleocr_spec.submodule_search_locations: |
| 16 | + raise SystemExit('paddleocr==3.6.0 is required to build FormulaOCR.') |
| 17 | +paddleocr_package_dir = Path( |
| 18 | + next(iter(paddleocr_spec.submodule_search_locations)) |
| 19 | +).resolve() |
| 20 | +datas = [ |
| 21 | + (str(paddleocr_package_dir), 'PaddleOCR-main/paddleocr'), |
| 22 | + (str(ROOT / 'icon.png'), '.'), |
| 23 | + (str(ROOT / 'icon.ico'), '.'), |
| 24 | +] |
| 25 | +bundled_models_root = os.environ.get('FORMULA_OCR_BUNDLED_PADDLE_MODELS', '').strip() |
| 26 | +if bundled_models_root: |
| 27 | + bundled_root = Path(bundled_models_root) |
| 28 | + required_model_files = {'inference.json', 'inference.yml', 'inference.pdiparams'} |
| 29 | + if bundled_root.is_dir(): |
| 30 | + for model_dir in bundled_root.iterdir(): |
| 31 | + required = required_model_files |
| 32 | + if model_dir.name == 'LaTeX_OCR_rec': |
| 33 | + required = required | {'config.json'} |
| 34 | + if model_dir.is_dir() and required.issubset( |
| 35 | + {item.name for item in model_dir.iterdir() if item.is_file()} |
| 36 | + ): |
| 37 | + datas.append((str(model_dir), f'models/paddle/{model_dir.name}')) |
| 38 | +bundled_onnx_root = os.environ.get('FORMULA_OCR_BUNDLED_ONNX_MODELS', '').strip() |
| 39 | +onnx_model_files = { |
| 40 | + 'RapidLaTeXOCR': {'image_resizer.onnx', 'encoder.onnx', 'decoder.onnx', 'tokenizer.json'}, |
| 41 | + 'MathCraftFormula': { |
| 42 | + 'config.json', 'encoder_model.onnx', 'decoder_model.onnx', |
| 43 | + 'generation_config.json', 'preprocessor_config.json', |
| 44 | + 'special_tokens_map.json', 'tokenizer.json', 'tokenizer_config.json', |
| 45 | + }, |
| 46 | + 'Pix2TextMFR15': { |
| 47 | + 'config.json', 'encoder_model.onnx', 'decoder_model.onnx', |
| 48 | + 'generation_config.json', 'preprocessor_config.json', |
| 49 | + 'special_tokens_map.json', 'tokenizer.json', 'tokenizer_config.json', |
| 50 | + }, |
| 51 | + 'MixTexZhEn': { |
| 52 | + 'added_tokens.json', 'config.json', 'decoder_model_merged.onnx', |
| 53 | + 'encoder_model.onnx', 'generation_config.json', 'merges.txt', |
| 54 | + 'preprocessor_config.json', 'special_tokens_map.json', 'tokenizer.json', |
| 55 | + 'tokenizer_config.json', 'vocab.json', |
| 56 | + }, |
| 57 | + 'UniMERNetSmallONNX': { |
| 58 | + 'config.json', 'decoder_model_quantized.onnx', |
| 59 | + 'decoder_with_past_model_quantized.onnx', |
| 60 | + 'encoder_model_quantized.onnx', 'preprocessor_config.json', |
| 61 | + 'tokenizer.json', |
| 62 | + }, |
| 63 | +} |
| 64 | +if bundled_onnx_root: |
| 65 | + onnx_root = Path(bundled_onnx_root) |
| 66 | + if onnx_root.is_dir(): |
| 67 | + for model_dir in onnx_root.iterdir(): |
| 68 | + required = onnx_model_files.get(model_dir.name) |
| 69 | + if model_dir.is_dir() and required and required.issubset( |
| 70 | + {item.name for item in model_dir.iterdir() if item.is_file()} |
| 71 | + ): |
| 72 | + datas.append((str(model_dir), f'models/onnx/{model_dir.name}')) |
| 73 | +binaries = [] |
| 74 | +runtime_root = Path(sys.executable).resolve().parent |
| 75 | +runtime_bin_dirs = ( |
| 76 | + runtime_root / 'Library' / 'bin', |
| 77 | + runtime_root / 'DLLs', |
| 78 | + runtime_root, |
| 79 | +) |
| 80 | +for dll_names in ( |
| 81 | + ('tcl86t.dll',), |
| 82 | + ('tk86t.dll',), |
| 83 | + ('libexpat.dll', 'expat.dll'), |
| 84 | +): |
| 85 | + dll_path = next( |
| 86 | + ( |
| 87 | + directory / dll_name |
| 88 | + for directory in runtime_bin_dirs |
| 89 | + for dll_name in dll_names |
| 90 | + if (directory / dll_name).is_file() |
| 91 | + ), |
| 92 | + None, |
| 93 | + ) |
| 94 | + if dll_path is not None: |
| 95 | + binaries.append((str(dll_path), '.')) |
| 96 | +hiddenimports = ['paddle', 'paddlex', 'numpy', 'tokenizers', 'onnxruntime', 'rapid_latex_ocr'] |
| 97 | +datas += copy_metadata('tokenizers') |
| 98 | +datas += copy_metadata('latex2mathml') |
| 99 | +datas += copy_metadata('paddleocr') |
| 100 | +for package_name in ( |
| 101 | + 'paddle', |
| 102 | + 'paddlex', |
| 103 | + 'cv2', |
| 104 | + 'tokenizers', |
| 105 | + 'pypdfium2', |
| 106 | + 'latex2mathml', |
| 107 | + 'onnxruntime', |
| 108 | + 'rapid_latex_ocr', |
| 109 | +): |
| 110 | + package_datas, package_binaries, package_hiddenimports = collect_all(package_name) |
| 111 | + datas += package_datas |
| 112 | + binaries += package_binaries |
| 113 | + hiddenimports += package_hiddenimports |
| 114 | +hiddenimports = list(dict.fromkeys(hiddenimports)) |
| 115 | + |
| 116 | + |
| 117 | +a = Analysis( |
| 118 | + [str(ROOT / 'formula_ocr_app' / 'app.py')], |
| 119 | + pathex=[str(ROOT)], |
| 120 | + binaries=binaries, |
| 121 | + datas=datas, |
| 122 | + hiddenimports=hiddenimports, |
| 123 | + hookspath=[], |
| 124 | + hooksconfig={}, |
| 125 | + runtime_hooks=[], |
| 126 | + excludes=['tensorflow', 'torch', 'torchvision', 'torchaudio', 'modelscope', 'matplotlib', 'sklearn', 'scipy', 'paddle.tensorrt', 'paddlex.inference.serving', 'shapely.tests'], |
| 127 | + noarchive=False, |
| 128 | + optimize=0, |
| 129 | +) |
| 130 | +pyz = PYZ(a.pure) |
| 131 | + |
| 132 | +exe_options = dict( |
| 133 | + name='FormulaOCR', |
| 134 | + debug=False, |
| 135 | + bootloader_ignore_signals=False, |
| 136 | + strip=False, |
| 137 | + upx=True, |
| 138 | + console=False, |
| 139 | + disable_windowed_traceback=False, |
| 140 | + argv_emulation=False, |
| 141 | + target_arch=None, |
| 142 | + codesign_identity=None, |
| 143 | + entitlements_file=None, |
| 144 | + icon=[str(ROOT / 'icon.ico')], |
| 145 | +) |
| 146 | + |
| 147 | +exe = EXE( |
| 148 | + pyz, |
| 149 | + a.scripts, |
| 150 | + [], |
| 151 | + exclude_binaries=True, |
| 152 | + contents_directory='_internal', |
| 153 | + **exe_options, |
| 154 | +) |
| 155 | +coll = COLLECT( |
| 156 | + exe, |
| 157 | + a.binaries, |
| 158 | + a.datas, |
| 159 | + strip=False, |
| 160 | + upx=True, |
| 161 | + upx_exclude=[], |
| 162 | + name='FormulaOCR', |
| 163 | +) |
0 commit comments