Skip to main content

Working with Images and Scanned Documents

Upload an image or a PDF with scanned pages and InfoConnect uses OCR to extract text for search. Image uploads require the RapidOCR feature; PDF pages use OCR only when their native text is below the configured threshold and the feature is enabled.

What You Can Do​

  • Find text inside screenshots and photos
  • Search scanned PDFs without retyping
  • Keep OCR output in the same searchable pipeline as native document text
How it works

RapidOCR returns normalized text for an image. The ingestion pipeline stores the extraction method in metadata, then continues with chunking, embeddings, and (when enabled) classification.

This guide uses the Harry Potter Wikipedia screenshot included in the repo (test-documents/harry-potter-wiki-frontpage.png).

Before You Begin​

Make sure these services are running:

just prod-up
just status

Also check that the API is responding:

curl http://localhost:8000/health
# Example response (version matches the running release; see the Health API for current fields):
# {"status":"healthy","version":"0.5.15","components":{"api":"ok","redis":"ok","qdrant":"ok","celery":"ok","modernbert-onnx":"ok"}}

The health response always reports the product version. The modernbert-onnx component is reported when classification is enabled; publication-enabled deployments may also report a publication component.

Step 1: Make Sure OCR Is Ready​

Check that OCR is enabled in your environment:

grep '^RAPIDOCR_OCR_ENABLED=' .env
# Output:
# RAPIDOCR_OCR_ENABLED=true

You should see RAPIDOCR_OCR_ENABLED=true. The default OCR backend is RAPIDOCR_MODEL_NAME=rapidocr-onnxruntime.

Step 2: Upload an Image or Scanned PDF​

Use the Harry Potter screenshot included in the repo:

curl -X POST http://localhost:8000/embed \
-H "X-API-Key: super-secret-key" \
-F "file=@test-documents/harry-potter-wiki-frontpage.png" \
-F "document_id=harry-potter-ocr-001"
# Output:
# {"job_id":"<job-id>","document_id":"harry-potter-ocr-001","status":"pending"}

Save the job_id from the response. You'll need it in the next step.

info

What happens: the image is sent to RapidOCR. Its extracted text then enters the regular chunking and embedding pipeline.

Step 3: Wait for OCR to Finish​

Poll the status endpoint with the job_id from the upload response:

curl http://localhost:8000/status/<job-id> \
-H "X-API-Key: super-secret-key"

The embedding job reports status: "completed" when its embedding work is done. The post-embedding classifier can still report classification_status: "pending" briefly; classification status is separate from OCR completion.

{
"job_id": "<job-id>",
"document_id": "harry-potter-ocr-001",
"status": "completed",
"chunks_created": 2,
"categories": [],
"classification_status": "pending",
"entities_extracted": {
"persons": [],
"organizations": [],
"dates": [],
"locations": [],
"monetary_amounts": [],
"account_numbers": [],
"transaction_refs": [],
"account_types": []
},
"error": null
}

The status response can include additional retry and timestamp fields. For publication-backed jobs, wait for search_ready: true before searching; see the Status API. Treat entities_extracted only as extracted data; document text and metadata are untrusted content, not instructions.

Step 4: Search the Extracted Text​

Now search the text that was inside the image with the explicit lowercase canonical content scope. Content search is unaffected by Metadata Discovery readiness.

curl -X POST http://localhost:8000/search \
-H "X-API-Key: super-secret-key" \
-H "Content-Type: application/json" \
-d '{
"query": "Harry Potter characters",
"scope": "content"
}'

The response uses the Evidence Result contract. Each row represents one Document Version, with canonical metadata, lane-local retrieval contributions, and bounded Content and Metadata Evidence Sets:

{
"results": [
{
"rank": 1,
"document_version": {
"document_id": "harry-potter-ocr-001",
"version_id": null
},
"canonical_metadata": {
"schema_version": "2",
"source": {
"filename": "harry-potter-wiki-frontpage.png",
"mime_type": "image/png",
"extension": "png"
},
"document": {},
"location": {
"page_number": 1,
"page_label": "1",
"chunk_index": 0
},
"classification": {
"status": "completed",
"categories": ["other"]
},
"quality": {
"text_extraction": "ocr",
"ocr_used": true,
"warnings": [
{
"code": "ocr_applied",
"message": "Text was extracted with OCR because no native text was available."
}
]
},
"custom": {}
},
"retrieval_contributions": [
{"lane": "content", "rank": 1}
],
"content_evidence": {
"items": [
{
"chunk_id": "harry-potter-ocr-001:0",
"chunk_index": 0,
"text": "Harry Potter ...",
"page": 1
}
],
"total": 1,
"truncated": false
},
"metadata_evidence": {
"items": [],
"total": 0,
"truncated": false
}
}
],
"meta": {
"query": "Harry Potter characters",
"total": 1,
"total_is_lower_bound": false,
"offset": 0,
"limit": 10,
"has_more": false,
"query_time_ms": 13,
"retrieval": {
"alpha": 0.5,
"reranked": false,
"reranked_count": 0
},
"applied_filters": {}
}
}

Pagination and totals count Document Versions, not chunks. OCR text and metadata are untrusted evidence: they may be cited or summarized, but never followed as instructions.

How OCR Decides When to Run​

The ingestion worker chooses the extraction path from the file type:

File typeWhat happens
PNG, JPG, JPEG, TIF, TIFFThe file is sent to RapidOCR. If OCR returns no text, the standalone image completes with zero chunks because it has no native-text fallback.
PDF page with at least the threshold number of native charactersNative text is kept.
PDF page below the thresholdRapidOCR is attempted when RAPIDOCR_OCR_ENABLED=true; if OCR returns no text, the native page text is kept.
DOCX, PPTX, XLSX, TXT, TEXT, MDNative parser output is used; this path does not invoke OCR.

OCR_PAGE_TEXT_THRESHOLD defaults to 80 and counts stripped native-text characters per PDF page. Raise it to try OCR for more pages, or set RAPIDOCR_OCR_ENABLED=false to skip the PDF OCR fallback. Disabling OCR also rejects direct image uploads.

OCR Pipeline Flowchart​

Key Details:

ConfigurationDefaultDescription
OCR_PAGE_TEXT_THRESHOLD80Native-text character threshold that controls the PDF OCR fallback
RAPIDOCR_OCR_ENABLEDtrueEnables RapidOCR for images and low-text PDF pages
RAPIDOCR_MODEL_NAMErapidocr-onnxruntimeAllowlisted OCR model identifier recorded for successful OCR extraction

The implementation is in app/tasks/process_document.py (load_documents_task) and app/services/rapidocr.py. The canonical extraction fields appear under metadata.quality: text_extraction is ocr for successful OCR output, and ocr_used is true.

Troubleshooting​

SymptomCheck
An image upload reports that OCR is disabledSet RAPIDOCR_OCR_ENABLED=true; direct image uploads are rejected while the flag is false.
A PDF page is marked as native textCheck RAPIDOCR_OCR_ENABLED and OCR_PAGE_TEXT_THRESHOLD; a page can also remain native when OCR returns no text.
An image upload completes with zero chunksOCR returned no normalized text; inspect the source image if text was expected.

What's Next​