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.
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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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.