Skip to main content

PHI-finder

CI/CD Codecov

Local testing (docker required)

conda create -n phi-finder python==3.11
conda activate phi-finder
pip install -e .[dev,test] --no-cache-dir
pytest .

Building

python -m pip install --upgrade build

python -m build

pip install dist/phi_finder-0.1.18-py3-none-any.whl

Basic usage (headers only)

import pydicom as dicom
from phi_finder.dicom_tools import anonymise_dicom

path = "/path/to/some/dicom.dcm"
dcm = dicom.dcmread(path)
anonymised_dcm = anonymise_dicom.anonymise_image(dcm)
anonymised_dcm.save_as('/path/to/some/dicom_anon.dcm')

More advanced usage

import pydicom as dicom
from presidio_image_redactor import (
    DicomImageRedactorEngine, ImageAnalyzerEngine, ContrastSegmentedImageEnhancer)
from phi_finder.dicom_tools import anonymise_dicom

path = "/path/to/some/dicom.dcm"
dcm = dicom.dcmread(path)
score_threshold=.15
analyser = anonymise_dicom._build_presidio_analyser(score_threshold, "en_core_web_lg")
image_redactor = DicomImageRedactorEngine(
    image_analyzer_engine=ImageAnalyzerEngine(
        analyzer_engine=analyser, 
        image_preprocessor=ContrastSegmentedImageEnhancer(),
        ))
anonymised_dcm = anonymise_dicom.anonymise_image(dcm,score_threshold=score_threshold,
                                                 analyser=analyser,
                                                 image_redactor=image_redactor,
                                                 )
anonymised_dcm.save_as('/path/to/some/dicom_anon.dcm')

De-identifying a file and generating its HTML report

html_report builds a self-contained HTML report describing what was removed: the names of the scrubbed header fields, plus a before/after diff of every changed header value (e.g. a radiology report). The snapshot must be taken before anonymise_image, which mutates the dataset in place.

import pydicom as dicom
from phi_finder.dicom_tools import anonymise_dicom, html_report

path = "/path/to/some/dicom.dcm"
dcm = dicom.dcmread(path)

# Record the header values before they are redacted.
value_snapshot = html_report.snapshot_values(dcm)

gliner_pii = anonymise_dicom._build_transformer()
anonymised_dcm = anonymise_dicom.anonymise_image(dcm, gliner_pii=gliner_pii, use_case='dicom_retain_patient_scan_private', score_threshold=0.15)
anonymised_dcm.save_as('/path/to/some/dicom_anon.dcm')

report = html_report.build_html_report(
    html_report.read_flagged_headers(anonymised_dcm),
    n_images=1,
    session_id="my-session",
    use_case="dicom_retain_patient_scan_private",
    value_diffs=html_report.collect_value_diffs(value_snapshot, anonymised_dcm),
)
with open('/path/to/some/deidentification_report.html', 'w', encoding='utf-8') as f:
    f.write(report)

value_diffs is optional — omit it (along with snapshot_values / collect_value_diffs) to get a report that lists only the names of the scrubbed header fields.

Warning: a report built with value_diffs reproduces the original clinical-note text, since the struck-through spans are the PHI itself.

One report for a whole series (multiple images)

A DICOM series is many files. To describe the whole session in a single report, accumulate the findings across images and pass n_images as the total: extend one flagged_headers list per image, and de-duplicate the value diffs by html_report.diff_key(diff) so a field that is identical across every slice (e.g. a repeated note) is shown once, while genuinely per-slice values (e.g. a UID) are kept distinct.

import pydicom as dicom
from phi_finder.dicom_tools import anonymise_dicom, html_report

paths = ["/path/to/dicom0.dcm", "/path/to/dicom1.dcm"]

flagged_headers = []
value_diffs = {}  # keyed by diff_key so duplicates across slices collapse

for i, path in enumerate(paths):
    dcm = dicom.dcmread(path)
    value_snapshot = html_report.snapshot_values(dcm)  # before anonymise mutates
    anonymised_dcm = anonymise_dicom.anonymise_image(dcm)
    anonymised_dcm.save_as(f"/path/to/dicom{i}_anon.dcm")

    flagged_headers.extend(html_report.read_flagged_headers(anonymised_dcm))
    for diff in html_report.collect_value_diffs(value_snapshot, anonymised_dcm):
        value_diffs.setdefault(html_report.diff_key(diff), diff)

report = html_report.build_html_report(
    flagged_headers,
    n_images=len(paths),
    session_id="my-session",
    use_case="Standard",
    value_diffs=list(value_diffs.values()),
)
with open("/path/to/deidentification_report.html", "w", encoding="utf-8") as f:
    f.write(report)

De-identifying headers with the DICOM PS3.15 profile

The use_case argument selects how header values are de-identified:

