Turn scanned faxes, images, and CSV/CDA exports into clean text ready for OpenMed de-identification and NER, fully on-device. Use when the user has clinical documents (image scans, photographed/faxed notes, tabular CSV/TSV exports, C-CDA XML) and needs OCR or structured intake before openmed.deidentify and openmed.analyze_text, asks about openmed.multimodal, OCR engines (Tesseract / PaddleOCR), tabular redaction, or layout and reading order. Covers the verified ocr() and redact_document() entry points and the ExtractedDocument contract. Pairs before deidentifying-clinical-text and extracting-clinical-entities.
72
89%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Clinical text often arrives as scanned faxes, photographed notes, CSV exports, or
C-CDA XML — not plain text. openmed.multimodal converts these into a normalized
ExtractedDocument (clean text + character-offset → source-location spans) so you
can run de-identification and NER. It runs on-device: OCR backends are local,
no document leaves the machine.
openmed.deidentify and
openmed.analyze_text.This is the first stage. After intake, hand off to
deidentifying-clinical-text then extracting-clinical-entities.
redact_document dispatches by file extension. Live handlers:
| Input | Extensions | Path |
|---|---|---|
| Images / scans | .png .jpg .jpeg .tif .tiff .bmp .gif .webp | OCR (ocr() / image handler) |
| Tables | .csv .tsv | column-aware tabular redaction |
| C-CDA | .xml (detected as CDA) | stdlib CDA adapter |
PDF and DOCX have no live handler yet — redact_document("x.pdf") raises
UnsupportedDocumentError. Convert PDFs to page images first (or to text with your
own tool) and feed the images through OCR. See
references/multimodal-ingest.md for the full
contract, engines, and the tabular pipeline.
pip install "openmed[multimodal]" # document intake contract + image deps
pip install "openmed[ocr-paddle]" # add the PaddleOCR engine
# Tesseract engine also needs the system binary, e.g.: brew install tesseractThe clean two-step intake path. ocr() lives in the submodule (it is intentionally
not re-exported from openmed.multimodal):
from openmed.multimodal.ocr import ocr
import openmed
# 1) OCR a scanned/faxed note -> OcrResult -> ExtractedDocument -> plain text
result = ocr("fax_page.png", engine=None) # None = auto-select an installed engine
doc = result.to_document() # ExtractedDocument
text = doc.text # clean text for downstream OpenMed
# 2) De-identify, then run NER (privacy-first order)
deid = openmed.deidentify(text, method="mask", policy="hipaa_safe_harbor")
ner = openmed.analyze_text(deid.deidentified_text, output_format="dict")
for ent in ner.entities:
print(ent.label, ent.text, ent.confidence)engine may be None (auto-select), "tesseract", "paddleocr", or an
OcrEngine instance. OcrResult exposes .text and per-word boxes via .words
(each OcrWord has text, bbox, confidence, page).
redact_documentFor images, CSV/TSV, and CDA, redact_document performs intake and
de-identification in a single, format-aware call, returning an already-redacted
ExtractedDocument:
from openmed.multimodal import redact_document
# Image scan: OCR + redact in one call
doc = redact_document("fax_page.png")
print(doc.text) # redacted text
print(doc.spans[:3]) # SourceSpan offsets -> page / bbox in the original scan
# CSV export: per-column classification (direct id / quasi-id / safe) + redaction
table_doc = redact_document("patients.csv")
print(table_doc.text)Use redact_document when you want OpenMed to own intake and redaction
(especially for tables, where redaction is column-scoped, not free-text NER). Use
the ocr() → to_document() → deidentify path when you want to control the
de-identification method, policy, or mapping yourself.
CSV columns get classified before any cell is touched, so a free-text NER pass is not run blindly over structured data:
from openmed.multimodal import read_table, redact_table
view = read_table("patients.csv") # TableView with column decisions
for col in view.columns:
print(col.name, "->", col.assigned_class, col.action, col.canonical_label)
redacted = redact_table("patients.csv", keep_year=True)
print(redacted.text) # redacted CSV
for entry in redacted.manifest: # PHI-SAFE audit: counts/actions per column, no raw values
print(entry)redact_table(...) returns a RedactedTable with .text, .headers, .rows,
.columns, and a PHI-safe .manifest (no raw cell values). See
references/multimodal-ingest.md for column
classes and actions.
Every ExtractedDocument keeps character offset → source location. After detecting
PHI on doc.text, project a span's offset back to its page and bounding box:
from openmed.multimodal.ocr import ocr
import openmed
doc = ocr("fax_page.png").to_document()
deid = openmed.deidentify(doc.text, method="mask")
for ent in deid.pii_entities:
loc = doc.location_at(ent.start) # SourceSpan or None
if loc is not None:
print(ent.label, "page", loc.page, "bbox", loc.bbox)This lets you redact pixels on the original scan, not just the extracted text.
deidentifying-clinical-text: pass doc.text to openmed.deidentify(...)
with a policy profile; this is the required next stage for PHI.extracting-clinical-entities: run openmed.analyze_text on the
redacted text, not raw OCR output.ocr() is imported from the submodule: from openmed.multimodal.ocr import ocr. It is deliberately not re-exported from openmed.multimodal.redact_document raises UnsupportedDocumentError
for them. Rasterize to images first.[ocr-paddle] for PaddleOCR, or the system
Tesseract binary for pytesseract. Missing backends raise
MissingDependencyError with an install hint.OcrWord.confidence to flag low-quality pages.redact_table redacts per column classification —
don't run whole-table NER and expect structured columns to be handled correctly.manifest and any logs record
counts/actions/labels, never raw values. Keep OCR intermediates on-device and out
of logs.80da98c
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.