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.

Download files

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

Source Distribution

phi_finder-0.1.18.tar.gz (81.9 kB view details)

Uploaded Source

Built Distribution

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

phi_finder-0.1.18-py3-none-any.whl (83.9 kB view details)

Uploaded Python 3

File details

Details for the file phi_finder-0.1.18.tar.gz.

File metadata

  • Download URL: phi_finder-0.1.18.tar.gz
  • Upload date:
  • Size: 81.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.11.0

File hashes

Hashes for phi_finder-0.1.18.tar.gz
Algorithm Hash digest
SHA256 115a2c02b8bd4ca53ea4965c6e67be872a21e7e3781ba7ea903ff0f221fc0e42
MD5 26330ec6cd86cc8d2cec699d90d25cdb
BLAKE2b-256 ea3dd6bf3774f860c649c6bc8ed9ec253b5398ca4c971e4558a80c96fb69f350

See more details on using hashes here.

File details

Details for the file phi_finder-0.1.18-py3-none-any.whl.

File metadata

  • Download URL: phi_finder-0.1.18-py3-none-any.whl
  • Upload date:
  • Size: 83.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.11.0

File hashes

Hashes for phi_finder-0.1.18-py3-none-any.whl
Algorithm Hash digest
SHA256 fe284a11f4de258785f947375d044cb2a83a0bf505d535766dc8e2f12d0b52cc
MD5 9737917bd1827c07eebafc863198a796
BLAKE2b-256 8e5a351336e1f1376a4941f54e83724d8de6c33c85157eeb7e3562d93d2bf65c

See more details on using hashes here.

Release history Release notifications | RSS feed

2025.8.1

2 files

2025.7.2

2 files

2025.7.1

2 files

2025.7.0

2 files

2025.6.0

2 files

2025.5.3

2 files

2025.5.2

2 files

2025.5.1

2 files

2025.5.0

2 files

This release

0.1.18 This release

2 files

0.1.17

2 files

0.1.16

2 files

0.1.15

2 files

0.1.14

2 files

0.1.13

2 files

0.1.12

2 files

0.1.11

2 files

0.1.10

2 files

0.1.9

2 files

0.1.8

2 files

0.1.7

2 files

0.1.6

2 files

0.1.5

2 files

0.1.4

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

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