Metadata-Version: 2.4
Name: phi-finder
Version: 0.1.15
Summary: Collection of tools to check uploaded scans and records for identifiable data.
Project-URL: repository, https://github.com/Australian-Imaging-Service/phi-finder
Author-email: Pedro Faustini <pedro.faustini@mq.edu.au>
Maintainer-email: Pedro Faustini <pedro.faustini@mq.edu.au>
Keywords: anonymisation,clinical data,dicom
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3.10
Classifier: Topic :: Scientific/Engineering
Requires-Python: <3.13,>=3.11
Requires-Dist: faker>=36.1.1
Requires-Dist: fileformats-medimage-extras
Requires-Dist: gliner==0.2.26
Requires-Dist: numpy==1.26.4
Requires-Dist: opencv-python>=4.10.0.84
Requires-Dist: pandas>=2.2.3
Requires-Dist: pdfminer-six>=20240706
Requires-Dist: pikepdf>=9.4.0
Requires-Dist: presidio-analyzer>=2.2.355
Requires-Dist: presidio-anonymizer>=2.2.355
Requires-Dist: presidio-image-redactor>=0.0.53
Requires-Dist: presidio-structured>=0.0.4a0
Requires-Dist: pymupdf>=1.24.13
Requires-Dist: spacy
Requires-Dist: torch==2.6.0
Requires-Dist: transformers>=4.48.2
Requires-Dist: typing-extensions>=4.6.3; python_version < '4.0'
Provides-Extra: dev
Requires-Dist: black; extra == 'dev'
Requires-Dist: codespell; extra == 'dev'
Requires-Dist: fileformats[test]; extra == 'dev'
Requires-Dist: flake8; extra == 'dev'
Requires-Dist: flake8-pyproject; extra == 'dev'
Requires-Dist: mypy>=1.5.1; extra == 'dev'
Requires-Dist: pre-commit; extra == 'dev'
Requires-Dist: pydata-sphinx-theme>=0.13; extra == 'dev'
Requires-Dist: pydicom; extra == 'dev'
Provides-Extra: docs
Requires-Dist: docutils>=0.10; extra == 'docs'
Requires-Dist: furo>=2022.2.14.1; extra == 'docs'
Requires-Dist: mock>1.0; extra == 'docs'
Requires-Dist: numpydoc>=0.6.0; extra == 'docs'
Requires-Dist: packaging; extra == 'docs'
Requires-Dist: sphinx-argparse>=0.2.0; extra == 'docs'
Requires-Dist: sphinx-click>=3.1; extra == 'docs'
Requires-Dist: sphinx>=2.1.2; extra == 'docs'
Provides-Extra: test
Requires-Dist: fileformats-extras; extra == 'test'
Requires-Dist: fileformats-medimage-extras; extra == 'test'
Requires-Dist: frametree-xnat>=0.6.10; extra == 'test'
Requires-Dist: frametree>=0.15.2; extra == 'test'
Requires-Dist: medimages4tests; extra == 'test'
Requires-Dist: mypy>=1.5.1; extra == 'test'
Requires-Dist: openpyxl; extra == 'test'
Requires-Dist: pydra2app-xnat>=0.8.3; extra == 'test'
Requires-Dist: pydra2app>=0.20.0; extra == 'test'
Requires-Dist: pydra==1.0a3; extra == 'test'
Requires-Dist: pytest-cov>=2.12.1; extra == 'test'
Requires-Dist: pytest-env>=0.6.2; extra == 'test'
Requires-Dist: pytest>=6.2.5; extra == 'test'
Requires-Dist: thinc>=8.3.4; extra == 'test'
Requires-Dist: xnat4tests; extra == 'test'
Requires-Dist: xnat>=0.7.2; extra == 'test'
Description-Content-Type: text/markdown

# PHI-finder

[![CI/CD](https://github.com/australian-imaging-service/phi-finder/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/australian-imaging-service/phi-finder/actions/workflows/ci-cd.yml)
[![Codecov](https://codecov.io/gh/australian-imaging-service/phi-finder/branch/main/graph/badge.svg?token=UIS0OGPST7)](https://codecov.io/gh/australian-imaging-service/phi-finder)


## Local testing (docker required)

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

## Building

```bash
python -m pip install --upgrade build

python -m build

pip install dist/phi_finder-0.1.14-py3-none-any.whl
```

## Basic usage (headers only)


```python
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

```python
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 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](https://dicom.nema.org/medical/dicom/current/output/chtml/part15/chapter_E.html). In this mode the NER engines are **not** run on the headers.

```python
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.

```python
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.

```python
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"`, the default,
or `"Aggressive"`) falls back to the Presidio/GLiNER pipeline described above.

> **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.
