Metadata-Version: 2.4
Name: ocr-pipeline
Version: 0.1.0
Summary: OCR pipeline for life science screenshots - RAG ready
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: typer>=0.9.0
Requires-Dist: pydantic>=2.6.0
Requires-Dist: pydantic-settings>=2.2.0
Requires-Dist: pyyaml>=6.0.1
Requires-Dist: structlog>=24.1.0
Requires-Dist: rich>=13.7.0
Requires-Dist: tqdm>=4.66.0
Requires-Dist: opencv-python-headless>=4.9.0
Requires-Dist: pillow>=10.2.0
Requires-Dist: numpy<2.0,>=1.26.0; python_version < "3.12"
Requires-Dist: numpy>=1.26.0; python_version >= "3.12"
Requires-Dist: pytesseract>=0.3.10
Requires-Dist: langchain-text-splitters>=0.0.2
Requires-Dist: nbformat>=5.9.0
Requires-Dist: sqlite-utils>=3.37.0
Requires-Dist: watchdog>=3.0.0
Requires-Dist: python-slugify>=8.0.0
Requires-Dist: xxhash>=3.4.0
Requires-Dist: python-magic>=0.4.27
Requires-Dist: platformdirs>=4.2.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0.0; extra == "dev"
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
Requires-Dist: pytest-mock>=3.12.0; extra == "dev"
Requires-Dist: ruff>=0.2.0; extra == "dev"
Requires-Dist: mypy>=1.8.0; extra == "dev"
Requires-Dist: pre-commit>=3.6.0; extra == "dev"
Provides-Extra: grobid
Provides-Extra: paddle
Requires-Dist: paddleocr<3.0.0,>=2.7.0; extra == "paddle"
Requires-Dist: paddlepaddle>=2.6.0; extra == "paddle"
Provides-Extra: scientific
Requires-Dist: spacy<3.8.0,>=3.7.0; python_version < "3.12" and extra == "scientific"
Requires-Dist: spacy>=3.7.0; python_version >= "3.12" and extra == "scientific"
Requires-Dist: scispacy==0.5.4; python_version < "3.12" and extra == "scientific"
Provides-Extra: tables
Requires-Dist: torch>=2.2.0; extra == "tables"
Requires-Dist: torchvision>=0.17.0; extra == "tables"
Requires-Dist: transformers>=4.38.0; extra == "tables"
Requires-Dist: timm>=1.0.0; extra == "tables"
Provides-Extra: figures
Requires-Dist: layoutparser>=0.3.0; extra == "figures"
Provides-Extra: full
Requires-Dist: ocr-pipeline[dev,figures,paddle,scientific,tables]; extra == "full"

# OCR Pipeline for Life Science Screenshots

Turns scientific screenshots into RAG-ready Markdown, with metadata attached.

- Reliable Tesseract OCR by default, with optional PaddleOCR
- Preprocessing: deskew, denoise, CLAHE, line removal
- Optional figure/table detection
- Optional scientific entity extraction with scispaCy
- Citation matching and embedding-ready chunking

## Quick start

```bash
# The default basic profile is lightweight: Tesseract, cleanup, citations,
# chunking, Markdown, and no model downloads.
uv sync
uv run ocr-pipeline doctor

# Smoke-test a small directory.
uv run ocr-pipeline run \
  --input-dir ~/Pictures/test_screenshots \
  --output-dir /tmp/ocr-output \
  --workers 1 \
  --exclude "*.photoslibrary/*"
```

Use `ocr-pipeline doctor` before a first run or after changing environments. It checks the active profile without loading ML models or downloading model weights.

Use `--force` to reprocess files already recorded in the processing database. The
`--exclude` option is repeatable and prevents recursive scans from entering bundles
such as macOS `.photoslibrary` directories.

### Capability profiles

| Profile | What it enables | Intended environment |
| --- | --- | --- |
| `basic` (default) | Tesseract OCR, cleanup, citations, chunks, Markdown | Python 3.11+ |
| `scientific` | Basic + `en_core_sci_lg` entity extraction | **Python 3.11** |
| `full` | Scientific + PaddleOCR + figure/table detection | Platform-specific ML environment |

Select a profile per run with `--profile`, or set `profile: basic`, `scientific`, or
`full` at the top of `config.yaml`. The profile is an explicit promise: unavailable
optional features are reported by `doctor`; they are not silently initialized on a
basic run.

```bash
uv run ocr-pipeline doctor --profile scientific
uv run ocr-pipeline setup scientific  # prints the safe setup commands
uv run ocr-pipeline run --profile scientific --input-dir ~/Pictures/test_screenshots
```

#### Scientific NER environment

`en_core_sci_lg` is distributed separately from scispaCy. Create a dedicated Python
3.11 environment—do not mix its compiled spaCy stack with a Python 3.13 environment:

```bash
uv venv --python 3.11 .venv-scientific
uv pip install --python .venv-scientific/bin/python -e '.[scientific]'
uv pip install --python .venv-scientific/bin/python \
  https://s3-us-west-2.amazonaws.com/ai2-s2-scispacy/releases/v0.5.4/en_core_sci_lg-0.5.4.tar.gz

.venv-scientific/bin/ocr-pipeline doctor --profile scientific
.venv-scientific/bin/ocr-pipeline run --profile scientific --input-dir ~/Pictures/test_screenshots
```

Use the explicit `.venv-scientific/bin/ocr-pipeline` path for that environment;
`uv run` always selects the project's default `.venv`.

## Docker

