Skip to main content

Outis

CI License: Apache-2.0 Python 3.13+

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, or opt-in across a whole project release. 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.
  • Site dictionaries (opt-in): load a site's own facility names, departments and identifier formats once at startup and match them in a single pass. They are site-supplied, and their effect on accuracy has not been measured.
  • Local by design: loopback-only by default, optional API key, no request logging, Cache-Control: no-store on responses.

Quickstart

Requires Python 3.13+.

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.

Docker

Images with the model built in are published to the GitHub Container Registry for CPU (linux/amd64, linux/arm64) and NVIDIA GPUs (linux/amd64). They run without network access.

docker run --rm -p 8000:8000 -e OUTIS_API_KEY=change-me ghcr.io/ewhauser/outis:cpu
docker run --rm --gpus all -p 8000:8000 -e OUTIS_API_KEY=change-me ghcr.io/ewhauser/outis:gpu

Requests then send the key in an X-API-Key header. See deployment for tags and settings.

CLI

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

outis batch de-identifies a whole note table, JSONL or Parquet (with the outis[parquet] extra), in one process. Every row needs a unique note ID; synthetic mode also needs a patient ID column and OUTIS_SECRET. Output is written as it goes. With --resume, the same command starts the run or, after an interruption, continues it from the last completed batch:

outis batch --input notes.parquet --output deid.parquet \
  --id-column note_id --text-column note_text \
  --patient-column patient_id --mode synthetic --resume

See batch processing for the output format, resume guarantee, out-of-memory handling and measured throughput.

Detect, review, then replace

Detection and replacement can run as separate steps, so a person can check the spans before any text is released. outis batch --detect-only writes a span file (offsets and labels, no text). outis review writes a local HTML report of original and replaced text where spans can be removed or added; it contains PHI and is written owner-only. outis batch --spans, or POST /v1/replace, then applies the reviewed spans without loading the model, and can re-apply them later under another key, project or style. Replacing /v1/detect spans gives the same output as /v1/deidentify. See supplied spans.

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, supplied spans and span files, structured data, files, WebSockets
Synthetic replacement Secrets, patient anchors, date shifting, identity linking, project scope
Deployment Launcher, default model, corpus batch runs and throughput, 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.4.0

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.4.0
File Size Uploaded Provenance
outis-0.4.0.tar.gz 661.0 kB With provenance Details

Built distribution (wheel)

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

Total release size: 1.1 MB

Release files / outis-0.4.0.tar.gz

Download URL outis-0.4.0.tar.gz
Size 661.0 kB
Tags Source
SHA-256 checksum
How to use checksums
d7a14cc3a2b722b7ef83898b1f7209dbe2bafa6dcb3509b5e1d12245a58b66e8
BLAKE2b-256 checksum
How to use checksums
2798cfcc2193849d505b93b15fe82f15fcc54324798908d0c7c2393e21ff2f16
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 13, 2026.

Transparency log

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

Download URL outis-0.4.0-py3-none-any.whl
Size 465.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e965e636b11d6a1f6c327b559083d817b70ee6d3180e6d8775bf5aa41858eb1a
BLAKE2b-256 checksum
How to use checksums
e1973efbef875da288c01bbccd62a25d55e3f57c6c2d416581ab233c6b01cea1
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 13, 2026.

Transparency log

Security for outis 0.4.0

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.4.0.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 13, 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