mirror of
https://github.com/data-privacy-stack/presidio.git
synced 2026-07-23 11:20:55 -05:00
Consolidating general bounding box operations (#1011)
This commit is contained in:
@@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file.
|
||||
* Added evaluation code for the DICOM image redaction capabilities
|
||||
* Modified `ImagePiiVerifyEngine` to allow passing of kwargs
|
||||
* Updated all image redactor engines and OCR classes to allow passing of an OCR confidence threshold and other OCR parameters
|
||||
* Moved general bounding box operations to new class `BboxProcessor`
|
||||
|
||||
#### General
|
||||
* Updated documentation to include instructions on using DICOM evaluation code
|
||||
|
||||
@@ -75,6 +75,9 @@ The `DicomImagePiiVerifyEngine` class can be used to assist in ground truth labe
|
||||
import pydicom
|
||||
from presidio_image_redactor import DicomImagePiiVerifyEngine
|
||||
|
||||
# Initialize engine
|
||||
dicom_engine = DicomImagePiiVerifyEngine()
|
||||
|
||||
# Choose your file to create ground truth for
|
||||
filename = "path/to/your/file.dcm"
|
||||
instance = pydicom.dcmread(filename)
|
||||
@@ -84,8 +87,8 @@ padding_width = 25
|
||||
verification_image, ocr_results, analyzer_results = dicom_engine.verify_dicom_instance(instance, padding_width)
|
||||
|
||||
# Format results for more direct comparison
|
||||
ocr_results_formatted = dicom_engine._get_bboxes_from_ocr_results(ocr_results)
|
||||
analyzer_results_formatted = dicom_engine._get_bboxes_from_analyzer_results(analyzer_results)
|
||||
ocr_results_formatted = dicom_engine.bbox_processor.get_bboxes_from_ocr_results(ocr_results)
|
||||
analyzer_results_formatted = dicom_engine.bbox_processor.get_bboxes_from_analyzer_results(analyzer_results)
|
||||
```
|
||||
|
||||
By looking at the output of `verify_dicom_instance`, we can create a ground truth labels json.
|
||||
|
||||
@@ -75,6 +75,9 @@ The `DicomImagePiiVerifyEngine` class can be used to assist in ground truth labe
|
||||
import pydicom
|
||||
from presidio_image_redactor import DicomImagePiiVerifyEngine
|
||||
|
||||
# Initialize engine
|
||||
dicom_engine = DicomImagePiiVerifyEngine()
|
||||
|
||||
# Choose your file to create ground truth for
|
||||
filename = "path/to/your/file.dcm"
|
||||
instance = pydicom.dcmread(filename)
|
||||
@@ -84,8 +87,8 @@ padding_width = 25
|
||||
verification_image, ocr_results, analyzer_results = dicom_engine.verify_dicom_instance(instance, padding_width)
|
||||
|
||||
# Format results for more direct comparison
|
||||
ocr_results_formatted = dicom_engine._get_bboxes_from_ocr_results(ocr_results)
|
||||
analyzer_results_formatted = dicom_engine._get_bboxes_from_analyzer_results(analyzer_results)
|
||||
ocr_results_formatted = dicom_engine.bbox_processor.get_bboxes_from_ocr_results(ocr_results)
|
||||
analyzer_results_formatted = dicom_engine.bbox_processor.get_bboxes_from_analyzer_results(analyzer_results)
|
||||
```
|
||||
|
||||
By looking at the output of `verify_dicom_instance`, we can create a ground truth labels json.
|
||||
|
||||
@@ -3,6 +3,7 @@ import logging
|
||||
|
||||
from .ocr import OCR
|
||||
from .tesseract_ocr import TesseractOCR
|
||||
from .bbox import BboxProcessor
|
||||
from .image_analyzer_engine import ImageAnalyzerEngine
|
||||
from .image_redactor_engine import ImageRedactorEngine
|
||||
from .image_pii_verify_engine import ImagePiiVerifyEngine
|
||||
@@ -15,6 +16,7 @@ logging.getLogger("presidio-image-redactor").addHandler(logging.NullHandler())
|
||||
__all__ = [
|
||||
"OCR",
|
||||
"TesseractOCR",
|
||||
"BboxProcessor",
|
||||
"ImageAnalyzerEngine",
|
||||
"ImageRedactorEngine",
|
||||
"ImagePiiVerifyEngine",
|
||||
|
||||
133
presidio-image-redactor/presidio_image_redactor/bbox.py
Normal file
133
presidio-image-redactor/presidio_image_redactor/bbox.py
Normal file
@@ -0,0 +1,133 @@
|
||||
from presidio_image_redactor.entities import ImageRecognizerResult
|
||||
from typing import List, Tuple, Dict, Union
|
||||
|
||||
|
||||
class BboxProcessor:
|
||||
"""Common module for general bounding box operators."""
|
||||
|
||||
def get_bboxes_from_ocr_results(
|
||||
self,
|
||||
ocr_results: Dict[str, List[Union[int, str]]],
|
||||
) -> List[Dict[str, Union[int, float, str]]]:
|
||||
"""Get bounding boxes on padded image for all detected words from ocr_results.
|
||||
|
||||
:param ocr_results: Raw results from OCR.
|
||||
:return: Bounding box information per word.
|
||||
"""
|
||||
bboxes = []
|
||||
for i in range(len(ocr_results["text"])):
|
||||
detected_text = ocr_results["text"][i]
|
||||
if detected_text:
|
||||
bbox = {
|
||||
"left": ocr_results["left"][i],
|
||||
"top": ocr_results["top"][i],
|
||||
"width": ocr_results["width"][i],
|
||||
"height": ocr_results["height"][i],
|
||||
"conf": float(ocr_results["conf"][i]),
|
||||
"label": detected_text,
|
||||
}
|
||||
bboxes.append(bbox)
|
||||
|
||||
return bboxes
|
||||
|
||||
def get_bboxes_from_analyzer_results(
|
||||
self,
|
||||
analyzer_results: List[ImageRecognizerResult],
|
||||
) -> List[Dict[str, Union[str, float, int]]]:
|
||||
"""Organize bounding box info from analyzer results.
|
||||
|
||||
:param analyzer_results: Results from using ImageAnalyzerEngine.
|
||||
|
||||
:return: Bounding box info organized.
|
||||
"""
|
||||
bboxes = []
|
||||
for i in range(len(analyzer_results)):
|
||||
result = analyzer_results[i].to_dict()
|
||||
|
||||
bbox_item = {
|
||||
"entity_type": result["entity_type"],
|
||||
"score": result["score"],
|
||||
"left": result["left"],
|
||||
"top": result["top"],
|
||||
"width": result["width"],
|
||||
"height": result["height"],
|
||||
}
|
||||
bboxes.append(bbox_item)
|
||||
|
||||
return bboxes
|
||||
|
||||
def remove_bbox_padding(
|
||||
self,
|
||||
analyzer_bboxes: List[Dict[str, Union[str, float, int]]],
|
||||
padding_width: int,
|
||||
) -> List[Dict[str, int]]:
|
||||
"""Remove added padding in bounding box coordinates.
|
||||
|
||||
:param analyzer_bboxes: The bounding boxes from analyzer results.
|
||||
:param padding_width: Pixel width used for padding (0 if no padding).
|
||||
|
||||
:return: Bounding box information per word.
|
||||
"""
|
||||
if padding_width < 0:
|
||||
raise ValueError("Padding width must be a non-negative integer.")
|
||||
|
||||
# Remove padding from all bounding boxes
|
||||
bboxes = [
|
||||
{
|
||||
"top": max(0, bbox["top"] - padding_width),
|
||||
"left": max(0, bbox["left"] - padding_width),
|
||||
"width": bbox["width"],
|
||||
"height": bbox["height"],
|
||||
}
|
||||
for bbox in analyzer_bboxes
|
||||
]
|
||||
|
||||
return bboxes
|
||||
|
||||
def match_with_source(
|
||||
self,
|
||||
all_pos: List[Dict[str, Union[str, int, float]]],
|
||||
pii_source_dict: List[Dict[str, Union[str, int, float]]],
|
||||
detected_pii: Dict[str, Union[str, float, int]],
|
||||
tolerance: int = 50,
|
||||
) -> Tuple[List[Dict[str, Union[str, int, float]]], bool]:
|
||||
"""Match returned redacted PII bbox data with some source of truth for PII.
|
||||
|
||||
:param all_pos: Dictionary storing all positives.
|
||||
:param pii_source_dict: List of PII labels for this instance.
|
||||
:param detected_pii: Detected PII (single entity from analyzer_results).
|
||||
:param tolerance: Tolerance for exact coordinates and size data.
|
||||
:return: List of all positive with PII mapped back as possible
|
||||
and whether a match was found.
|
||||
"""
|
||||
all_pos_match = all_pos.copy()
|
||||
|
||||
# Get info from detected PII (positive)
|
||||
results_left = detected_pii["left"]
|
||||
results_top = detected_pii["top"]
|
||||
results_width = detected_pii["width"]
|
||||
results_height = detected_pii["height"]
|
||||
results_score = detected_pii["score"]
|
||||
match_found = False
|
||||
|
||||
# See what in the ground truth this positive matches
|
||||
for label in pii_source_dict:
|
||||
source_left = label["left"]
|
||||
source_top = label["top"]
|
||||
source_width = label["width"]
|
||||
source_height = label["height"]
|
||||
|
||||
match_left = abs(source_left - results_left) <= tolerance
|
||||
match_top = abs(source_top - results_top) <= tolerance
|
||||
match_width = abs(source_width - results_width) <= tolerance
|
||||
match_height = abs(source_height - results_height) <= tolerance
|
||||
matching = [match_left, match_top, match_width, match_height]
|
||||
|
||||
if False not in matching:
|
||||
# If match is found, carry over ground truth info
|
||||
positive = label
|
||||
positive["score"] = results_score
|
||||
all_pos_match.append(positive)
|
||||
match_found = True
|
||||
|
||||
return all_pos_match, match_found
|
||||
@@ -10,7 +10,7 @@ from presidio_image_redactor import (
|
||||
ImageAnalyzerEngine,
|
||||
DicomImageRedactorEngine,
|
||||
)
|
||||
from presidio_image_redactor.image_pii_verify_engine import ImagePiiVerifyEngine
|
||||
from presidio_image_redactor import ImagePiiVerifyEngine, BboxProcessor
|
||||
from presidio_analyzer import PatternRecognizer
|
||||
|
||||
from typing import Tuple, List, Optional
|
||||
@@ -41,6 +41,9 @@ class DicomImagePiiVerifyEngine(ImagePiiVerifyEngine, DicomImageRedactorEngine):
|
||||
else:
|
||||
self.image_analyzer_engine = image_analyzer_engine
|
||||
|
||||
# Initialize bbox processor
|
||||
self.bbox_processor = BboxProcessor()
|
||||
|
||||
def verify_dicom_instance(
|
||||
self,
|
||||
instance: pydicom.dataset.FileDataset,
|
||||
@@ -130,8 +133,12 @@ class DicomImagePiiVerifyEngine(ImagePiiVerifyEngine, DicomImageRedactorEngine):
|
||||
ocr_kwargs=ocr_kwargs,
|
||||
**text_analyzer_kwargs,
|
||||
)
|
||||
formatted_ocr_results = self._get_bboxes_from_ocr_results(ocr_results)
|
||||
detected_phi = self._get_bboxes_from_analyzer_results(analyzer_results)
|
||||
formatted_ocr_results = self.bbox_processor.get_bboxes_from_ocr_results(
|
||||
ocr_results
|
||||
)
|
||||
detected_phi = self.bbox_processor.get_bboxes_from_analyzer_results(
|
||||
analyzer_results
|
||||
)
|
||||
|
||||
# Remove duplicate entities in results
|
||||
detected_phi = self._remove_duplicate_entities(detected_phi)
|
||||
@@ -154,31 +161,6 @@ class DicomImagePiiVerifyEngine(ImagePiiVerifyEngine, DicomImageRedactorEngine):
|
||||
|
||||
return verify_image, eval_results
|
||||
|
||||
@staticmethod
|
||||
def _get_bboxes_from_ocr_results(ocr_results: dict) -> List[dict]:
|
||||
"""Get bounding boxes on padded image for all detected words from ocr_results.
|
||||
|
||||
:param ocr_results: Raw results from OCR.
|
||||
:return: Bounding box information per word.
|
||||
"""
|
||||
bboxes = []
|
||||
item_count = 0
|
||||
for i in range(len(ocr_results["text"])):
|
||||
detected_text = ocr_results["text"][i]
|
||||
if detected_text:
|
||||
bbox = {
|
||||
"left": ocr_results["left"][i],
|
||||
"top": ocr_results["top"][i],
|
||||
"width": ocr_results["width"][i],
|
||||
"height": ocr_results["height"][i],
|
||||
"conf": float(ocr_results["conf"][i]),
|
||||
"label": detected_text,
|
||||
}
|
||||
bboxes.append(bbox)
|
||||
item_count += 1
|
||||
|
||||
return bboxes
|
||||
|
||||
@staticmethod
|
||||
def _remove_duplicate_entities(
|
||||
results: List[dict], dup_pix_tolerance: int = 5
|
||||
@@ -216,55 +198,8 @@ class DicomImagePiiVerifyEngine(ImagePiiVerifyEngine, DicomImageRedactorEngine):
|
||||
|
||||
return results_no_dups
|
||||
|
||||
@staticmethod
|
||||
def _match_with_source(
|
||||
all_pos: List[dict],
|
||||
phi_source_dict: List[dict],
|
||||
detected_phi: dict,
|
||||
tolerance: int = 50,
|
||||
) -> Tuple[dict, bool]:
|
||||
"""Match returned redacted PHI bbox data with some source of truth for PHI.
|
||||
|
||||
:param all_pos: Dictionary storing all positives.
|
||||
:param phi_source_dict: List of PHI labels for this instance.
|
||||
:param detected_phi: Detected PHI (single entity from analyzer_results).
|
||||
:param tolerance: Tolerance for exact coordinates and size data.
|
||||
:return: List of all positive with PHI mapped back as possible
|
||||
and whether a match was found.
|
||||
"""
|
||||
# Get info from detected PHI (positive)
|
||||
results_left = detected_phi["left"]
|
||||
results_top = detected_phi["top"]
|
||||
results_width = detected_phi["width"]
|
||||
results_height = detected_phi["height"]
|
||||
results_score = detected_phi["score"]
|
||||
match_found = False
|
||||
|
||||
# See what in the ground truth this positive matches
|
||||
for label in phi_source_dict:
|
||||
source_left = label["left"]
|
||||
source_top = label["top"]
|
||||
source_width = label["width"]
|
||||
source_height = label["height"]
|
||||
|
||||
match_left = abs(source_left - results_left) <= tolerance
|
||||
match_top = abs(source_top - results_top) <= tolerance
|
||||
match_width = abs(source_width - results_width) <= tolerance
|
||||
match_height = abs(source_height - results_height) <= tolerance
|
||||
matching = [match_left, match_top, match_width, match_height]
|
||||
|
||||
if False not in matching:
|
||||
# If match is found, carry over ground truth info
|
||||
positive = label
|
||||
positive["score"] = results_score
|
||||
all_pos.append(positive)
|
||||
match_found = True
|
||||
|
||||
return all_pos, match_found
|
||||
|
||||
@classmethod
|
||||
def _label_all_positives(
|
||||
cls,
|
||||
self,
|
||||
gt_labels_dict: dict,
|
||||
ocr_results: List[dict],
|
||||
detected_phi: List[dict],
|
||||
@@ -289,18 +224,18 @@ class DicomImagePiiVerifyEngine(ImagePiiVerifyEngine, DicomImageRedactorEngine):
|
||||
for analyzer_result in detected_phi:
|
||||
|
||||
# See if there are any ground truth matches
|
||||
all_pos, gt_match_found = cls._match_with_source(
|
||||
all_pos, gt_match_found = self.bbox_processor.match_with_source(
|
||||
all_pos, gt_labels_dict, analyzer_result, tolerance
|
||||
)
|
||||
|
||||
# If not, check back with OCR
|
||||
if not gt_match_found:
|
||||
all_pos, _ = cls._match_with_source(
|
||||
all_pos, _ = self.bbox_processor.match_with_source(
|
||||
all_pos, ocr_results, analyzer_result, tolerance
|
||||
)
|
||||
|
||||
# Remove any duplicates
|
||||
all_pos = cls._remove_duplicate_entities(all_pos, 0)
|
||||
all_pos = self._remove_duplicate_entities(all_pos, 0)
|
||||
|
||||
return all_pos
|
||||
|
||||
|
||||
@@ -75,7 +75,12 @@ class DicomImageRedactorEngine(ImageRedactorEngine):
|
||||
)
|
||||
|
||||
# Redact all bounding boxes from DICOM file
|
||||
bboxes = self._format_bboxes(analyzer_results, padding_width)
|
||||
analyzer_bboxes = self.bbox_processor.get_bboxes_from_analyzer_results(
|
||||
analyzer_results
|
||||
)
|
||||
bboxes = self.bbox_processor.remove_bbox_padding(
|
||||
analyzer_bboxes, padding_width
|
||||
)
|
||||
redacted_image = self._add_redact_box(instance, bboxes, crop_ratio, fill)
|
||||
|
||||
return redacted_image
|
||||
@@ -634,58 +639,6 @@ class DicomImageRedactorEngine(ImageRedactorEngine):
|
||||
|
||||
return phi_str_list
|
||||
|
||||
@staticmethod
|
||||
def _get_bboxes_from_analyzer_results(analyzer_results: list) -> List[dict]:
|
||||
"""Organize bounding box info from analyzer results.
|
||||
|
||||
:param analyzer_results: Results from using ImageAnalyzerEngine.
|
||||
|
||||
:return: Bounding box info organized.
|
||||
"""
|
||||
bboxes = []
|
||||
for i in range(len(analyzer_results)):
|
||||
result = analyzer_results[i].to_dict()
|
||||
|
||||
bbox_item = {
|
||||
"entity_type": result["entity_type"],
|
||||
"score": result["score"],
|
||||
"left": result["left"],
|
||||
"top": result["top"],
|
||||
"width": result["width"],
|
||||
"height": result["height"],
|
||||
}
|
||||
bboxes.append(bbox_item)
|
||||
|
||||
return bboxes
|
||||
|
||||
@classmethod
|
||||
def _format_bboxes(cls, analyzer_results: list, padding_width: int) -> List[dict]:
|
||||
"""Format the bounding boxes to write directly back to DICOM pixel data.
|
||||
|
||||
:param analyzer_results: The analyzer results.
|
||||
:param padding_width: Pixel width used for padding (0 if no padding).
|
||||
|
||||
:return: Bounding box information per word.
|
||||
"""
|
||||
if padding_width < 0:
|
||||
raise ValueError("Padding width must be a positive number.")
|
||||
|
||||
# Write bounding box info to json files for now
|
||||
bboxes = cls._get_bboxes_from_analyzer_results(analyzer_results)
|
||||
|
||||
# remove padding from all bounding boxes
|
||||
bboxes = [
|
||||
{
|
||||
"top": max(0, bbox["top"] - padding_width),
|
||||
"left": max(0, bbox["left"] - padding_width),
|
||||
"width": bbox["width"],
|
||||
"height": bbox["height"],
|
||||
}
|
||||
for bbox in bboxes
|
||||
]
|
||||
|
||||
return bboxes
|
||||
|
||||
@classmethod
|
||||
def _set_bbox_color(
|
||||
cls, instance: pydicom.dataset.FileDataset, fill: str
|
||||
@@ -829,7 +782,12 @@ class DicomImageRedactorEngine(ImageRedactorEngine):
|
||||
)
|
||||
|
||||
# Redact all bounding boxes from DICOM file
|
||||
bboxes = self._format_bboxes(analyzer_results, padding_width)
|
||||
analyzer_bboxes = self.bbox_processor.get_bboxes_from_analyzer_results(
|
||||
analyzer_results
|
||||
)
|
||||
bboxes = self.bbox_processor.remove_bbox_padding(
|
||||
analyzer_bboxes, padding_width
|
||||
)
|
||||
redacted_dicom_instance = self._add_redact_box(
|
||||
instance, bboxes, crop_ratio, fill
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ from typing import Union, Tuple, Optional
|
||||
|
||||
from PIL import Image, ImageDraw, ImageChops
|
||||
|
||||
from presidio_image_redactor import ImageAnalyzerEngine
|
||||
from presidio_image_redactor import ImageAnalyzerEngine, BboxProcessor
|
||||
|
||||
|
||||
class ImageRedactorEngine:
|
||||
@@ -17,6 +17,8 @@ class ImageRedactorEngine:
|
||||
else:
|
||||
self.image_analyzer_engine = image_analyzer_engine
|
||||
|
||||
self.bbox_processor = BboxProcessor()
|
||||
|
||||
def redact(
|
||||
self,
|
||||
image: Image,
|
||||
|
||||
@@ -7,9 +7,7 @@ the original parent ImagePiiVerifyEngine class.
|
||||
import PIL
|
||||
import pydicom
|
||||
|
||||
from presidio_image_redactor.dicom_image_pii_verify_engine import (
|
||||
DicomImagePiiVerifyEngine,
|
||||
)
|
||||
from presidio_image_redactor import DicomImagePiiVerifyEngine, BboxProcessor
|
||||
import pytest
|
||||
|
||||
PADDING_WIDTH = 25
|
||||
@@ -39,6 +37,7 @@ def test_verify_correctly(
|
||||
get_mock_dicom_verify_results (dict): Dictionary with loaded results.
|
||||
"""
|
||||
# Assign
|
||||
bbox_processor = BboxProcessor()
|
||||
expected_ocr_results = get_mock_dicom_verify_results["ocr_results_formatted"]
|
||||
expected_analyzer_results = get_mock_dicom_verify_results["analyzer_results"]
|
||||
expected_ocr_results_labels = []
|
||||
@@ -51,10 +50,10 @@ def test_verify_correctly(
|
||||
test_ocr_results,
|
||||
test_analyzer_results,
|
||||
) = mock_engine.verify_dicom_instance(get_mock_dicom_instance, PADDING_WIDTH)
|
||||
test_ocr_results_formatted = mock_engine._get_bboxes_from_ocr_results(
|
||||
test_ocr_results_formatted = bbox_processor.get_bboxes_from_ocr_results(
|
||||
test_ocr_results
|
||||
)
|
||||
test_analyzer_results_formatted = mock_engine._get_bboxes_from_analyzer_results(
|
||||
test_analyzer_results_formatted = bbox_processor.get_bboxes_from_analyzer_results(
|
||||
test_analyzer_results
|
||||
)
|
||||
|
||||
|
||||
428
presidio-image-redactor/tests/test_bbox.py
Normal file
428
presidio-image-redactor/tests/test_bbox.py
Normal file
@@ -0,0 +1,428 @@
|
||||
"""Test suite for bbox.py"""
|
||||
from presidio_image_redactor import BboxProcessor
|
||||
from presidio_image_redactor.entities.image_recognizer_result import (
|
||||
ImageRecognizerResult,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from typing import List
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def mock_bbox_processor():
|
||||
"""Instance of BboxProcessor"""
|
||||
bbox_proc = BboxProcessor()
|
||||
|
||||
return bbox_proc
|
||||
|
||||
|
||||
# ------------------------------------------------------
|
||||
# BboxProcessor.get_bboxes_from_ocr_results()
|
||||
# ------------------------------------------------------
|
||||
@pytest.mark.parametrize(
|
||||
"ocr_results_raw, expected_results",
|
||||
[
|
||||
(
|
||||
{
|
||||
"left": [123],
|
||||
"top": [0],
|
||||
"width": [100],
|
||||
"height": [25],
|
||||
"conf": ["1"],
|
||||
"text": ["JOHN"],
|
||||
},
|
||||
[
|
||||
{
|
||||
"left": 123,
|
||||
"top": 0,
|
||||
"width": 100,
|
||||
"height": 25,
|
||||
"conf": 1,
|
||||
"label": "JOHN",
|
||||
}
|
||||
],
|
||||
),
|
||||
(
|
||||
{
|
||||
"left": [123, 345],
|
||||
"top": [0, 15],
|
||||
"width": [100, 75],
|
||||
"height": [25, 30],
|
||||
"conf": ["1", "0.87"],
|
||||
"text": ["JOHN", "DOE"],
|
||||
},
|
||||
[
|
||||
{
|
||||
"left": 123,
|
||||
"top": 0,
|
||||
"width": 100,
|
||||
"height": 25,
|
||||
"conf": 1,
|
||||
"label": "JOHN",
|
||||
},
|
||||
{
|
||||
"left": 345,
|
||||
"top": 15,
|
||||
"width": 75,
|
||||
"height": 30,
|
||||
"conf": 0.87,
|
||||
"label": "DOE",
|
||||
},
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_get_bboxes_from_ocr_results_happy_path(
|
||||
mock_bbox_processor: BboxProcessor,
|
||||
ocr_results_raw: dict,
|
||||
expected_results: list,
|
||||
):
|
||||
"""Test happy path for BboxProcessor.get_bboxes_from_ocr_results
|
||||
Args:
|
||||
mock_bbox_processor (BboxProcessor): Instantiated engine.
|
||||
ocr_results_raw (dict): Raw OCR results.
|
||||
expected_results (list): Formatted OCR results.
|
||||
"""
|
||||
# Act
|
||||
test_bboxes = mock_bbox_processor.get_bboxes_from_ocr_results(ocr_results_raw)
|
||||
|
||||
# Assert
|
||||
assert test_bboxes == expected_results
|
||||
|
||||
|
||||
# ------------------------------------------------------
|
||||
# BboxProcessor.get_bboxes_from_analyzer_results()
|
||||
# ------------------------------------------------------
|
||||
@pytest.mark.parametrize(
|
||||
"analyzer_results, expected_bboxes",
|
||||
[
|
||||
(
|
||||
[
|
||||
ImageRecognizerResult(
|
||||
entity_type="TYPE_1",
|
||||
start=0,
|
||||
end=0,
|
||||
left=25,
|
||||
top=25,
|
||||
width=100,
|
||||
height=100,
|
||||
score=0.99,
|
||||
),
|
||||
ImageRecognizerResult(
|
||||
entity_type="TYPE_2",
|
||||
start=10,
|
||||
end=10,
|
||||
left=25,
|
||||
top=49,
|
||||
width=75,
|
||||
height=51,
|
||||
score=0.7,
|
||||
),
|
||||
ImageRecognizerResult(
|
||||
entity_type="TYPE_3",
|
||||
start=25,
|
||||
end=35,
|
||||
left=613,
|
||||
top=26,
|
||||
width=226,
|
||||
height=35,
|
||||
score=0.6,
|
||||
),
|
||||
],
|
||||
[
|
||||
{
|
||||
"entity_type": "TYPE_1",
|
||||
"score": 0.99,
|
||||
"left": 25,
|
||||
"top": 25,
|
||||
"width": 100,
|
||||
"height": 100,
|
||||
},
|
||||
{
|
||||
"entity_type": "TYPE_2",
|
||||
"score": 0.7,
|
||||
"left": 25,
|
||||
"top": 49,
|
||||
"width": 75,
|
||||
"height": 51,
|
||||
},
|
||||
{
|
||||
"entity_type": "TYPE_3",
|
||||
"score": 0.6,
|
||||
"left": 613,
|
||||
"top": 26,
|
||||
"width": 226,
|
||||
"height": 35,
|
||||
},
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_get_bboxes_from_analyzer_results_happy_path(
|
||||
mock_bbox_processor: BboxProcessor,
|
||||
analyzer_results: list,
|
||||
expected_bboxes: list,
|
||||
):
|
||||
"""Test happy path for BboxProcessor.get_bboxes_from_analyzer_results
|
||||
|
||||
Args:
|
||||
analyzer_results (list): Results from using ImageAnalyzerEngine.
|
||||
expected_bboxes (list): Expected output bounding box list.
|
||||
"""
|
||||
# Arrange
|
||||
|
||||
# Act
|
||||
test_bboxes = mock_bbox_processor.get_bboxes_from_analyzer_results(analyzer_results)
|
||||
|
||||
# Assert
|
||||
assert test_bboxes == expected_bboxes
|
||||
|
||||
|
||||
# ------------------------------------------------------
|
||||
# BboxProcessor.remove_bbox_padding()
|
||||
# ------------------------------------------------------
|
||||
@pytest.mark.parametrize(
|
||||
"mock_intermediate_bbox, padding_width, expected_bboxes",
|
||||
[
|
||||
(
|
||||
[
|
||||
{
|
||||
"entity_type": "TYPE_1",
|
||||
"score": 0.99,
|
||||
"left": 10,
|
||||
"top": 15,
|
||||
"width": 100,
|
||||
"height": 100,
|
||||
},
|
||||
{
|
||||
"entity_type": "TYPE_2",
|
||||
"score": 0.7,
|
||||
"left": 25,
|
||||
"top": 49,
|
||||
"width": 75,
|
||||
"height": 51,
|
||||
},
|
||||
{
|
||||
"entity_type": "TYPE_3",
|
||||
"score": 0.6,
|
||||
"left": 613,
|
||||
"top": 26,
|
||||
"width": 226,
|
||||
"height": 35,
|
||||
},
|
||||
],
|
||||
25,
|
||||
[
|
||||
{"top": 0, "left": 0, "width": 100, "height": 100},
|
||||
{"top": 24, "left": 0, "width": 75, "height": 51},
|
||||
{"top": 1, "left": 588, "width": 226, "height": 35},
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_remove_bbox_padding_happy_path(
|
||||
mock_bbox_processor: BboxProcessor,
|
||||
mock_intermediate_bbox: dict,
|
||||
padding_width: int,
|
||||
expected_bboxes: list,
|
||||
):
|
||||
"""Test happy path for BboxProcessor.remove_bbox_padding
|
||||
|
||||
Args:
|
||||
mock_intermediate_bbox (dict): Value for mock of get_bboxes_from_analyzer_results.
|
||||
padding_width (int): Pixel width used for padding.
|
||||
expected_bboxes_dict (dict): Expected output bounding box dictionary.
|
||||
"""
|
||||
# Arrange
|
||||
|
||||
# Act
|
||||
test_bboxes_dict = mock_bbox_processor.remove_bbox_padding(mock_intermediate_bbox, padding_width)
|
||||
|
||||
# Assert
|
||||
assert test_bboxes_dict == expected_bboxes
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"padding_width, expected_error_type",
|
||||
[(-1, "ValueError"), (-200, "ValueError")],
|
||||
)
|
||||
def test_remove_bbox_padding_exceptions(
|
||||
mock_bbox_processor: BboxProcessor, padding_width: int, expected_error_type: str
|
||||
):
|
||||
"""Test error handling of remove_bbox_padding
|
||||
|
||||
Args:
|
||||
padding_width (int): Pixel width used for padding.
|
||||
expected_error_type (str): Type of error we expect to be raised.
|
||||
"""
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
# Act
|
||||
_ = mock_bbox_processor.remove_bbox_padding([], padding_width)
|
||||
|
||||
# Assert
|
||||
assert expected_error_type == exc_info.typename
|
||||
|
||||
|
||||
# ------------------------------------------------------
|
||||
# DicomImagePiiVerifyEngine._match_with_source()
|
||||
# ------------------------------------------------------
|
||||
@pytest.mark.parametrize(
|
||||
"source_labels, results, tolerance, expected_results, expected_match_found",
|
||||
[
|
||||
(
|
||||
[
|
||||
{
|
||||
"label": "DAVIDSON",
|
||||
"left": 25,
|
||||
"top": 25,
|
||||
"width": 241,
|
||||
"height": 37,
|
||||
},
|
||||
{
|
||||
"label": "DOUGLAS",
|
||||
"left": 287,
|
||||
"top": 25,
|
||||
"width": 230,
|
||||
"height": 36,
|
||||
},
|
||||
{
|
||||
"label": "[M]",
|
||||
"left": 535,
|
||||
"top": 25,
|
||||
"width": 60,
|
||||
"height": 45,
|
||||
},
|
||||
],
|
||||
{
|
||||
"entity_type": "PERSON",
|
||||
"score": 1.0,
|
||||
"left": 287,
|
||||
"top": 25,
|
||||
"width": 230,
|
||||
"height": 36,
|
||||
},
|
||||
50,
|
||||
[
|
||||
{
|
||||
"label": "DOUGLAS",
|
||||
"score": 1.0,
|
||||
"left": 287,
|
||||
"top": 25,
|
||||
"width": 230,
|
||||
"height": 36,
|
||||
}
|
||||
],
|
||||
True,
|
||||
),
|
||||
(
|
||||
[
|
||||
{
|
||||
"label": "DAVIDSON",
|
||||
"left": 25,
|
||||
"top": 25,
|
||||
"width": 241,
|
||||
"height": 37,
|
||||
},
|
||||
{
|
||||
"label": "DOUGLAS",
|
||||
"left": 287,
|
||||
"top": 25,
|
||||
"width": 230,
|
||||
"height": 36,
|
||||
},
|
||||
{
|
||||
"label": "[M]",
|
||||
"left": 535,
|
||||
"top": 25,
|
||||
"width": 60,
|
||||
"height": 45,
|
||||
},
|
||||
],
|
||||
{
|
||||
"entity_type": "PERSON",
|
||||
"score": 1.0,
|
||||
"left": 300,
|
||||
"top": 15,
|
||||
"width": 250,
|
||||
"height": 40,
|
||||
},
|
||||
50,
|
||||
[
|
||||
{
|
||||
"label": "DOUGLAS",
|
||||
"score": 1.0,
|
||||
"left": 287,
|
||||
"top": 25,
|
||||
"width": 230,
|
||||
"height": 36,
|
||||
}
|
||||
],
|
||||
True,
|
||||
),
|
||||
(
|
||||
[
|
||||
{
|
||||
"label": "DAVIDSON",
|
||||
"left": 25,
|
||||
"top": 25,
|
||||
"width": 241,
|
||||
"height": 37,
|
||||
},
|
||||
{
|
||||
"label": "DOUGLAS",
|
||||
"left": 287,
|
||||
"top": 25,
|
||||
"width": 230,
|
||||
"height": 36,
|
||||
},
|
||||
{
|
||||
"label": "[M]",
|
||||
"left": 535,
|
||||
"top": 25,
|
||||
"width": 60,
|
||||
"height": 45,
|
||||
},
|
||||
],
|
||||
{
|
||||
"entity_type": "PERSON",
|
||||
"score": 1.0,
|
||||
"left": 99,
|
||||
"top": 99,
|
||||
"width": 99,
|
||||
"height": 99,
|
||||
},
|
||||
10,
|
||||
[],
|
||||
False,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_match_with_source_happy_path(
|
||||
mock_bbox_processor: BboxProcessor,
|
||||
source_labels: List[dict],
|
||||
results: dict,
|
||||
tolerance: int,
|
||||
expected_results: List[dict],
|
||||
expected_match_found: bool,
|
||||
):
|
||||
"""Test happy path for BboxProcessor.match_with_source
|
||||
|
||||
Args:
|
||||
mock_bbox_processor (BboxProcessor): Instantiated engine.
|
||||
source_labels (dict): Ground truth labels for single instance.
|
||||
results (dict): Detected PHI dictionary.
|
||||
tolerance (int): Pixel difference tolerance for matching entities.
|
||||
expected_results (dict): Expected output dictionary.
|
||||
expected_match_found (bool): Expected match_found.
|
||||
"""
|
||||
# Assign
|
||||
all_pos = []
|
||||
|
||||
# Act
|
||||
test_all_pos, test_match_found = mock_bbox_processor.match_with_source(
|
||||
all_pos, source_labels, results, tolerance
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert test_all_pos == expected_results
|
||||
assert test_match_found == expected_match_found
|
||||
@@ -2,9 +2,11 @@
|
||||
"""
|
||||
import pydicom
|
||||
|
||||
from presidio_image_redactor import TesseractOCR, ImageAnalyzerEngine
|
||||
from presidio_image_redactor.dicom_image_pii_verify_engine import (
|
||||
from presidio_image_redactor import (
|
||||
TesseractOCR,
|
||||
ImageAnalyzerEngine,
|
||||
DicomImagePiiVerifyEngine,
|
||||
BboxProcessor,
|
||||
)
|
||||
from typing import List
|
||||
import pytest
|
||||
@@ -145,11 +147,11 @@ def test_eval_dicom_instance_happy_path(
|
||||
return_value=[None, None, None],
|
||||
)
|
||||
mock_get_ocr_bboxes = mocker.patch.object(
|
||||
DicomImagePiiVerifyEngine, "_get_bboxes_from_ocr_results", return_value=None
|
||||
BboxProcessor, "get_bboxes_from_ocr_results", return_value=None
|
||||
)
|
||||
mock_get_analyzer_bboxes = mocker.patch.object(
|
||||
DicomImagePiiVerifyEngine,
|
||||
"_get_bboxes_from_analyzer_results",
|
||||
BboxProcessor,
|
||||
"get_bboxes_from_analyzer_results",
|
||||
return_value=None,
|
||||
)
|
||||
mock_remove_dups = mocker.patch.object(
|
||||
@@ -181,80 +183,6 @@ def test_eval_dicom_instance_happy_path(
|
||||
assert mock_recall.call_count == 1
|
||||
|
||||
|
||||
# ------------------------------------------------------
|
||||
# DicomImagePiiVerifyEngine._get_bboxes_from_ocr_results()
|
||||
# ------------------------------------------------------
|
||||
@pytest.mark.parametrize(
|
||||
"ocr_results_raw, expected_results",
|
||||
[
|
||||
(
|
||||
{
|
||||
"left": [123],
|
||||
"top": [0],
|
||||
"width": [100],
|
||||
"height": [25],
|
||||
"conf": ["1"],
|
||||
"text": ["JOHN"],
|
||||
},
|
||||
[
|
||||
{
|
||||
"left": 123,
|
||||
"top": 0,
|
||||
"width": 100,
|
||||
"height": 25,
|
||||
"conf": 1,
|
||||
"label": "JOHN",
|
||||
}
|
||||
],
|
||||
),
|
||||
(
|
||||
{
|
||||
"left": [123, 345],
|
||||
"top": [0, 15],
|
||||
"width": [100, 75],
|
||||
"height": [25, 30],
|
||||
"conf": ["1", "0.87"],
|
||||
"text": ["JOHN", "DOE"],
|
||||
},
|
||||
[
|
||||
{
|
||||
"left": 123,
|
||||
"top": 0,
|
||||
"width": 100,
|
||||
"height": 25,
|
||||
"conf": 1,
|
||||
"label": "JOHN",
|
||||
},
|
||||
{
|
||||
"left": 345,
|
||||
"top": 15,
|
||||
"width": 75,
|
||||
"height": 30,
|
||||
"conf": 0.87,
|
||||
"label": "DOE",
|
||||
},
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_get_bboxes_from_ocr_results_happy_path(
|
||||
mock_engine: DicomImagePiiVerifyEngine,
|
||||
ocr_results_raw: dict,
|
||||
expected_results: list,
|
||||
):
|
||||
"""Test happy path for DicomImagePiiVerifyEngine._get_bboxes_from_ocr_results
|
||||
Args:
|
||||
mock_engine (DicomImagePiiVerifyEngine): Instantiated engine.
|
||||
ocr_results_raw (dict): Raw OCR results.
|
||||
expected_results (list): Formatted OCR results.
|
||||
"""
|
||||
# Act
|
||||
test_bboxes = mock_engine._get_bboxes_from_ocr_results(ocr_results_raw)
|
||||
|
||||
# Assert
|
||||
assert test_bboxes == expected_results
|
||||
|
||||
|
||||
# ------------------------------------------------------
|
||||
# DicomImagePiiVerifyEngine._remove_duplicate_entities()
|
||||
# ------------------------------------------------------
|
||||
@@ -471,171 +399,6 @@ def test_remove_duplicate_entities_happy_path(
|
||||
assert test_results_no_dups == expected_results
|
||||
|
||||
|
||||
# ------------------------------------------------------
|
||||
# DicomImagePiiVerifyEngine._match_with_source()
|
||||
# ------------------------------------------------------
|
||||
@pytest.mark.parametrize(
|
||||
"source_labels, results, tolerance, expected_results, expected_match_found",
|
||||
[
|
||||
(
|
||||
[
|
||||
{
|
||||
"label": "DAVIDSON",
|
||||
"left": 25,
|
||||
"top": 25,
|
||||
"width": 241,
|
||||
"height": 37,
|
||||
},
|
||||
{
|
||||
"label": "DOUGLAS",
|
||||
"left": 287,
|
||||
"top": 25,
|
||||
"width": 230,
|
||||
"height": 36,
|
||||
},
|
||||
{
|
||||
"label": "[M]",
|
||||
"left": 535,
|
||||
"top": 25,
|
||||
"width": 60,
|
||||
"height": 45,
|
||||
},
|
||||
],
|
||||
{
|
||||
"entity_type": "PERSON",
|
||||
"score": 1.0,
|
||||
"left": 287,
|
||||
"top": 25,
|
||||
"width": 230,
|
||||
"height": 36,
|
||||
},
|
||||
50,
|
||||
[
|
||||
{
|
||||
"label": "DOUGLAS",
|
||||
"score": 1.0,
|
||||
"left": 287,
|
||||
"top": 25,
|
||||
"width": 230,
|
||||
"height": 36,
|
||||
}
|
||||
],
|
||||
True,
|
||||
),
|
||||
(
|
||||
[
|
||||
{
|
||||
"label": "DAVIDSON",
|
||||
"left": 25,
|
||||
"top": 25,
|
||||
"width": 241,
|
||||
"height": 37,
|
||||
},
|
||||
{
|
||||
"label": "DOUGLAS",
|
||||
"left": 287,
|
||||
"top": 25,
|
||||
"width": 230,
|
||||
"height": 36,
|
||||
},
|
||||
{
|
||||
"label": "[M]",
|
||||
"left": 535,
|
||||
"top": 25,
|
||||
"width": 60,
|
||||
"height": 45,
|
||||
},
|
||||
],
|
||||
{
|
||||
"entity_type": "PERSON",
|
||||
"score": 1.0,
|
||||
"left": 300,
|
||||
"top": 15,
|
||||
"width": 250,
|
||||
"height": 40,
|
||||
},
|
||||
50,
|
||||
[
|
||||
{
|
||||
"label": "DOUGLAS",
|
||||
"score": 1.0,
|
||||
"left": 287,
|
||||
"top": 25,
|
||||
"width": 230,
|
||||
"height": 36,
|
||||
}
|
||||
],
|
||||
True,
|
||||
),
|
||||
(
|
||||
[
|
||||
{
|
||||
"label": "DAVIDSON",
|
||||
"left": 25,
|
||||
"top": 25,
|
||||
"width": 241,
|
||||
"height": 37,
|
||||
},
|
||||
{
|
||||
"label": "DOUGLAS",
|
||||
"left": 287,
|
||||
"top": 25,
|
||||
"width": 230,
|
||||
"height": 36,
|
||||
},
|
||||
{
|
||||
"label": "[M]",
|
||||
"left": 535,
|
||||
"top": 25,
|
||||
"width": 60,
|
||||
"height": 45,
|
||||
},
|
||||
],
|
||||
{
|
||||
"entity_type": "PERSON",
|
||||
"score": 1.0,
|
||||
"left": 99,
|
||||
"top": 99,
|
||||
"width": 99,
|
||||
"height": 99,
|
||||
},
|
||||
10,
|
||||
[],
|
||||
False,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_match_with_source_happy_path(
|
||||
mock_engine: DicomImagePiiVerifyEngine,
|
||||
source_labels: List[dict],
|
||||
results: dict,
|
||||
tolerance: int,
|
||||
expected_results: List[dict],
|
||||
expected_match_found: bool,
|
||||
):
|
||||
"""Test happy path for DicomImagePiiVerifyEngine._match_with_source
|
||||
|
||||
Args:
|
||||
mock_engine (DicomImagePiiVerifyEngine): Instantiated engine.
|
||||
source_labels (dict): Ground truth labels for single instance.
|
||||
results (dict): Detected PHI dictionary.
|
||||
tolerance (int): Pixel difference tolerance for matching entities.
|
||||
expected_results (dict): Expected output dictionary.
|
||||
expected_match_found (bool): Expected match_found.
|
||||
"""
|
||||
# Assign
|
||||
all_pos = []
|
||||
|
||||
# Act
|
||||
test_all_pos, test_match_found = mock_engine._match_with_source(
|
||||
all_pos, source_labels, results, tolerance
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert test_all_pos == expected_results
|
||||
assert test_match_found == expected_match_found
|
||||
|
||||
|
||||
# ------------------------------------------------------
|
||||
# DicomImagePiiVerifyEngine._label_all_positives()
|
||||
# ------------------------------------------------------
|
||||
|
||||
@@ -5,10 +5,7 @@ import os
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import pydicom
|
||||
from presidio_image_redactor.dicom_image_redactor_engine import DicomImageRedactorEngine
|
||||
from presidio_image_redactor.entities.image_recognizer_result import (
|
||||
ImageRecognizerResult,
|
||||
)
|
||||
from presidio_image_redactor import DicomImageRedactorEngine
|
||||
from typing import Union, Tuple
|
||||
import pytest
|
||||
|
||||
@@ -828,185 +825,6 @@ def test_make_phi_list_happy_path(
|
||||
assert set(test_phi_str_list) == set(expected_return_list)
|
||||
|
||||
|
||||
# ------------------------------------------------------
|
||||
# DicomImageRedactorEngine._get_bboxes_from_analyzer_results()
|
||||
# ------------------------------------------------------
|
||||
@pytest.mark.parametrize(
|
||||
"analyzer_results, expected_bboxes",
|
||||
[
|
||||
(
|
||||
[
|
||||
ImageRecognizerResult(
|
||||
entity_type="TYPE_1",
|
||||
start=0,
|
||||
end=0,
|
||||
left=25,
|
||||
top=25,
|
||||
width=100,
|
||||
height=100,
|
||||
score=0.99,
|
||||
),
|
||||
ImageRecognizerResult(
|
||||
entity_type="TYPE_2",
|
||||
start=10,
|
||||
end=10,
|
||||
left=25,
|
||||
top=49,
|
||||
width=75,
|
||||
height=51,
|
||||
score=0.7,
|
||||
),
|
||||
ImageRecognizerResult(
|
||||
entity_type="TYPE_3",
|
||||
start=25,
|
||||
end=35,
|
||||
left=613,
|
||||
top=26,
|
||||
width=226,
|
||||
height=35,
|
||||
score=0.6,
|
||||
),
|
||||
],
|
||||
[
|
||||
{
|
||||
"entity_type": "TYPE_1",
|
||||
"score": 0.99,
|
||||
"left": 25,
|
||||
"top": 25,
|
||||
"width": 100,
|
||||
"height": 100,
|
||||
},
|
||||
{
|
||||
"entity_type": "TYPE_2",
|
||||
"score": 0.7,
|
||||
"left": 25,
|
||||
"top": 49,
|
||||
"width": 75,
|
||||
"height": 51,
|
||||
},
|
||||
{
|
||||
"entity_type": "TYPE_3",
|
||||
"score": 0.6,
|
||||
"left": 613,
|
||||
"top": 26,
|
||||
"width": 226,
|
||||
"height": 35,
|
||||
},
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_get_bboxes_from_analyzer_results_happy_path(
|
||||
mock_engine: DicomImageRedactorEngine,
|
||||
analyzer_results: list,
|
||||
expected_bboxes: list,
|
||||
):
|
||||
"""Test happy path for DicomImageRedactorEngine._get_bboxes_from_analyzer_results
|
||||
|
||||
Args:
|
||||
analyzer_results (list): Results from using ImageAnalyzerEngine.
|
||||
expected_bboxes (list): Expected output bounding box list.
|
||||
"""
|
||||
# Arrange
|
||||
|
||||
# Act
|
||||
test_bboxes = mock_engine._get_bboxes_from_analyzer_results(analyzer_results)
|
||||
|
||||
# Assert
|
||||
assert test_bboxes == expected_bboxes
|
||||
|
||||
|
||||
# ------------------------------------------------------
|
||||
# DicomImageRedactorEngine._format_bboxes()
|
||||
# ------------------------------------------------------
|
||||
@pytest.mark.parametrize(
|
||||
"mock_intermediate_bbox, padding_width, expected_bboxes",
|
||||
[
|
||||
(
|
||||
[
|
||||
{
|
||||
"entity_type": "TYPE_1",
|
||||
"score": 0.99,
|
||||
"left": 10,
|
||||
"top": 15,
|
||||
"width": 100,
|
||||
"height": 100,
|
||||
},
|
||||
{
|
||||
"entity_type": "TYPE_2",
|
||||
"score": 0.7,
|
||||
"left": 25,
|
||||
"top": 49,
|
||||
"width": 75,
|
||||
"height": 51,
|
||||
},
|
||||
{
|
||||
"entity_type": "TYPE_3",
|
||||
"score": 0.6,
|
||||
"left": 613,
|
||||
"top": 26,
|
||||
"width": 226,
|
||||
"height": 35,
|
||||
},
|
||||
],
|
||||
25,
|
||||
[
|
||||
{"top": 0, "left": 0, "width": 100, "height": 100},
|
||||
{"top": 24, "left": 0, "width": 75, "height": 51},
|
||||
{"top": 1, "left": 588, "width": 226, "height": 35},
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_format_bboxes_happy_path(
|
||||
mocker,
|
||||
mock_engine: DicomImageRedactorEngine,
|
||||
mock_intermediate_bbox: dict,
|
||||
padding_width: int,
|
||||
expected_bboxes: list,
|
||||
):
|
||||
"""Test happy path for DicomImageRedactorEngine._format_bboxes
|
||||
|
||||
Args:
|
||||
mock_intermediate_bbox (dict): Value for mock of _get_bboxes_from_analyzer_results.
|
||||
padding_width (int): Pixel width used for padding.
|
||||
expected_bboxes_dict (dict): Expected output bounding box dictionary.
|
||||
"""
|
||||
# Arrange
|
||||
mock_get_bboxes = mocker.patch(
|
||||
"presidio_image_redactor.dicom_image_redactor_engine.DicomImageRedactorEngine._get_bboxes_from_analyzer_results",
|
||||
return_value=mock_intermediate_bbox,
|
||||
)
|
||||
|
||||
# Act
|
||||
test_bboxes_dict = mock_engine._format_bboxes([], padding_width)
|
||||
|
||||
# Assert
|
||||
assert mock_get_bboxes.call_count == 1
|
||||
assert test_bboxes_dict == expected_bboxes
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"padding_width, expected_error_type",
|
||||
[(-1, "ValueError"), (-200, "ValueError")],
|
||||
)
|
||||
def test_format_bboxes_exceptions(
|
||||
mock_engine: DicomImageRedactorEngine, padding_width: int, expected_error_type: str
|
||||
):
|
||||
"""Test error handling of _format_bboxes
|
||||
|
||||
Args:
|
||||
padding_width (int): Pixel width used for padding.
|
||||
expected_error_type (str): Type of error we expect to be raised.
|
||||
"""
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
# Act
|
||||
_ = mock_engine._format_bboxes([], padding_width)
|
||||
|
||||
# Assert
|
||||
assert expected_error_type == exc_info.typename
|
||||
|
||||
|
||||
# ------------------------------------------------------
|
||||
# DicomImageRedactorEngine._set_bbox_color()
|
||||
# ------------------------------------------------------
|
||||
@@ -1258,9 +1076,13 @@ def test_DicomImageRedactorEngine_redact_happy_path(
|
||||
return_value=None,
|
||||
)
|
||||
|
||||
mock_format_bboxes = mocker.patch.object(
|
||||
DicomImageRedactorEngine,
|
||||
"_format_bboxes",
|
||||
mock_get_analyze_bbox = mocker.patch(
|
||||
"presidio_image_redactor.image_redactor_engine.BboxProcessor.get_bboxes_from_analyzer_results",
|
||||
return_value=None,
|
||||
)
|
||||
|
||||
mock_remove_bbox_padding = mocker.patch(
|
||||
"presidio_image_redactor.image_redactor_engine.BboxProcessor.remove_bbox_padding",
|
||||
return_value=None,
|
||||
)
|
||||
|
||||
@@ -1285,7 +1107,8 @@ def test_DicomImageRedactorEngine_redact_happy_path(
|
||||
assert mock_make_phi_list.call_count == 1
|
||||
assert mock_pattern_recognizer.call_count == 1
|
||||
assert mock_analyze.call_count == 1
|
||||
assert mock_format_bboxes.call_count == 1
|
||||
assert mock_get_analyze_bbox.call_count == 1
|
||||
assert mock_remove_bbox_padding.call_count == 1
|
||||
assert mock_add_redact_box.call_count == 1
|
||||
|
||||
|
||||
@@ -1355,8 +1178,13 @@ def test_DicomImageRedactorEngine_redact_single_dicom_image_happy_path(
|
||||
return_value=None,
|
||||
)
|
||||
|
||||
mock_format_bboxes = mocker.patch(
|
||||
"presidio_image_redactor.dicom_image_redactor_engine.DicomImageRedactorEngine._format_bboxes",
|
||||
mock_get_analyze_bbox = mocker.patch(
|
||||
"presidio_image_redactor.image_redactor_engine.BboxProcessor.get_bboxes_from_analyzer_results",
|
||||
return_value=None,
|
||||
)
|
||||
|
||||
mock_remove_bbox_padding = mocker.patch(
|
||||
"presidio_image_redactor.image_redactor_engine.BboxProcessor.remove_bbox_padding",
|
||||
return_value=None,
|
||||
)
|
||||
|
||||
@@ -1386,7 +1214,8 @@ def test_DicomImageRedactorEngine_redact_single_dicom_image_happy_path(
|
||||
assert mock_make_phi_list.call_count == 1
|
||||
assert mock_pattern_recognizer.call_count == 1
|
||||
assert mock_analyze.call_count == 1
|
||||
assert mock_format_bboxes.call_count == 1
|
||||
assert mock_get_analyze_bbox.call_count == 1
|
||||
assert mock_remove_bbox_padding.call_count == 1
|
||||
assert mock_add_redact_box.call_count == 1
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user