```bash
docker build -t ocr-pipeline .

docker run -v ~/Pictures:/data/screenshots -v ./data:/app/data ocr-pipeline run
docker run -v ~/Pictures:/data/screenshots -v ./data:/app/data ocr-pipeline watch
```

## Config

<details>
<summary><code>config.yaml</code></summary>

```yaml
input:
  paths: ["~/Pictures", "/mnt/storage3/aman/screenshots"]
  patterns: ["SCR-*.png", "*.jpg", "*.jpeg", "*.tiff"]
  recursive: true
  exclude_patterns: ["*.photoslibrary/*"]

ocr:
  engine: "tesseract"  # paddleocr | tesseract | auto
  languages: ["en", "latin"]
  use_gpu: false
  preprocess:
    deskew: true
    denoise: true
    clahe: true
    adaptive_threshold: true
    remove_lines: true

processing:
  workers: 4
  batch_size: 10
  retry_attempts: 2

detectors:
  figures:
    enabled: false
    confidence_threshold: 0.7
  tables:
    enabled: false
    confidence_threshold: 0.7

entities:
  enabled: false
  model: "en_core_sci_lg"

citations:
  regex_enabled: true
  grobid_enabled: false  # set true if you're running a GROBID server

chunking:
  chunk_size: 1000
  chunk_overlap: 200

output:
  base_directory: "./data/ocr_output"
  organize_by: "date_run"  # date_run | source_dir | flat
  write_consolidated: true
  consolidated_filename: "all_ocr.md"
  frontmatter:
    - source_path
    - source_hash
    - timestamp
    - ocr_engine
    - ocr_confidence_mean
    - language
    - detected_entities
    - entity_extraction_backend
    - has_figures
    - has_tables
    - citations_found
    - chunk_index
    - total_chunks

watch:
  enabled: true
  debounce_seconds: 5
  db_path: "./data/processed_files.db"
```

</details>

## Output

Each chunk is a Markdown file with YAML frontmatter:

```markdown
---
source_path: "/Users/Aman/Pictures/SCR-20250115-gel.png"
source_hash: "a1b2c3d4e5f6..."
timestamp: "2025-01-15T10:30:00Z"
ocr_engine: "paddleocr"
ocr_confidence_mean: 0.91
language: "en"
detected_entities: ["GENE", "PROTEIN", "CHEMICAL"]
has_figures: true
has_tables: false
citations_found: ["DOI:10.1038/nature12345", "PMID:12345678"]
chunk_index: 0
total_chunks: 2
---

# Screenshot: SCR-20250115-gel.png

## Figures

### Figure 1
- BBox: [100, 200, 800, 600]
- Confidence: 0.92
- Caption: "Western blot showing BRCA1 expression..."

## Detected Entities

- BRCA1
- CRISPR
- β-actin

## Citations

- DOI: 10.1038/nature12345
- PMID: 12345678

## Extracted Text

**Western Blot Analysis of BRCA1 Expression**

Lane 1: WT control
Lane 2: BRCA1 KO (CRISPR)
Lane 3: BRCA1 KO + pBRCA1-WT rescue
Lane 4: BRCA1 KO + pBRCA1-C61G mutant

Anti-BRCA1 (1:1000), Anti-β-actin (1:5000)
```

Files land in `data/ocr_output/<date>/run_NNN/`. Individual chunk files are retained for RAG indexing. Set `output.write_consolidated: true` to also write `all_ocr.md` containing one frontmatter block and all source text grouped by image.

## Life science specifics

Gene/protein names go through scispaCy's `en_core_sci_lg`. Chemical formulas and units (µM, ng/mL, kb/Mb/Gb, °C, ×g) get normalized, scientific notation gets cleaned up (`1.5×10⁻³` → `1.5×10^-3`), and gel/blot figures get their captions pulled out separately. Citations are matched by regex for DOI, PMID, arXiv, PMC, and ISBN.

## Optional models

The basic profile downloads no ML models. Advanced profiles download models only when
the corresponding capability is enabled and used, cached in `~/.cache/ocr_pipeline/`:

- PaddleOCR models (~200MB)
- scispaCy `en_core_sci_lg` (~800MB)
- Table Transformer (~500MB)
- LayoutParser PubLayNet (~300MB)

## Runtime requirements and health

The core pipeline can run with Tesseract alone. PaddleOCR, scientific NER, figure detection, and table detection are optional capabilities with heavyweight, platform-specific dependencies. The pipeline records the OCR engine actually used and the entity-extraction backend in generated frontmatter; inspect them after each run rather than assuming configured models loaded.

- **PaddleOCR:** the project pins the legacy 2.x API used by the pipeline. Install the locked environment with `uv sync`; a startup fallback to Tesseract is logged when Paddle cannot initialize.
- **Scientific NER:** install a compatible `scispacy` distribution and the separately distributed `en_core_sci_lg` model before enabling production scientific NER. Without it, the pipeline uses its conservative regex fallback and marks the backend accordingly.
- **Figures:** `layoutparser`'s `Detectron2LayoutModel` requires a Detectron2 build matching your Torch/Python platform. It is intentionally not forced as a universal dependency because no single wheel supports every platform.
- **Tables:** the Table Transformer model is downloaded by Transformers on first use; ensure the selected model's optional dependencies (including `timm`, when required by that model revision) are installed in the runtime image.

Use `ocr-pipeline doctor --profile <profile>` to verify dependencies before processing. Use `ocr-pipeline config` to inspect effective settings. A configured but unavailable optional model is reported as missing by `doctor`; basic runs never attempt to initialize it.

## TODO

- [ ] Build a knowledge graph from extracted entities/citations

