Skip to main content

OpenMed delivers state-of-the-art biomedical and clinical LLMs that rival proprietary enterprise stacks, unifying model discovery, advanced extractions, and one-line orchestration.

Project description

OpenMed — local-first healthcare AI

Local-first healthcare AI that never leaves the device

maziyarpanahi%2Fopenmed | Trendshift

Turn clinical text into structured insight with one line of code.
Entity extraction, PII de-identification, and 1,000+ specialized medical models that run entirely on your own hardware — from a one-liner in Python to a native Swift app on iPhone, powered by Apple MLX. No cloud. No vendor lock-in. No patient data leaving your network.

PyPI Python Models arXiv License Stars

Swift — OpenMedKit Apple Silicon — MLX Platforms Docs

1,000+ models  ·  15 languages  ·  247 PII checkpoints  ·  100% on-device  ·  Apache-2.0

English · 简体中文 · Español · Français · Deutsch · Italiano · Português · Nederlands · العربية · हिन्दी · తెలుగు · 日本語 · Türkçe · فارسی


See it in action

OpenMed runs entirely on the device — clinical text never leaves it. Here it is on iPhone, fully offline:

OpenMed Scan on iPhone — on-device PII de-identification and clinical extraction via OpenMedKit
On iPhone via OpenMedKit — scan a clinical note, de-identify it, and extract clinical signals, all locally with Apple MLX. Nothing is uploaded.

OpenMed redacting PII from a clinical discharge document in real time
Real-time PII de-identification — the Nemotron Privacy Filter redacting names, addresses, IDs, and billing data from a clinical discharge packet, entirely on-device. (All values shown are synthetic.)

30-second example

from openmed import analyze_text

result = analyze_text(
    "Patient started on imatinib for chronic myeloid leukemia.",
    model_name="disease_detection_superclinical",
)

for entity in result.entities:
    print(f"{entity.label:<12} {entity.text:<28} {entity.confidence:.2f}")
# DISEASE      chronic myeloid leukemia     0.98
# DRUG         imatinib                     0.95

A state-of-the-art clinical NER model running locally — no API key, no network call.


Why OpenMed?

OpenMed Cloud medical APIs
Runs on your device / servers
Patient data leaves your network Never Sent to the vendor
Cost Free & open-source Per-call pricing
Specialized medical models 1,000+ Limited
Languages 13+ Varies
Offline / air-gapped
Apple Silicon (MLX) acceleration n/a
Native iOS / macOS apps ✅ via OpenMedKit
Vendor lock-in None — Apache-2.0 Yes
  • Specialized models — 1,000+ curated biomedical & clinical models, many outperforming proprietary stacks.
  • HIPAA-aware de-identification — all 18 Safe Harbor identifiers, smart entity merging, format-preserving fakes.
  • Runs everywhere — CPU, CUDA, Apple Silicon (MLX), and natively in iOS/macOS apps via OpenMedKit.
  • One-line deployment — Python API, Dockerized REST service, or batch pipelines.
  • Zero lock-in — Apache-2.0, your infrastructure, your data.

On-device on Apple — Swift, MLX & iOS

OpenMed is built to run where your data already lives. On Apple hardware it accelerates with MLX, and it ships straight into iPhone, iPad, and Mac apps through OpenMedKit — so PII detection and clinical extraction happen fully offline, on the device.

// Add OpenMedKit to your app
dependencies: [
    .package(url: "https://github.com/maziyarpanahi/openmed.git", from: "1.7.0"),
]
  • MLX runtime for PII token classification, the Privacy Filter family, experimental GLiNER-family zero-shot tasks, and Python MLX-LM text generation with Laneformer; includes a CoreML fallback path for supported token-classification artifacts.
  • One model name, every platform — MLX model names automatically fall back to the matching PyTorch checkpoint on non-Apple hardware.
  • Python on Apple Silicon too: pip install "openmed[mlx]".

Guides: MLX backend · OpenMedKit (Swift) · CoreML export

MLX vs CPU latency on Apple Silicon — 24 to 33 times faster
MLX on Apple Silicon: 24–33× faster than CPU PyTorch for the Privacy Filter — median latency per inference step, lower is better.

