Skip to main content

Outis

CI License: Apache-2.0 Python 3.11+

Local PHI detection, typed redaction and patient-consistent synthetic replacement for English clinical text.

Outis is Greek for "nobody": the name Odysseus gave the Cyclops.

The detector is a fully fine-tuned DeBERTa-v3-base token classifier with a constrained BIOES decoder. There is no rule layer: every detected span comes from the model. Everything runs on your machine; no text is sent to a hosted service. It ships as a Python library, a CLI and a FastAPI server.

Features

  • 14 PHI types: NAME, DATE, AGE, ADDRESS, LOCATION, FACILITY, PHONE, EMAIL, URL, IP, MRN, SSN, ACCOUNT and ID.
  • Redaction or replacement: typed markers or masks, or fictional values that stay consistent per patient. Dates shift by a stable per-patient offset that preserves intervals.
  • Model-only detection: one fine-tuned encoder, decoded to exact original character offsets.
  • Configurable policies: per-type actions, custom identifier rules, linked messages, explicit restoration, and JSON, CSV, XML and plain-text file input.
  • Local by design: loopback-only by default, optional API key, no request logging, Cache-Control: no-store on responses.

Quickstart

Requires Python 3.11+.

pip install outis

The first run downloads the model (736 MB) from Hugging Face into the Hugging Face cache, and later runs load it from there. Each release pins the model revision it was tested with. Only the model is downloaded; your text never leaves the machine. Once the model is cached, HF_HUB_OFFLINE=1 forbids network access. To use a checkpoint directory instead, pass it as Deidentifier(model_dir), --model-dir or OUTIS_MODEL_DIR.

Add the pdf extra (pip install 'outis[pdf]') for PDF file input.

To work from source, use uv and prefix the commands below with uv run:

git clone https://github.com/ewhauser/outis.git
cd outis
uv sync

A checkout uses models/deid-deberta-v3-base when its weight file is present, and otherwise downloads the model as above. To keep the weights in the checkout:

uv run hf download ewhauser/outis-deberta-v3-base hf/model.safetensors --local-dir models/deid-deberta-v3-base

Python

from outis import Deidentifier

deid = Deidentifier(secret=b"application-secret-at-least-32-bytes")

result = deid.deidentify("Mara Calder was seen on 2024-02-28.", mode="redact")
print(result["text"])

synthetic = deid.deidentify(
    "Mara Calder was seen on 2024-02-28.",
    mode="synthetic",
    patient_id="internal-patient-42",
)
print(synthetic["text"])

Each result contains the transformed text and, for every entity, its type, original character offsets, replacement and score. It also lists any warnings and the model version.

HTTP API

outis serve --port 8000
curl http://127.0.0.1:8000/v1/deidentify \
  -H 'Content-Type: application/json' \
  -d '{"text":"Patient Mara Calder, DOB 1974-06-13. Creatinine 1.2 mg/dL.","mode":"redact"}'

Interactive docs are served at http://127.0.0.1:8000/docs, and the OpenAPI schema at /openapi.json. Synthetic mode needs a replacement secret in OUTIS_SECRET; in a checkout, uv run scripts/serve_local.py --port 8000 creates and reuses one in .cache/local-api-secret. For other deployments, see deployment.

CLI

outis text --input note.txt --mode redact

How it works

text ─▶ DeBERTa-v3-base token classifier (overlapping 512-token windows)
          └─▶ constrained BIOES decoder ─▶ spans at original offsets ─▶ redact / replace

The default model was fine-tuned on about 116,000 synthetic notes from 46 banks written by this project's generators and independent synthetic authors. No real clinical data was used for training. See the model card.

Results

All results come from synthetic text. None of them measures accuracy on real clinical notes.

Public benchmarks

Every model is scored the same way, under this project's annotation policy (details). ↑ means higher is better, ↓ lower is better; the best result in each row is in bold.

Benchmark Measure Outis MedDeID GLiNER-PII*
ASQ-PHI
1,051 search queries
PHI characters masked ↑ 99.94% 99.66% 98.57%
Non-PHI text removed ↓ 0.18% 0.91% 2.43%
Notes fully masked ↑ 99.52% 97.60% 94.47%
MedDeID English
300 notes
PHI characters masked ↑ 99.27% 99.19% 94.87%
Non-PHI text removed ↓ 0.01% 0.15% 0.64%
Notes fully masked ↑ 95.00% 93.00% 69.67%
Technetium-I
5,000-note sample
PHI characters masked ↑ 99.65% 99.65% 98.83%
Non-PHI text removed ↓ 0.57% 1.14% 0.78%
Notes fully masked ↑ 84.60% 84.60% 43.32%

