Released: 2026-05-13
A major refactor of the Versatile-OCR-Program from a flat-script structure (v1.0_initial / v2.0_initial) to a modular, config-driven, automation-ready architecture. The pipeline behavior and OCR output remain unchanged — this release is a pure refactor + cleanup. All previously hardcoded user-tunable values are centralized in config.yaml and prompts/.
The flat 5-file structure (v2.0_initial/ containing Dockerfile, advanced_ocr.py, custom_doclayout_yolo.py, ocr_stage1.py, ocr_stage2.py) has been reorganized into:
src/ocr/— OCR engine modules (advanced_ocr.py,custom_doclayout_yolo.py,docker_build.py,Dockerfile)src/stages/— Stage execution wrappers (ocr_stage_1.py,ocr_stage_2.py— note the underscore in the new filenames)
Two new auto-run wrappers were added to the root for batch execution:
auto_run_stage1.py— Recursive PDF directory scanning. Walks an input tree, finds every directory containing PDFs, and invokesocr_stage_1.pyper directory. Skips hidden directories automatically.auto_run_stage2.py— Parallel processing of multiple GCS paths usingThreadPoolExecutor(default 70 workers). Real-time per-worker logging with worker IDs.
All hardcoded user-tunable values have been moved to config.yaml. Default values match the prior hardcoded values exactly — no behavior change. The new keys are:
ocr.language_hints(default["ja", "en", "ko"]) — Google Vision OCR language hintsocr.pdf_dpi(default200) — DPI used bypdf2image.convert_from_pathocr.iou_threshold(default0.5) — Region duplicate-merge IoU thresholdocr.image_processing.vision_max_dim(default1600) — Image resize cap for Vision APIocr.image_processing.gemini_max_dim(default1024) — Image resize cap for Gemini APIocr.image_processing.jpeg_quality(default85) — JPEG re-encoding qualitygcs.stage1_prefix(default"stage_1") — GCS path prefix for Stage 1 outputgcs.stage2_prefix(default"stage_2") — GCS path prefix for Stage 2 outputgcs.validate_max_results(default5) — Sample size for GCS path validationgemini.model(default"gemini-2.0-flash") — Gemini model for figure/table analysisgemini.figure_prompt_path(default"prompts/gemini_figure.txt")gemini.table_prompt_path(default"prompts/gemini_table.txt")stage2.system_prompt_path(default"prompts/chatgpt_stage2.md")docker.source_files.prompts(new entry:"prompts/") — Tellsdocker_build.pyto copy the prompts directory into the build context
auto_run.yaml separates execution parameters (input directory, mode, GCS paths, worker count) from the global config.yaml.
The long inline prompt strings previously hardcoded inside Python source have been extracted to dedicated files:
prompts/gemini_figure.txt— Gemini API prompt for figure region analysis (wasadvanced_ocr.py:714inline)prompts/gemini_table.txt— Gemini API prompt for table region analysis (wasadvanced_ocr.py:892inline)prompts/chatgpt_stage2.md— ChatGPT Stage 2 region-preserving system prompt (wasocr_stage_2.py:251inline)
A small _load_prompt(path) helper resolves relative paths against the config.yaml location (recorded in _config_dir when the config is loaded), so prompts load correctly from any working directory or inside Docker.
The local output directory pattern (/tmp/ocr_output) used in v2 is removed. Stage 1 uploads results directly to GCS, and Stage 2 reads from and writes to GCS exclusively. This decouples the two stages cleanly and enables parallel re-runs.
process_folder_stage2() is idempotent: it counts JSON files in Stage 1 vs Stage 2 directories and skips already-complete folders.
The [Formula Start]/[Formula End], [Image Start]/[Image End], [Table Start]/[Table End] placeholder system used in v2 to mark special content for ChatGPT preservation is removed. Stage 1 now emits a simplified output (regions array only — no flattened text, no metadata), and Stage 2's ChatGPT prompt enforces strict region-structure preservation (same count, same type/coords/id fields, only text modified).
Three-tier fallback in chatgpt_correct_text:
- JSON parse failure → use original regions
- Region-structure validation failure (
validate_region_structure) → use original regions - Exception → empty result
Region IDs are forcibly re-assigned by the code (page_{N}_region_{i}) regardless of what ChatGPT returns.
validate_direct_path() and related GCS path resolution now tries NFC normalization first, then falls back to NFD. This handles Korean filenames uploaded from macOS environments (a previously fragile area).
docker_build.py— Safe Docker build automation using a temporary build context. Smartconfig.yamldiscovery (walks up directory tree), per-source-file resolution, file existence check (--check-files), and optional context preservation for debugging (--keep-context). Now supports directory copy viashutil.copytree(required for the newprompts/directory).temp_controller.ipynb— Manual debug notebook. Not committed to the repository (see.gitignore); kept locally for ad-hoc single-directory runs during development.
The following non-functional changes were applied alongside the architectural refactor. All preserve external behavior.
- Removed leaked
GEMINI_API_KEYdebug print atsrc/stages/ocr_stage_1.py:37. The lineprint("DEBUG GEMINI:", os.getenv("GEMINI_API_KEY"))exposed the API key in plain text on every stage 1 run.
- Promoted
import reto the top-level ofsrc/ocr/advanced_ocr.py(was duplicated as in-function imports inside_process_figure_regionand_process_table_region). - Removed duplicate method definitions of
_process_title_regionand_process_list_regioninsrc/ocr/advanced_ocr.py. The second definitions (at the bottom of the class) were__doc__/comment variants of the earlier ones with identical functional code; they have been removed and the earlier (more descriptive) definitions retained. - Fixed
src/ocr/Dockerfile:6comment:NumPy <2.0 (1.24.3)→NumPy 1.26.4(matches the actualnumpy==1.26.4install). - Pinned
google-genai==1.68.0andpython-dotenv==1.2.2insrc/ocr/Dockerfile(previously unpinned).
Two cache-key algorithm constants were promoted to module-level constants but not externalized to config, since changing them invalidates all cached results:
HASH_THUMBNAIL_SIZE = 32
HASH_JPEG_QUALITY = 50These are not user-tunable in the practical sense.
The following project-specific patterns were added:
src/input/ # Personal learning materials (Korean math textbook PDFs)
cache/ # OCR image hash cache (contains personal data results)
*.swp # Vim swap files
temp_controller.ipynb # Manual debug notebook
If you were running v2.0_initial/ocr_stage1.py directly:
-
Required env vars (unchanged):
OPENAI_API_KEY,MATHPIX_APP_ID,MATHPIX_APP_KEY,GOOGLE_SHEETS_SPREADSHEET_ID,GOOGLE_APPLICATION_CREDENTIALS,GEMINI_API_KEY. -
New required files at repo root:
config.yamlandauto_run.yaml. The example values in the committedconfig.yamlreflect a specific local environment (/home/jupyter/Program/.env,/home/jupyter/Program/credentials, etc.) — adjust paths to your environment before running. -
Output destination: v2 produced both local output files and GCS uploads; v3 is GCS-only. Update any downstream consumers that read local output files.
-
Stage 1 changes:
- Entry script renamed:
ocr_stage1.py→src/stages/ocr_stage_1.py - For recursive batch runs, use the new
auto_run_stage1.pywrapper. - Output JSON now contains regions only (the flattened
textfield,image_pathmetadata, andprocessing_timestamphave been removed). - GCS path structure:
stage_1/{relative_path}/{pdf_name}/page_{NNN}.jsonwherestage_1is configurable viagcs.stage1_prefix.
- Entry script renamed:
-
Stage 2 changes:
- Entry script renamed:
ocr_stage2.py→src/stages/ocr_stage_2.py - For parallel multi-path runs, use the new
auto_run_stage2.pywrapper. - System prompt now externalized to
prompts/chatgpt_stage2.md(path configurable instage2.system_prompt_path). - Model is configurable via
stage2.model(defaultgpt-5-nano). - Special content placeholders (
[Formula Start]/[Formula End]etc.) are no longer recognized — Stage 1 output is already simplified to a regions-only array.
- Entry script renamed:
-
Docker:
Dockerfilerelocated tosrc/ocr/Dockerfile.- Prefer
docker_build.pyover directdocker build— it manages a temporary build context driven bydocker.source_filesinconfig.yaml. - The build context now includes the
prompts/directory (configured viadocker.source_files.promptsand copied bydocker_build.py'screate_build_context).
-
Tunable values: Anything you might want to vary per user/environment (OCR language hints, PDF DPI, image max dimensions, JPEG quality, Gemini model name, GCS path prefixes, prompt texts) is now in
config.yamlorprompts/. Edit there, not in source code. -
Jupyter / Vertex AI users: Docker group permission setup is still required — see
setup_guide.md§3 (sudo usermod -aG docker jupyter && sudo reboot).
| File | Change |
|---|---|
auto_run.yaml |
NEW |
auto_run_stage1.py |
NEW |
auto_run_stage2.py |
NEW |
config.yaml |
NEW (with v3 centralization items) |
src/ocr/advanced_ocr.py |
Relocated from v2.0_initial/, refactored for config-driven settings, prompt externalization, hash constants, method dedup, top-level import re |
src/ocr/custom_doclayout_yolo.py |
Relocated from v2.0_initial/, already config-driven in upstream |
src/ocr/docker_build.py |
NEW (safe build context automation, supports directory copy) |
src/ocr/Dockerfile |
Refactored from v2.0_initial/, COPY prompts/, pinned google-genai and python-dotenv, NumPy comment fix |
src/stages/ocr_stage_1.py |
Renamed from ocr_stage1.py, container path mapping, debug print removed, config-driven Docker execution |
src/stages/ocr_stage_2.py |
Renamed from ocr_stage2.py, region-preserving prompt externalized, idempotent processing, Unicode NFC/NFD fallback, GCS prefix from config |
prompts/ |
NEW directory (3 prompt files) |
changes/ |
NEW directory (2 documents — v2.0_initial.md migrated from patch_notes/, plus this release doc) |
legacy/ |
NEW directory — v1.0_initial/ and v2.0_initial/ moved here, plus a README.md explaining the move |
setup_guide.md |
Rewritten for v3 (clone → API keys → Docker → config → run → verification → troubleshooting) |
usage.md |
NEW — operational manual (CLI flags, scenarios, config reference, prompt editing, results, idempotency, FAQ) |
.gitignore |
Extended with 4 project-specific patterns |
sample_images/, README.md, LICENSE, planned_features.md |
Unchanged |
patch_notes/ |
Removed (content moved into changes/) |
Use this version (main after this release) for any new deployment of Versatile-OCR-Program. The legacy/v1.0_initial/ and legacy/v2.0_initial/ folders remain available for reference but should not be used for new work.
For questions, contact: raphael.es.seo@gmail.com