How it works

flowchart LR
    A["Clinical text"] --> B["OpenMed<br/>(100% on-device)"]
    B --> C["Medical entities"]
    B --> D["PII detected"]
    B --> E["De-identified text"]
    style B fill:#0D6E6E,stroke:#0A5656,stroke-width:2px,color:#ffffff
    style C fill:#D6EBEB,stroke:#0D6E6E,color:#0E1116
    style D fill:#F7DCD8,stroke:#C5453A,color:#0E1116
    style E fill:#F5E27A,stroke:#A9A088,color:#0E1116

Quick start

# Core + Hugging Face runtime (Linux, macOS, Windows; CPU or CUDA)
pip install "openmed[hf]"

# Add the REST service
pip install "openmed[hf,service]"

# Apple Silicon acceleration (MLX)
pip install "openmed[mlx]"

Python API

from openmed import analyze_text

analyze_text(
  "Patient received 75mg "
  "clopidogrel for NSTEMI.",
  model_name=
  "pharma_detection_superclinical",
)

REST service

uvicorn openmed.service.app:app \
  --host 0.0.0.0 --port 8080

GET /health POST /analyze POST /pii/extract POST /pii/deidentify

Batch

from openmed import BatchProcessor

p = BatchProcessor(
  model_name=
  "disease_detection_superclinical",
  group_entities=True,
)
p.process_texts([...])

Offline / air-gapped? Point model_name (or model_id) at a local directory and OpenMed loads it without contacting the Hugging Face Hub:

from openmed import OpenMedConfig, analyze_text

result = analyze_text(
    "Patient presents with chronic myeloid leukemia and Type 2 diabetes.",
    model_id="./models/OpenMed-NER-DiseaseDetect-SuperClinical-434M",
    config=OpenMedConfig(device="cpu"),
)

Models

A curated registry of specialized medical NER models — browse the full catalog.

Model Specialization Entity types Size
disease_detection_superclinical Disease & conditions DISEASE, CONDITION, DIAGNOSIS 434M
pharma_detection_superclinical Drugs & medications DRUG, MEDICATION, TREATMENT 434M
pii_superclinical_large PII & de-identification NAME, DATE, SSN, PHONE, EMAIL, ADDRESS 434M
anatomy_detection_electramed Anatomy & body parts ANATOMY, ORGAN, BODY_PART 109M
gene_detection_genecorpus Genes & proteins GENE, PROTEIN 109M

Privacy: PII detection & de-identification

from openmed import extract_pii, deidentify

text = "Patient: John Doe, DOB: 01/15/1970, SSN: 123-45-6789"

# Extract PII with smart merging (prevents tokenization fragmentation)
result = extract_pii(text, model_name="pii_superclinical_large", use_smart_merging=True)

# De-identify with the method you need
deidentify(text, method="mask")     # [NAME], [DATE]
deidentify(text, method="replace")  # Faker-backed, locale-aware, format-preserving fakes
deidentify(text, method="hash")     # Cryptographic hashing
deidentify(text, method="shift_dates", date_shift_days=180)
  • Smart entity merging keeps 01/15/1970 whole instead of fragmenting it.
  • Faker-backed obfuscation with custom clinical-ID providers (CPF, CNPJ, BSN, NIR, Codice Fiscale, NIE, Aadhaar, Steuer-ID, NPI).
  • HIPAA: all 18 Safe Harbor identifiers, configurable confidence thresholds.
  • Batch PII (v1.5.5): extract or de-identify across many documents with BatchProcessor(operation="extract_pii" | "deidentify", batch_size=16).
Batch PII processing throughput — up to 3.3x on CPU and 2.2x on MLX
Batch processing — up to 3.3× higher throughput on CPU and 2.2× on MLX vs. one document at a time.

Complete PII notebook · Smart merging · Anonymization quickstart

Privacy Filter family — three model families on the OpenAI Privacy Filter architecture

Same model code (gpt-oss-style sparse-MoE transformer with local attention, sink tokens, RoPE+YaRN, tiktoken o200k_base), different training data. All route through the same extract_pii() / deidentify() API — only model_name= changes.