use_case Standard headers Private headers Patient characteristics
"Standard" (default), "Aggressive", or any other value Presidio + GLiNER NER redaction Presidio + GLiNER NER redaction Sex kept; age → 000Y; birth date → year
"PS3.15" / "dicom_default" PS3.15 Basic Profile Removed Removed
"PS3.15_Rtn. Pat." / "dicom_retain_patient" PS3.15 Basic Profile Removed Kept (age, sex, size, weight, …)
"dicom_default_scan_private" PS3.15 Basic Profile NER-scrubbed (kept) Removed
"dicom_retain_patient_scan_private" PS3.15 Basic Profile NER-scrubbed (kept) Kept (age, sex, size, weight, …)

Matching is case-insensitive and separator-tolerant (see the note at the end of this section).

By default anonymise_image scans the header values with the Presidio NER pipeline (and GLiNER, when supplied). Passing use_case="PS3.15" (or its friendlier alias use_case="dicom_default") instead de-identifies the headers with the DICOM PS3.15 Annex E Basic Application Level Confidentiality Profile. In this mode the NER engines are not run on the headers.

import pydicom as dicom
from phi_finder.dicom_tools import anonymise_dicom

path = "/path/to/some/dicom.dcm"
dcm = dicom.dcmread(path)
anonymised_dcm = anonymise_dicom.anonymise_image(dcm, use_case="PS3.15")
anonymised_dcm.save_as('/path/to/some/dicom_anon.dcm')

Retain Patient Characteristics

Use use_case="PS3.15_Rtn. Pat." (or its friendlier alias use_case="dicom_retain_patient") to apply the basic profile together with the PS3.15 Retain Patient Characteristics Option. Direct identifiers (patient name, birth date, etc.) are still removed, but patient characteristics such as age, sex, size, weight, ethnic group and smoking status are kept.

import pydicom as dicom
from phi_finder.dicom_tools import anonymise_dicom

path = "/path/to/some/dicom.dcm"
dcm = dicom.dcmread(path)
anonymised_dcm = anonymise_dicom.anonymise_image(dcm, use_case="PS3.15_Rtn. Pat.")
anonymised_dcm.save_as('/path/to/some/dicom_anon.dcm')

Scanning private headers

Both DICOM modes have a _scan_private variant — use_case="dicom_default_scan_private" and use_case="dicom_retain_patient_scan_private". The standard headers are handled exactly as in the matching profile above, but instead of removing private attributes (the Basic Profile default), they are kept and their text values are scanned with the Presidio/GLiNER pipeline.

import pydicom as dicom
from phi_finder.dicom_tools import anonymise_dicom

path = "/path/to/some/dicom.dcm"
dcm = dicom.dcmread(path)
anonymised_dcm = anonymise_dicom.anonymise_image(dcm, use_case="dicom_default_scan_private")
anonymised_dcm.save_as('/path/to/some/dicom_anon.dcm')

The use_case match is case-insensitive and tolerant of separator spelling, so "PS3.15", "ps3.15", "PS3_15", "PS3-15" and the alias "dicom_default" all select the plain profile, and "PS3.15_Rtn. Pat.", "PS3.15 Retain Patient Characteristics" or the alias "dicom_retain_patient" select the retain variant. Appending _scan_private to either alias selects the private-header-scanning variant. Any other value (e.g. "Standard", or "NER Only") falls back to the Presidio/GLiNER pipeline.

Note: use_case only controls how the headers are handled. Burned-in pixel PHI is still redacted only when an image_redactor is passed, exactly as in the examples above.

Metadata

Release files for phi-finder 0.1.18

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

Source distribution (sdist)

Source distribution for phi-finder 0.1.18
File Size Uploaded Provenance Actions
phi_finder-0.1.18.tar.gz 81.9 kB Without provenance Details for phi_finder-0.1.18.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for phi-finder 0.1.18
File Interpreter ABI Platform Actions
phi_finder-0.1.18-py3-none-any.whl Python 3 none any Details for phi_finder-0.1.18-py3-none-any.whl

Total release size: 165.8 kB

Release files / phi_finder-0.1.18.tar.gz

Download URL phi_finder-0.1.18.tar.gz
Size 81.9 kB
Tags Source
SHA-256 checksum
How to use checksums
115a2c02b8bd4ca53ea4965c6e67be872a21e7e3781ba7ea903ff0f221fc0e42
BLAKE2b-256 checksum
How to use checksums
ea3dd6bf3774f860c649c6bc8ed9ec253b5398ca4c971e4558a80c96fb69f350
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.0

Release files / phi_finder-0.1.18-py3-none-any.whl

Download URL phi_finder-0.1.18-py3-none-any.whl
Size 83.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fe284a11f4de258785f947375d044cb2a83a0bf505d535766dc8e2f12d0b52cc
BLAKE2b-256 checksum
How to use checksums
8e5a351336e1f1376a4941f54e83724d8de6c33c85157eeb7e3562d93d2bf65c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.0

Security for phi-finder 0.1.18

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

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.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.18 This release

2 release files No provenance

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