PHI-finder
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_diffsreproduces 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_caseonly controls how the headers are handled. Burned-in pixel PHI is still redacted only when animage_redactoris 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
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 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
115a2c02b8bd4ca53ea4965c6e67be872a21e7e3781ba7ea903ff0f221fc0e42
|
|
| MD5 |
26330ec6cd86cc8d2cec699d90d25cdb
|
|
| BLAKE2b-256 |
ea3dd6bf3774f860c649c6bc8ed9ec253b5398ca4c971e4558a80c96fb69f350
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fe284a11f4de258785f947375d044cb2a83a0bf505d535766dc8e2f12d0b52cc
|
|
| MD5 |
9737917bd1827c07eebafc863198a796
|
|
| BLAKE2b-256 |
8e5a351336e1f1376a4941f54e83724d8de6c33c85157eeb7e3562d93d2bf65c
|