Variant PyTorch (CPU + CUDA) MLX (Apple Silicon) MLX 8-bit
OpenAI Privacy Filter openai/privacy-filter OpenMed/privacy-filter-mlx …-mlx-8bit
Nemotron-PII fine-tune OpenMed/privacy-filter-nemotron …-nemotron-mlx …-nemotron-mlx-8bit
OpenMed Multilingual OpenMed/privacy-filter-multilingual …-multilingual-mlx …-multilingual-mlx-8bit
from openmed import extract_pii

text = "Patient Sarah Connor (DOB: 03/15/1985) at MRN 4471882."

extract_pii(text, model_name="openai/privacy-filter")              # PyTorch baseline
extract_pii(text, model_name="OpenMed/privacy-filter-nemotron")    # same code, different weights
extract_pii(text, model_name="OpenMed/privacy-filter-mlx")         # Apple Silicon (MLX)

On non-Apple-Silicon hosts, MLX model names are automatically substituted with the matching PyTorch checkpoint (with a one-time warning) — ship one model name, run anywhere. See Privacy Filter architecture & backend routing.


Multilingual PII (15 languages)

Extraction and de-identification support 15 supported PII language codes: ar, de, en, es, fr, he, hi, id, it, ja, nl, pt, te, th, and tr247 PII checkpoints total.

python -c "from openmed import extract_pii; print([(e.label, e.text) for e in extract_pii('Dr. Pedro Almeida, CPF: 123.456.789-09, email: pedro@hospital.pt', lang='pt').entities])"
Show per-language examples (Portuguese, Dutch, Hindi, Arabic, Japanese, Turkish)
from openmed import extract_pii

portuguese = extract_pii("Paciente: Pedro Almeida, CPF: 123.456.789-09, telefone: +351 912 345 678", lang="pt", use_smart_merging=True)
dutch      = extract_pii("Patiënt: Eva de Vries, BSN: 123456782, telefoon: +31 6 12345678", lang="nl", use_smart_merging=True)
hindi      = extract_pii("रोगी: अनीता शर्मा, फोन: +91 9876543210, पता: नई दिल्ली 110001", lang="hi", use_smart_merging=True)
arabic     = extract_pii("المريضة ليلى حسن، الهاتف +20 10 1234 5678، الرقم القومي 29801011234567.", lang="ar", use_smart_merging=True)
japanese   = extract_pii("患者 佐藤 花子、電話 +81 90 1234 5678、マイナンバー 1234 5678 9012.", lang="ja", use_smart_merging=True)
turkish    = extract_pii("Hasta Ayşe Yılmaz, telefon +90 532 123 45 67, TCKN 10000000146.", lang="tr", use_smart_merging=True)

for r in (portuguese, dutch, hindi, arabic, japanese, turkish):
    print([(e.label, e.text) for e in r.entities])

REST API

A Docker-friendly FastAPI service with request validation, shared pipeline preload, and unified error envelopes.

pip install "openmed[hf,service]"
uvicorn openmed.service.app:app --host 0.0.0.0 --port 8080

# or with Docker
docker build -t openmed:1.7.0 .
docker run --rm -p 8080:8080 -e OPENMED_PROFILE=prod openmed:1.7.0
curl -X POST http://127.0.0.1:8080/pii/extract \
  -H "Content-Type: application/json" \
  -d '{"text":"Paciente: Maria Garcia, DNI: 12345678Z","lang":"es"}'

Model lifecycle (v1.5.5): free memory on demand with GET /models/loaded, POST /models/unload, and a keep_alive idle window:

OPENMED_SERVICE_KEEP_ALIVE=10m uvicorn openmed.service.app:app --host 0.0.0.0 --port 8080
curl -X POST http://127.0.0.1:8080/models/unload -H "Content-Type: application/json" -d '{"all":true}'

See the full REST service guide.


Documentation

Full guides at openmed.life/docs.

Getting Started Analyze Text Model Registry
FAQ Anonymization Batch Processing
Configuration Profiles REST Service MLX Backend
Release Streams Generative Model Policy Contributing
Security Policy Compliance Posture

