Repository navigation
Expand file tree
/
Copy pathpyproject.toml
More file actions
188 lines (180 loc) · 10.3 KB
/
Copy pathpyproject.toml
File metadata and controls
188 lines (180 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"
[project]
name = "intern-ocr-harness"
version = "0.0.0"
description = "Engine-agnostic benchmark harness for OCR on clinical medical images (PHI-handling — see CLAUDE.md §0)."
requires-python = ">=3.12" # D-0.2: most modern floor with full wheel support; re-verify vs PaddleOCR+torch in Phase 9
# Core deps ONLY — pinned for determinism. NO engine deps (doctr/paddle/easyocr/
# transformers/anthropic/boto3/google-cloud-vision); those are added per-phase.
dependencies = [
"numpy==2.2.1",
"pandas==2.2.3",
"rapidfuzz==3.11.0",
"jiwer==3.1.0",
"Pillow==11.1.0",
"pydicom==3.0.1",
"matplotlib==3.10.0",
]
[project.optional-dependencies]
dev = [
"pytest==8.3.4",
"ruff==0.9.1",
]
# Engine deps, one extra per runner (Phase 9) — never in the core list above, so a core
# install stays minimal and an engine bump can't perturb the harness's own pins.
#
# CPU-only builds (D-9.1). docTR 1.0.x has torch/torchvision as HARD base deps, and on Linux
# plain-PyPI `torch` is the CUDA build (~2 GB of nvidia-* wheels). An extra cannot carry an
# index URL, so install CPU torch FIRST, then this extra:
#
# pip install torch==2.13.0+cpu torchvision==0.28.0+cpu \
# --index-url https://download.pytorch.org/whl/cpu
# pip install -e ".[doctr]"
#
# Resolved in-env 2026-07-31: torch 2.13.0+cpu, torchvision 0.28.0+cpu, doctr v1.0.1.
# Latency from a CPU build is NOT representative of GPU-served production numbers.
# Pretrained weights download once from doctr-static.mindee.com into ~/.cache/doctr/models
# (override with DOCTR_CACHE_DIR); no image ever leaves the environment.
doctr = ["python-doctr==1.0.1"]
#
# PP-OCRv6_medium (Phase 9). `paddleocr` pulls `paddlex[ocr-core]` but NOT the framework, so
# `paddlepaddle` is pinned explicitly. Plain `paddlepaddle` on PyPI IS the CPU build (the GPU
# build is the separate `paddlepaddle-gpu` package), so this extra needs no index URL — unlike
# [doctr] above — and `pip install -e ".[paddle]"` is enough (D-9.1, CPU-only).
#
# Resolved in-env 2026-07-31: paddleocr 3.7.0, paddlex 3.7.2, paddlepaddle 3.3.1;
# paddle.device.is_compiled_with_cuda() == False.
# Weights (PP-OCRv6_medium_det/_rec) download once into ~/.paddlex/official_models. paddlex
# 3.7 defaults PADDLE_PDX_MODEL_SOURCE to "huggingface", so the fetch stays inside the
# sandbox's network allowlist; no image ever leaves the environment.
# NOTE: the runner sets enable_mkldnn=False — the oneDNN CPU path raises NotImplementedError
# on this stack and no inference runs at all. See run_paddle_v6.py's docstring before bumping.
paddle = ["paddleocr==3.7.0", "paddlepaddle==3.3.1"]
#
# EasyOCR (Phase 9, floor/baseline arm). Depends on torch/torchvision, already satisfied by
# the CPU wheels installed for [doctr] above — install those FIRST if starting from scratch,
# or plain-PyPI `torch` will pull the CUDA build (D-9.1, CPU-only):
#
# pip install torch==2.13.0+cpu torchvision==0.28.0+cpu \
# --index-url https://download.pytorch.org/whl/cpu
# pip install -e ".[easyocr]"
#
# Resolved in-env 2026-07-31: easyocr 1.7.2, python-bidi 0.6.11, opencv-python-headless
# 5.0.0.93, scikit-image 0.26.0. No pin needed for python-bidi: easyocr 1.7.2 imports
# `bidi.algorithm.get_display`, which the 0.6.x rewrite dropped and then restored — 0.6.11
# imports cleanly. Re-verify that import before bumping either package.
# Weights (CRAFT detector + english_g2 recognizer) download once into ~/.EasyOCR/model from
# the project's public release assets; no image ever leaves the environment.
easyocr = ["easyocr==1.7.2"]
#
# Tesseract (Phase 10c, GT-seeding arm — D-10c.5). pip installs the WRAPPER ONLY:
# `pytesseract` is a thin subprocess shim, it bundles no OCR. The things that actually decide
# what a seed says are system-level and installed separately — the `tesseract` binary, its
# Leptonica image backend, and the `eng.traineddata` language model. A bump to ANY of those
# three changes the seed output while this pin stays identical, and that invalidates every
# seed already written exactly the way an engine bump invalidates results (CLAUDE.md rule #9):
# re-seed, don't mix, surface it loudly. That is why the seeder records a version for each of
# the three and a sha256 for the traineddata — the pin below is not the provenance.
#
# sudo apt-get install -y tesseract-ocr
# # tessdata_best (D-10c.2b: the settled variant; apt ships `standard`, not `best`)
# sudo curl -fsSL -o /usr/share/tesseract-ocr/5/tessdata/eng.traineddata \
# https://github.com/tesseract-ocr/tessdata_best/raw/main/eng.traineddata
# pip install -e ".[tesseract]"
#
# The tessdata path above is Debian/Ubuntu-specific and varies by distro and major version
# (e.g. /usr/share/tessdata, /usr/local/share/tessdata, Homebrew's Cellar path) — confirm it
# on the host rather than assuming. The seeding script never guesses: it takes --tessdata-dir
# explicitly and hashes the traineddata it was actually pointed at.
# Resolved in-env 2026-08-06: pytesseract 0.3.13 (wrapper pin only; binary/Leptonica/
# traineddata versions + traineddata sha256 are recorded per-run in the seed metadata).
tesseract = ["pytesseract==0.3.13"]
#
# SVTRv2 (Phase 13c, READING arm — not an end-to-end engine). `openocr-python` is the
# packaged OpenOCR release; this project uses its `task='rec'` recognizer only, never its
# detector (ruled out — plan.md PHASE 9). Depends on torch/torchvision, already satisfied by
# the CPU wheels installed for [doctr] above — install those FIRST if starting from scratch,
# or plain-PyPI `torch` will pull the CUDA build (D-9.1, CPU-only).
#
# Resolved in-env 2026-08-11: openocr-python 0.1.5, torch 2.13.0+cpu.
# Weights (openocr_svtrv2_ch.pth, ~124 MB) download once into ~/.cache/openocr from
# ModelScope, falling back to HuggingFace; no crop ever leaves the environment.
openocr = ["openocr-python==0.1.5"]
#
# Qwen3-VL-4B-Instruct (Phase 13d, reader Arm C — the VLM reader and the D-11.1 evidence).
# Self-hosted only: weights load from the local HF cache and inference runs in-process, so no
# crop ever leaves the environment (CLAUDE.md §4).
#
# ⚠️ INSTALL THIS IN A WORKTREE-LOCAL venv, NEVER THE SHARED BENCHMARK venv. A worktree's
# `.venv` is normally a symlink to the repo-root one, so a pip install from inside it changes
# the environment docTR/PP-OCRv6/EasyOCR are being measured in — invalidating cross-run
# comparability silently, the way an engine bump does (rule #9). Delete the symlink and make a
# real venv first (CLAUDE.md, "Git worktrees + the shared .venv").
#
# Same CPU-torch-first order as [doctr]: an extra cannot carry an index URL, and plain-PyPI
# `torch` on Linux is the CUDA build (~2 GB of nvidia-* wheels).
#
# pip install torch==2.13.0+cpu torchvision==0.28.0+cpu --index-url https://download.pytorch.org/whl/cpu
# pip install -e ".[qwen3vl]"
#
# Resolved in-env 2026-08-11: transformers 5.15.0, tokenizers 0.22.2, safetensors 0.8.0,
# accelerate 1.14.0, torch 2.13.0+cpu. transformers >= 5.x is required for `Qwen3VLForConditionalGeneration`
# (added 4.57); the reader loads it through `AutoModelForImageTextToText`.
# Weights (~9 GB, 2 safetensors shards) download once into ~/.cache/huggingface at the commit
# sha pinned in `harness/readers/read_qwen3vl.py:REVISION`; no image ever leaves the environment.
# A model-revision change invalidates prior results exactly like a library bump — the sha is in
# the reader's config() and therefore in every row's config_hash.
qwen3vl = ["transformers==5.15.0", "accelerate==1.14.0"]
#
# Gemini 2.5 Pro (Phase 13g, reader Arm C — the ONLY arm that leaves the environment).
# `google-genai` is the SDK, not the model: it carries no weights and pins no revision, so
# unlike every other extra here this line does NOT fix what the arm reads. Vertex serves the
# alias `gemini-2.5-pro` and reports no finer revision, so the arm's version is pinned from
# the RESPONSE at run time and the run date is part of its identity — see
# `harness/readers/read_gemini.py` and `prompts/phase13/13g_RESULT.md` §5.
#
# ⚠️ BAA-GATED. Real frames require a human-set `OCR_BAA_CLEARED_GEMINI=1` after the D-12.1
# checklist, and Vertex AI ONLY: the Gemini Developer API / AI Studio key surface is a
# different product and is NOT BAA-covered (CLAUDE.md §4). Installing this extra clears
# nothing — it only makes the import work.
#
# ⚠️ Install in a WORKTREE-LOCAL venv, never the shared benchmark one (same reason as
# [qwen3vl] above: rule #9 comparability).
#
# pip install -e ".[gemini]"
#
# Resolved in-env 2026-08-11: google-genai 2.17.0.
gemini = ["google-genai==2.17.0"]
[tool.setuptools.packages.find]
# `ground_truth*` added for Phase 10a: `gt_schema.py` is imported by the tests now and by
# `build_gt.py` (10e) / the real run loop (Phase 13) later, so it needs one import form that
# works under pytest, under `pip install -e .`, and from any cwd. Source files only —
# setuptools ships no package data, so `gt.csv` (gitignored) can never be packaged.
include = ["harness*", "ground_truth*"]
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-ra"
[tool.ruff]
target-version = "py312"
line-length = 100
# .claude/ holds Claude Code config + the safety-critical PHI guard hooks — not harness
# source, and never to be reformatted by lint (CLAUDE.md §6.4). Lint project code only.
extend-exclude = [".claude"]
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"]
[tool.ruff.lint.per-file-ignores]
# The module docstring carries the exact command Arnav copy-pastes to run the build. It is
# deliberately ONE line: a backslash-continued paste mangles the flags into literal escaped
# spaces. Wrapping it to satisfy E501 would break the thing the docstring exists for.
"ground_truth/build_gt.py" = ["E501"]
# Same reason, same one-line copy-paste command (Phase 13h): the dev-slice draw and the
# threshold sweep are both run by hand from their docstrings.
"ground_truth/select_dev_slice.py" = ["E501"]
"experiments/sweep_stock_vs_tuned.py" = ["E501"]
# Same again (Phase 13d): the reader's negative-control run is a one-line copy-paste with five
# path flags. Only the docstring command needs it — the code in that file stays inside 100.
"experiments/reader_negative_control.py" = ["E501"]
# Same reason again (Phase 13g): the docstring carries the one-line bench command.
"experiments/bench_reader_synthetic.py" = ["E501"]