AIS-deid
AIS-deid is a package that will evolve to include deidentification of multiple imaging file types. Current version: 0.1.0 Data types covered: DICOM only.
Note the CLI command is different from the PyPI package name:
Package installs as
ais-deid, but exposes its command-line tool asdicom-deid(the DICOM-specific tool within this package — more domains may be added under theais-deidname in future).
| Use | Name |
|---|---|
| PyPI package (what you pip-install) | ais-deid |
| Import package (what is imported in python) | ais-deid |
| CLI command (what to write in the terminal | dicom-deid |
dicom-deid
De-identify DICOM metadata using pydicom/deid.
Features
- Deterministic pseudonymisation — same patient maps to same anonymous ID across runs
- Full DICOM hierarchy preservation (patient → study → series → image)
- Private tag and sequence stripping
- Configurable date jittering
- Post-process validation command to audit output for residual PHI
- Click-based CLI with environment variable support
- Kubernetes-ready Docker image
Quickstart
1. Install
pip install -e ".[dev]"
2. Set required environment variables
# Generate a secure salt (do this once per site; store it in a secrets manager)
export DEID_SALT=$(python -c "import secrets; print(secrets.token_hex(32))")
# Optional: shift dates by N days (default 0)
export DEID_DATE_JITTER=-14
Security:
DEID_SALTmust never be hardcoded in source or committed to git. Use a secrets manager (AWS Secrets Manager, HashiCorp Vault, Kubernetes Secrets).
3. Run
dicom-deid process \
--input /data/raw_dicoms \
--output /data/deid_dicoms \
--recipe recipe.dicom
4. Validate output
dicom-deid validate --input /data/deid_dicoms
CLI Reference
dicom-deid [--verbose] [--version] COMMAND [OPTIONS]
Commands:
process De-identify all DICOM files under --input.
validate Audit de-identified output for residual PHI tags.
Process options:
| Option | Description |
|---|---|
--input -i PATH |
Input directory (required) |
--output -o PATH |
Output directory (required) |
--recipe -r PATH |
deid recipe file [env: DEID_RECIPE] [default: recipe.dicom] |
--date-jitter INT |
Days to shift dates [env: DEID_DATE_JITTER] |
--glob TEXT |
File glob pattern [default: **/*.dcm] |
--no-remove-private |
Do not strip private tags (not recommended) |
--no-strip-sequences |
Do not strip sequence tags (not recommended) |
Recipe customisation
Edit recipe.dicom to match your site's requirements. The deid recipe language supports:
| Action | Meaning |
|---|---|
ADD |
Add a tag with a fixed value |
REPLACE |
Replace a tag value (supports var: / func:) |
BLANK |
Set tag value to empty string |
REMOVE |
Delete the tag entirely |
JITTER |
Shift a date tag by N days |
KEEP |
Explicitly keep a tag unchanged |
Field expanders:
contains:Name— all tags whose keyword contains "Name"startswith:Patient— all tags starting with "Patient"endswith:Date— all tags ending with "Date"
Custom Python replacement functions are registered in src/ais_deid/dicom/transforms.py and referenced in the recipe with func:ais_deid.dicom.transforms.function_name.
Development
Setup
pip install -e ".[dev]"
pre-commit install
Run tests
export DEID_SALT="test_salt_local_dev_only"
pytest
Run linting manually
pre-commit run --all-files
Docker
# Build
docker build -t ais-deid:latest .
# Run
docker run --rm \
-e DEID_SALT="$DEID_SALT" \
-v /data/raw:/input:ro \
-v /data/deid:/output \
-v $(pwd)/recipe.dicom:/config/recipe.dicom:ro \
ais-deid:latest \
process --input /input --output /output --recipe /config/recipe.dicom
Kubernetes
# Create the salt secret
kubectl create secret generic dicom-deid-secrets \
--from-literal=deid-salt="$DEID_SALT"
# Load the recipe as a ConfigMap
kubectl create configmap dicom-deid-recipe \
--from-file=recipe.dicom=recipe.dicom
# Submit the job
kubectl apply -f kubernetes/job.yaml
# Watch progress
kubectl logs -f job/dicom-deid
Security notes
DEID_SALTis a site secret. Anyone with the salt can reverse pseudonymisation for known patient IDs. Treat it like a password.- The default recipe follows DICOM PS 3.15 Annex E (Basic Application Level Confidentiality Profile) but does not constitute a legal guarantee of de-identification. Validate with your institution's ethics/IRB process.
- Private tags (
remove_private=True) and nested sequences (strip_sequences=True) are stripped by default. Only disable these if you have a specific reason.
File overview
ais-deid/
├── src/
│ └── ais_deid/
│ ├── __init__.py
│ └── dicom/
│ ├── __init__.py
│ ├── cli.py
│ ├── engine.py
│ ├── header_reid.py
│ └── transforms.py
├── tests/
│ └── dicom/
│ ├── __init__.py
│ ├── conftest.py
│ ├── test_cli.py
│ ├── test_engine.py
│ ├── test_header_reid.py
│ └── test_transforms.py
├── Kubernetes/
│ └── job.yaml
├── Dockerfile
├── LICENSE
├── README.md
├── pyproject.toml
└── recipe.dicom
**ais-deid: Repo root**
pyproject.toml— The project's packaging, dependencies, and tooling configuration. Declares the package name (ais-deid), version, and Python requirement (≥3.10). Lists runtime dependencies (deid,pydicom,click,rich) and dev dependencies (pytest,pytest-cov,flake8,pre-commit). Registers thedicom-deidshell command as an entry point pointing atais_deid.dicom.cli:main, so it's available system-wide afterpip install. Also configures pytest's test paths and coverage settings.recipe.dicom— The de-identification rule file consumed by thedeidlibrary. Written in deid's plain-text recipe language, it defines an action for every category of PHI tag: patient identity (REPLACE/BLANK/REMOVE), dates (JITTER), times (BLANK), physician/operator/institution names (REMOVE), device identifiers (REMOVE), request/order fields (REMOVE), protocol descriptions (BLANK), secondary UIDs (REMOVE), sequences (REMOVE), and free-text comment fields (BLANK). Usesvar:placeholders for values computed at runtime (e.g.var:anon_patient_id), injected by the engine. Field expanders likeendswith:Dateandcontains:PhysicianNameapply rules to whole groups of tags with a single line. This is the primary file to edit when adjusting what gets removed or changed.Dockerfile— A multi-stage Docker build. Stage 1 (builder) installshatchand builds a wheel fromsrc/. Stage 2 (runtime) is a minimalpython:3.11-slimimage that installs only the pre-built wheel — no build tools in the final image. Creates a non-rootdeiduser for security, mounts/inputand/outputas working directories, and setsENTRYPOINT ["dicom-deid"]so the container is used directly as a CLI tool. Secrets (DEID_SALT) are deliberately not baked in and must be injected at runtime..flake8— Linting configuration. Sets max line length to 100, suppresses two common false-positive rules (E203, W503), excludes build/cache directories, and allows longer lines in test files where fixture data is verbose..pre-commit-config.yaml— Defines Git pre-commit hooks that run automatically before every commit. Includestrailing-whitespace,end-of-file-fixer,check-yaml,check-merge-conflict, anddebug-statementsfrom the pre-commit standard library;flake8withflake8-bugbearfor linting; andcodespellfor spell-checking..codespell-ignorewords— A suppression list for the codespell hook. Contains medical/DICOM terms that spell-checkers incorrectly flag as misspellings (dicom, deid, anonymise, anonymisation, etc.).
src/ais_deid/
__init__.py— Top-level package initialiser for theais_deidnamespace (created to allow future non-DICOM subpackages to sit alongsidedicom/).
src/ais_deid/dicom/
__init__.py— Package initialiser for the DICOM de-identification module. Declares__version__ = "0.1.0", imported by the CLI for--versionoutput and bypyproject.tomlas the authoritative version string.transforms.py— Pure functions that compute replacement values for DICOM tags. Manages theDEID_SALTsecret: reads it from the environment at import time and exposes_require_salt(), which raises a clearRuntimeErrorif it's missing._hash()performs salted SHA-256 pseudonymisation (24 hex chars, 96 bits — deterministic, so the same patient always gets the same anonymous ID).hash_patient_id()hashesPatientID.hash_accession_number()hashesAccessionNumberwith a field-name prefix so the same raw value produces a different hash for different tag types — preventing cross-linkage. Also providespassthrough()(returns value unchanged) andblank_if_present()(returnsNoneto blank a tag while keeping it present). All functions follow the deidfunc:signature(item, value, field, dicom) -> str | None.engine.py— The core orchestration class. Defines two result dataclasses:FileResult(records success/failure and error message for one file) andRunResult(aggregates all results, exposes.successes,.failures, and.summary()). TheDEFAULT_VARIABLE_BUILDERSdict maps eachvar:name in the recipe to a callable that derives the value from the DICOM dataset — this is how hashed IDs and the date jitter get passed to the recipe.DeidEngine.__init__()loads the recipe, merges any caller-supplied variable builders over the defaults, and eagerly validates thatDEID_SALTis available so misconfiguration is caught immediately.process_file()implements the deid API (construct parser with recipe, define vars, parse, save) with full try/except so one bad file never aborts a batch.process_directory()usesrglobwithrelative_to()to preserve the full patient/study/series directory hierarchy in the output.header_reid.py— Builds and writes a re-identification linkage document alongside the de-identified output, recording which tags were modified/removed/blanked/added and preserving the linkage UIDs needed to trace a de-identified file back to its source under controlled conditions.cli.py— The user-facing command-line interface, built with Click. The rootmaingroup provides--verbose(toggles DEBUG logging) and--version. Two subcommands:processruns de-identification — accepts--input,--output,--recipe(also viaDEID_RECIPEenv var),--date-jitter(also viaDEID_DATE_JITTER),--glob,--no-remove-private, and--no-strip-sequences; after running it prints a rich colour-coded summary table per file and exits with code 1 if any failures occurred.validateaudits a de-identified output directory — checks that high-risk tags (InstitutionName,ReferringPhysicianName,OperatorsName, etc.) are absent or empty, thatPatientNamedoesn't look like a real name, and thatPatientIdentityRemoved=YESis set; prints a failure table and exits with code 1 if issues are found.
tests/dicom/
Mirrors the source layout above — test_cli.py, test_engine.py, test_header_reid.py, test_transforms.py, and conftest.py (shared fixtures, including synthetic DICOM file generation via _make_dicom()).
License
Apache 2.0
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ais_deid-0.1.2.tar.gz.
File metadata
- Download URL: ais_deid-0.1.2.tar.gz
- Upload date:
- Size: 34.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8d7168308660cc46dbfa4d0b12d133ee06836640e47c1ddf15fdd46c57e7d6df
|
|
| MD5 |
b913d4b32d22805d811fae05249c4954
|
|
| BLAKE2b-256 |
bc0604eff4a9872ea1bab367fa88b1077df7b9e6eb00909664b2ba1e9e1dda38
|
File details
Details for the file ais_deid-0.1.2-py3-none-any.whl.
File metadata
- Download URL: ais_deid-0.1.2-py3-none-any.whl
- Upload date:
- Size: 25.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
87e3d87e69c9fe94c309121ad9e02e5a924f03fbe290fe6a9ec8ebd0f554e8a3
|
|
| MD5 |
935e103ab4bd6fe1245e122f4c2f22e1
|
|
| BLAKE2b-256 |
16aec29fd95cced39e97b0fb8c54e2aff7abc0bc06de3beddc1f46d8d4366b54
|