Meet the mascot

OpenMed mascot

OpenMed's guardian is a fluffy Persian cat styled as a tiny Avicenna (Ibn Sina) — the great Persian physician whose Canon of Medicine was the world's standard medical text for some 600 years. He keeps watch over the open book of medical knowledge, in a palette built around Persian turquoise (fīrūza): a local-first guardian for your most private data.



Contributing

Contributions welcome — bug reports, feature requests, and PRs alike. Please read the Contributing guide and our Code of Conduct first.


Security

Found a vulnerability? OpenMed redacts PHI, so a redaction bypass or PHI/PII leak is a security issue — please report it privately, never as a public issue. See SECURITY.md for the responsible-disclosure policy and the private reporting form. Never include real patient data in a report.


Credits

OpenMed builds on excellent open-source work — particular thanks to OpenAI (the Privacy Filter architecture), NVIDIA (the Nemotron PII dataset), Hugging Face (transformers & the model ecosystem), Apple (MLX), and the Faker maintainers.

License

Released under the Apache-2.0 License. Third-party asset notices are recorded in NOTICE.

Citation

@misc{panahi2025openmedneropensourcedomainadapted,
      title={OpenMed NER: Open-Source, Domain-Adapted State-of-the-Art Transformers for Biomedical NER Across 12 Public Datasets},
      author={Maziyar Panahi},
      year={2025},
      eprint={2508.01630},
      archivePrefix={arXiv},
      primaryClass={cs.CL},
      url={https://arxiv.org/abs/2508.01630},
}

Star History

If OpenMed is useful to you, a star helps others discover it.

Star History Chart

Built by the OpenMed team

Website · Docs · X / Twitter · LinkedIn

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

openmed-1.7.0.tar.gz (761.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

openmed-1.7.0-py3-none-any.whl (926.6 kB view details)

Uploaded Python 3

File details

Details for the file openmed-1.7.0.tar.gz.

File metadata

  • Download URL: openmed-1.7.0.tar.gz
  • Upload date:
  • Size: 761.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: Hatch/1.17.0 {"ci":true,"cpu":"x86_64","distro":{"id":"noble","libc":{"lib":"glibc","version":"2.39"},"name":"Ubuntu","version":"24.04"},"implementation":{"name":"CPython","version":"3.11.15"},"installer":{"name":"hatch","version":"1.17.0"},"openssl_version":"OpenSSL 3.0.13 30 Jan 2024","python":"3.11.15","system":{"name":"Linux","release":"6.17.0-1018-azure"}} HTTPX2/2.5.0

File hashes

Hashes for openmed-1.7.0.tar.gz
Algorithm Hash digest
SHA256 50b26b55889273c2364b9ea5781622c122827557490fcf2ca6cd1c0f02202a97
MD5 1966ca0e496f037b722762bcc4885c51
BLAKE2b-256 cb138e36307d5e0ac8bda2b0be6521bc104b14fae03e73d38cff6c0a76d35a4a

See more details on using hashes here.

File details

Details for the file openmed-1.7.0-py3-none-any.whl.

File metadata

  • Download URL: openmed-1.7.0-py3-none-any.whl
  • Upload date:
  • Size: 926.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: Hatch/1.17.0 {"ci":true,"cpu":"x86_64","distro":{"id":"noble","libc":{"lib":"glibc","version":"2.39"},"name":"Ubuntu","version":"24.04"},"implementation":{"name":"CPython","version":"3.11.15"},"installer":{"name":"hatch","version":"1.17.0"},"openssl_version":"OpenSSL 3.0.13 30 Jan 2024","python":"3.11.15","system":{"name":"Linux","release":"6.17.0-1018-azure"}} HTTPX2/2.5.0

File hashes

Hashes for openmed-1.7.0-py3-none-any.whl
Algorithm Hash digest
SHA256 de96eb41c21931e09aaf6050f8e963a3e5db243a813153eaad6593d0718676b7
MD5 e4d55009d23fbb19d621b0189e6fca26
BLAKE2b-256 ad3598bb1e77dbff44779841df71789a2aafad37e52e70e6ad364e0914a49b6a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page