* nvidia/gliner-PII with one added zero-shot label for hospitals.

We have looked at Outis's failures on ASQ-PHI and MedDeID English while developing it, so those two are not fully independent tests; Technetium-I has not been inspected. Per-identifier results are in the full comparison.

Held-out synthetic notes

On 2,400 synthetic notes from an author whose writing was never used in training:

Measure Result
Direct identifiers masked (names, record numbers, phone numbers, emails, addresses and similar) 99.64%
Names masked (95% lower bound) 99.79% (99.71%)
Indirect identifiers masked (dates, ages over 89, places, facilities) 98.97%
Detected spans that are real PHI 99.63%
Exact type and boundary match (F1) 97.34%
Notes with every identifier masked* 86.25%

* Counts ages over 89, the ages HIPAA's Safe Harbor method lists as identifiers; ages of 89 and under stay in the output by default. The model detects every age, so setting the request policy's ages option to all removes younger ages too (see the API guide). Counting every age, 81.90% of notes are fully masked.

On these same notes MedDeID masked 86.08% of direct identifiers and GLiNER-PII 79.09%. The notes follow this project's annotation conventions, which this model was trained on, so that gap overstates its advantage; the public benchmarks above are the fair comparison.

A separate test of real place names (2,352 notes) masked 98.81% of city and care-site name characters, but tagged a bare state or country in 4% of notes that contained no PHI.

How these numbers are computed is described in evaluation methodology.

Limitations

  • Not yet measured on real notes. Held-out synthetic authors are the best available evidence; real institutional notes may differ substantially.
  • Synthetic evaluation only. Training and internal test text comes from this project's own generators. Real institutional formats, rare identifying narratives and unseen identity styles are not measured.
  • Scope. English plain text only. OCR, scanned images, audio and multilingual text are out of scope.
  • Some options return PHI. Restoration mappings, sensitive entity analysis and per-type keep deliberately return original values. Treat those responses as PHI.
  • Scores are not calibrated. Model scores are not safety probabilities.
  • Replacement is not anonymity. Fictional values can resemble real people, and date shifting changes seasonal context. Surrogates do not guarantee resistance to re-identification.

Use human review and an appropriate privacy determination before releasing any output.

Documentation

Guide Contents
Evaluation methodology Targets, any-label masking and how models are compared
API guide Endpoints, policies, masks, structured data, files, WebSockets
Synthetic replacement Secrets, patient anchors, date shifting, identity linking
Deployment Launcher, default model, batch limits, security controls
Model card Architecture, training data, evaluation, risks
Training Training candidates and generating corpora
External verification Public benchmark protocol and results
Annotation policy Span conventions for every entity type
Places addendum Real localities, care-site abbreviations and public references

Contributing

See CONTRIBUTING.md. Report security issues privately, as described in SECURITY.md. Never put real PHI in an issue, pull request or test fixture.

Citation

If you use this project, please cite it using CITATION.cff.

License

Source code and the fine-tuned model are licensed under Apache-2.0. Generated synthetic corpus text and annotations are dedicated to the public domain under CC0-1.0. The DeBERTa-v3-base base model is MIT-licensed; see NOTICE.

Metadata

Release files for outis 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for outis 0.2.1
File Size Uploaded Provenance
outis-0.2.1.tar.gz 540.0 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for outis 0.2.1
File Interpreter ABI Platform
outis-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 942.7 kB

Release files / outis-0.2.1.tar.gz

Download URL outis-0.2.1.tar.gz
Size 540.0 kB
Tags Source
SHA-256 checksum
How to use checksums
a68d48f9a17cffbaa8d408a9e941f7838107210089400eaf48d8cb10008763dc
BLAKE2b-256 checksum
How to use checksums
5190f0b2aba3467706829b048eb8bfd9e58a164d7bc5902921c63d9d6337560f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release files / outis-0.2.1-py3-none-any.whl

Download URL outis-0.2.1-py3-none-any.whl
Size 402.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8753ad1cfe8a4b65feb4508050ca0de88bc93c0016fa2ef075200761dcc6b950
BLAKE2b-256 checksum
How to use checksums
0f3cfcf0d855d8eb48469d296965b7eb1f89eadcc953549eb7c36259dd28b534
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Security for outis 0.2.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for outis-0.2.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page