mirror of
https://github.com/data-privacy-stack/presidio.git
synced 2026-07-23 11:20:55 -05:00
Enable thresholding of OCR results (#1001)
This commit is contained in:
@@ -7,9 +7,11 @@ All notable changes to this project will be documented in this file.
|
||||
#### Image redactor
|
||||
* 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
|
||||
|
||||
#### General
|
||||
* Updated documentation to include instructions on using DICOM evaluation code
|
||||
* Updated documentation to mention OCR thresholding
|
||||
|
||||
## [2.2.31] - 14.12.2022
|
||||
### Changed
|
||||
|
||||
@@ -111,13 +111,13 @@ Let's say we ran the above code block and see the following for `ocr_results_for
|
||||
// OCR Results (formatted)
|
||||
[
|
||||
{
|
||||
"left": 25,
|
||||
"top": 25,
|
||||
"width": 241,
|
||||
"height": 37,
|
||||
"conf": 95.833916,
|
||||
"label": "DAVIDSON"
|
||||
},
|
||||
"left": 25,
|
||||
"top": 25,
|
||||
"width": 241,
|
||||
"height": 37,
|
||||
"conf": 95.833916,
|
||||
"label": "DAVIDSON"
|
||||
},
|
||||
{
|
||||
"left": 287,
|
||||
"top": 25,
|
||||
@@ -194,6 +194,8 @@ instance = pydicom.dcmread(file_of_interest)
|
||||
_, eval_results = dicom_engine.eval_dicom_instance(instance, ground_truth)
|
||||
```
|
||||
|
||||
You can also set optional arguments to see the effect of padding width, ground-truth matching tolerance, and OCR confidence threshold (e.g., `ocr_kwargs={"ocr_threshold": 50}`).
|
||||
|
||||
For a full demonstration, please see the [evaluation notebook](../samples/python/example_dicom_redactor_evaluation.ipynb).
|
||||
|
||||
*Return to the [Table of Contents](#table-of-contents)*
|
||||
|
||||
@@ -139,7 +139,8 @@ Python script example can be found under:
|
||||
engine.redact_from_file(input_path, output_dir, padding_width=25, fill="contrast")
|
||||
|
||||
# Option 3: Redact from directory
|
||||
engine.redact_from_directory("path/to/your/dicom", output_dir, padding_width=25, fill="contrast")
|
||||
ocr_kwargs = {"ocr_threshold": 50}
|
||||
engine.redact_from_directory("path/to/your/dicom", output_dir, fill="background", ocr_kwargs=ocr_kwargs)
|
||||
```
|
||||
|
||||
### Evaluating de-identification performance
|
||||
|
||||
@@ -111,13 +111,13 @@ Let's say we ran the above code block and see the following for `ocr_results_for
|
||||
// OCR Results (formatted)
|
||||
[
|
||||
{
|
||||
"left": 25,
|
||||
"top": 25,
|
||||
"width": 241,
|
||||
"height": 37,
|
||||
"conf": 95.833916,
|
||||
"label": "DAVIDSON"
|
||||
},
|
||||
"left": 25,
|
||||
"top": 25,
|
||||
"width": 241,
|
||||
"height": 37,
|
||||
"conf": 95.833916,
|
||||
"label": "DAVIDSON"
|
||||
},
|
||||
{
|
||||
"left": 287,
|
||||
"top": 25,
|
||||
@@ -194,6 +194,8 @@ instance = pydicom.dcmread(file_of_interest)
|
||||
_, eval_results = dicom_engine.eval_dicom_instance(instance, ground_truth)
|
||||
```
|
||||
|
||||
You can also set optional arguments to see the effect of padding width, ground-truth matching tolerance, and OCR confidence threshold (e.g., `ocr_kwargs={"ocr_threshold": 50}`).
|
||||
|
||||
For a full demonstration, please see the [evaluation notebook](../docs/samples/python/example_dicom_redactor_evaluation.ipynb).
|
||||
|
||||
*Return to the [Table of Contents](#table-of-contents)*
|
||||
|
||||
@@ -144,7 +144,8 @@ redacted_dicom_image = engine.redact(dicom_image, fill="contrast")
|
||||
engine.redact_from_file(input_path, output_dir, padding_width=25, fill="contrast")
|
||||
|
||||
# Option 3: Redact from directory
|
||||
engine.redact_from_directory("path/to/your/dicom", output_dir, padding_width=25, fill="contrast")
|
||||
ocr_kwargs = {"ocr_threshold": 50}
|
||||
engine.redact_from_directory("path/to/your/dicom", output_dir, fill="background", ocr_kwargs=ocr_kwargs)
|
||||
```
|
||||
|
||||
See the example notebook for more details and visual confirmation of the output: [docs/samples/python/example_dicom_image_redactor.ipynb](../docs/samples/python/example_dicom_image_redactor.ipynb).
|
||||
|
||||
@@ -44,16 +44,20 @@ class DicomImagePiiVerifyEngine(ImagePiiVerifyEngine, DicomImageRedactorEngine):
|
||||
def verify_dicom_instance(
|
||||
self,
|
||||
instance: pydicom.dataset.FileDataset,
|
||||
padding_width: Optional[int] = 25,
|
||||
display_image: Optional[bool] = True,
|
||||
**kwargs,
|
||||
padding_width: int = 25,
|
||||
display_image: bool = True,
|
||||
ocr_kwargs: Optional[dict] = None,
|
||||
**text_analyzer_kwargs,
|
||||
) -> Tuple[Optional[PIL.Image.Image], dict, list]:
|
||||
"""Verify PII on a single DICOM instance.
|
||||
|
||||
:param instance: Loaded DICOM instance including pixel data and metadata.
|
||||
:param padding_width: Padding width to use when running OCR.
|
||||
:param display_image: If the verificationimage is displayed and returned.
|
||||
:param kwargs: Additional values for the analyze method in ImageAnalyzerEngine.
|
||||
:param ocr_kwargs: Additional params for OCR methods.
|
||||
:param text_analyzer_kwargs: Additional values for the analyze method
|
||||
in ImageAnalyzerEngine.
|
||||
|
||||
:return: Image with boxes identifying PHI, OCR results,
|
||||
and analyzer results.
|
||||
"""
|
||||
@@ -78,12 +82,17 @@ class DicomImagePiiVerifyEngine(ImagePiiVerifyEngine, DicomImageRedactorEngine):
|
||||
)
|
||||
ocr_results = self.ocr_engine.perform_ocr(image)
|
||||
analyzer_results = self.image_analyzer_engine.analyze(
|
||||
image, ad_hoc_recognizers=[deny_list_recognizer], **kwargs
|
||||
image,
|
||||
ad_hoc_recognizers=[deny_list_recognizer],
|
||||
ocr_kwargs=ocr_kwargs,
|
||||
**text_analyzer_kwargs,
|
||||
)
|
||||
|
||||
# Get image with verification boxes
|
||||
verify_image = (
|
||||
self.verify(image, ad_hoc_recognizers=[deny_list_recognizer], **kwargs)
|
||||
self.verify(
|
||||
image, ad_hoc_recognizers=[deny_list_recognizer], **text_analyzer_kwargs
|
||||
)
|
||||
if display_image
|
||||
else None
|
||||
)
|
||||
@@ -94,10 +103,11 @@ class DicomImagePiiVerifyEngine(ImagePiiVerifyEngine, DicomImageRedactorEngine):
|
||||
self,
|
||||
instance: pydicom.dataset.FileDataset,
|
||||
ground_truth: dict,
|
||||
padding_width: Optional[int] = 25,
|
||||
tolerance: Optional[int] = 50,
|
||||
display_image: Optional[bool] = False,
|
||||
**kwargs,
|
||||
padding_width: int = 25,
|
||||
tolerance: int = 50,
|
||||
display_image: bool = False,
|
||||
ocr_kwargs: Optional[dict] = None,
|
||||
**text_analyzer_kwargs,
|
||||
) -> Tuple[Optional[PIL.Image.Image], dict]:
|
||||
"""Evaluate performance for a single DICOM instance.
|
||||
|
||||
@@ -106,12 +116,19 @@ class DicomImagePiiVerifyEngine(ImagePiiVerifyEngine, DicomImageRedactorEngine):
|
||||
:param padding_width: Padding width to use when running OCR.
|
||||
:param tolerance: Pixel distance tolerance for matching to ground truth.
|
||||
:param display_image: If the verificationimage is displayed and returned.
|
||||
:param kwargs: Additional values for the analyze method in ImageAnalyzerEngine.
|
||||
:param ocr_kwargs: Additional params for OCR methods.
|
||||
:param text_analyzer_kwargs: Additional values for the analyze method
|
||||
in ImageAnalyzerEngine.
|
||||
|
||||
:return: Evaluation comparing redactor engine results vs ground truth.
|
||||
"""
|
||||
# Verify detected PHI
|
||||
verify_image, ocr_results, analyzer_results = self.verify_dicom_instance(
|
||||
instance, padding_width, display_image, **kwargs
|
||||
instance,
|
||||
padding_width,
|
||||
display_image,
|
||||
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)
|
||||
|
||||
@@ -10,7 +10,7 @@ import PIL
|
||||
import png
|
||||
import numpy as np
|
||||
from matplotlib import pyplot as plt # necessary import for PIL typing # noqa: F401
|
||||
from typing import Tuple, List, Union
|
||||
from typing import Tuple, List, Union, Optional
|
||||
|
||||
from presidio_image_redactor import ImageRedactorEngine
|
||||
from presidio_image_redactor import ImageAnalyzerEngine # noqa: F401
|
||||
@@ -29,7 +29,8 @@ class DicomImageRedactorEngine(ImageRedactorEngine):
|
||||
fill: str = "contrast",
|
||||
padding_width: int = 25,
|
||||
crop_ratio: float = 0.75,
|
||||
**kwargs,
|
||||
ocr_kwargs: Optional[dict] = None,
|
||||
**text_analyzer_kwargs,
|
||||
):
|
||||
"""Redact method to redact the given DICOM image.
|
||||
|
||||
@@ -41,7 +42,9 @@ class DicomImageRedactorEngine(ImageRedactorEngine):
|
||||
:param padding_width: Padding width to use when running OCR.
|
||||
:param crop_ratio: Portion of image to consider when selecting
|
||||
most common pixel value as the background color value.
|
||||
:param kwargs: Additional values for the analyze method in AnalyzerEngine
|
||||
:param ocr_kwargs: Additional params for OCR methods.
|
||||
:param text_analyzer_kwargs: Additional values for the analyze method
|
||||
in AnalyzerEngine.
|
||||
|
||||
:return: DICOM instance with redacted pixel data.
|
||||
"""
|
||||
@@ -65,7 +68,10 @@ class DicomImageRedactorEngine(ImageRedactorEngine):
|
||||
supported_entity="PERSON", deny_list=phi_list
|
||||
)
|
||||
analyzer_results = self.image_analyzer_engine.analyze(
|
||||
image, ad_hoc_recognizers=[deny_list_recognizer], **kwargs
|
||||
image,
|
||||
ad_hoc_recognizers=[deny_list_recognizer],
|
||||
ocr_kwargs=ocr_kwargs,
|
||||
**text_analyzer_kwargs,
|
||||
)
|
||||
|
||||
# Redact all bounding boxes from DICOM file
|
||||
@@ -81,7 +87,8 @@ class DicomImageRedactorEngine(ImageRedactorEngine):
|
||||
padding_width: int = 25,
|
||||
crop_ratio: float = 0.75,
|
||||
fill: str = "contrast",
|
||||
**kwargs,
|
||||
ocr_kwargs: Optional[dict] = None,
|
||||
**text_analyzer_kwargs,
|
||||
) -> None:
|
||||
"""Redact method to redact from a given file.
|
||||
|
||||
@@ -93,7 +100,9 @@ class DicomImageRedactorEngine(ImageRedactorEngine):
|
||||
:param padding_width : Padding width to use when running OCR.
|
||||
:param fill: Color setting to use for redaction box
|
||||
("contrast" or "background").
|
||||
:param kwargs: Additional values for the analyze method in AnalyzerEngine
|
||||
:param ocr_kwargs: Additional params for OCR methods.
|
||||
:param text_analyzer_kwargs: Additional values for the analyze method
|
||||
in AnalyzerEngine.
|
||||
"""
|
||||
# Verify the given paths
|
||||
if Path(input_dicom_path).is_dir() is True:
|
||||
@@ -116,7 +125,8 @@ class DicomImageRedactorEngine(ImageRedactorEngine):
|
||||
padding_width=padding_width,
|
||||
overwrite=True,
|
||||
dst_parent_dir=".",
|
||||
**kwargs,
|
||||
ocr_kwargs=ocr_kwargs,
|
||||
**text_analyzer_kwargs,
|
||||
)
|
||||
|
||||
print(f"Output written to {output_location}")
|
||||
@@ -130,7 +140,8 @@ class DicomImageRedactorEngine(ImageRedactorEngine):
|
||||
padding_width: int = 25,
|
||||
crop_ratio: float = 0.75,
|
||||
fill: str = "contrast",
|
||||
**kwargs,
|
||||
ocr_kwargs: Optional[dict] = None,
|
||||
**text_analyzer_kwargs,
|
||||
) -> None:
|
||||
"""Redact method to redact from a directory of files.
|
||||
|
||||
@@ -144,7 +155,9 @@ class DicomImageRedactorEngine(ImageRedactorEngine):
|
||||
most common pixel value as the background color value.
|
||||
:param fill: Color setting to use for redaction box
|
||||
("contrast" or "background").
|
||||
:param kwargs: Additional values for the analyze method in AnalyzerEngine
|
||||
:param ocr_kwargs: Additional params for OCR methods.
|
||||
:param text_analyzer_kwargs: Additional values for the analyze method
|
||||
in AnalyzerEngine.
|
||||
"""
|
||||
# Verify the given paths
|
||||
if Path(input_dicom_path).is_dir() is False:
|
||||
@@ -167,7 +180,8 @@ class DicomImageRedactorEngine(ImageRedactorEngine):
|
||||
padding_width=padding_width,
|
||||
overwrite=True,
|
||||
dst_parent_dir=".",
|
||||
**kwargs,
|
||||
ocr_kwargs=ocr_kwargs,
|
||||
**text_analyzer_kwargs,
|
||||
)
|
||||
|
||||
print(f"Output written to {output_location}")
|
||||
@@ -758,7 +772,8 @@ class DicomImageRedactorEngine(ImageRedactorEngine):
|
||||
padding_width: int,
|
||||
overwrite: bool,
|
||||
dst_parent_dir: str,
|
||||
**kwargs,
|
||||
ocr_kwargs: Optional[dict] = None,
|
||||
**text_analyzer_kwargs,
|
||||
) -> str:
|
||||
"""Redact text PHI present on a DICOM image.
|
||||
|
||||
@@ -771,7 +786,9 @@ class DicomImageRedactorEngine(ImageRedactorEngine):
|
||||
:param overwrite: Only set to True if you are providing the
|
||||
duplicated DICOM path in dcm_path.
|
||||
:param dst_parent_dir: String path to parent directory of where to store copies.
|
||||
:param kwargs: Additional values for the analyze method in AnalyzerEngine
|
||||
:param ocr_kwargs: Additional params for OCR methods.
|
||||
:param text_analyzer_kwargs: Additional values for the analyze method
|
||||
in AnalyzerEngine.
|
||||
|
||||
:return: Path to the output DICOM file.
|
||||
"""
|
||||
@@ -805,7 +822,10 @@ class DicomImageRedactorEngine(ImageRedactorEngine):
|
||||
supported_entity="PERSON", deny_list=phi_list
|
||||
)
|
||||
analyzer_results = self.image_analyzer_engine.analyze(
|
||||
image, ad_hoc_recognizers=[deny_list_recognizer], **kwargs
|
||||
image,
|
||||
ad_hoc_recognizers=[deny_list_recognizer],
|
||||
ocr_kwargs=ocr_kwargs,
|
||||
**text_analyzer_kwargs,
|
||||
)
|
||||
|
||||
# Redact all bounding boxes from DICOM file
|
||||
@@ -825,7 +845,8 @@ class DicomImageRedactorEngine(ImageRedactorEngine):
|
||||
padding_width: int,
|
||||
overwrite: bool,
|
||||
dst_parent_dir: str,
|
||||
**kwargs,
|
||||
ocr_kwargs: Optional[dict] = None,
|
||||
**text_analyzer_kwargs,
|
||||
) -> str:
|
||||
"""Redact text PHI present on all DICOM images in a directory.
|
||||
|
||||
@@ -838,7 +859,9 @@ class DicomImageRedactorEngine(ImageRedactorEngine):
|
||||
:param overwrite: Only set to True if you are providing
|
||||
the duplicated DICOM dir in dcm_dir.
|
||||
:param dst_parent_dir: String path to parent directory of where to store copies.
|
||||
:param kwargs: Additional values for the analyze method in AnalyzerEngine
|
||||
:param ocr_kwargs: Additional params for OCR methods.
|
||||
:param text_analyzer_kwargs: Additional values for the analyze method
|
||||
in AnalyzerEngine.
|
||||
|
||||
Return:
|
||||
dst_dir (str): Path to the output DICOM directory.
|
||||
@@ -865,7 +888,8 @@ class DicomImageRedactorEngine(ImageRedactorEngine):
|
||||
padding_width,
|
||||
overwrite,
|
||||
dst_parent_dir,
|
||||
**kwargs,
|
||||
ocr_kwargs=ocr_kwargs,
|
||||
**text_analyzer_kwargs,
|
||||
)
|
||||
|
||||
return dst_dir
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import List
|
||||
from typing import List, Tuple, Optional
|
||||
|
||||
from presidio_analyzer import AnalyzerEngine, RecognizerResult
|
||||
|
||||
@@ -14,7 +14,11 @@ class ImageAnalyzerEngine:
|
||||
:param ocr: the OCR object to be used to detect text in images.
|
||||
"""
|
||||
|
||||
def __init__(self, analyzer_engine: AnalyzerEngine = None, ocr: OCR = None):
|
||||
def __init__(
|
||||
self,
|
||||
analyzer_engine: Optional[AnalyzerEngine] = None,
|
||||
ocr: Optional[OCR] = None,
|
||||
):
|
||||
if not analyzer_engine:
|
||||
analyzer_engine = AnalyzerEngine()
|
||||
self.analyzer_engine = analyzer_engine
|
||||
@@ -23,25 +27,62 @@ class ImageAnalyzerEngine:
|
||||
ocr = TesseractOCR()
|
||||
self.ocr = ocr
|
||||
|
||||
def analyze(self, image: object, **kwargs) -> List[ImageRecognizerResult]:
|
||||
def analyze(
|
||||
self, image: object, ocr_kwargs: Optional[dict] = None, **text_analyzer_kwargs
|
||||
) -> List[ImageRecognizerResult]:
|
||||
"""Analyse method to analyse the given image.
|
||||
|
||||
:param image: PIL Image/numpy array or file path(str) to be processed
|
||||
:param kwargs: Additional values for the analyze method in AnalyzerEngine
|
||||
:param image: PIL Image/numpy array or file path(str) to be processed.
|
||||
:param ocr_kwargs: Additional params for OCR methods.
|
||||
:param text_analyzer_kwargs: Additional values for the analyze method
|
||||
in AnalyzerEngine.
|
||||
|
||||
:return: list of the extract entities with image bounding boxes
|
||||
:return: List of the extract entities with image bounding boxes.
|
||||
"""
|
||||
ocr_result = self.ocr.perform_ocr(image)
|
||||
# Perform OCR
|
||||
perform_ocr_kwargs, ocr_threshold = self._parse_ocr_kwargs(ocr_kwargs)
|
||||
ocr_result = self.ocr.perform_ocr(image, **perform_ocr_kwargs)
|
||||
|
||||
# Apply OCR confidence threshold if it is passed in
|
||||
if ocr_threshold:
|
||||
ocr_result = self.threshold_ocr_result(ocr_result, ocr_threshold)
|
||||
|
||||
# Analyze text
|
||||
text = self.ocr.get_text_from_ocr_dict(ocr_result)
|
||||
|
||||
analyzer_result = self.analyzer_engine.analyze(
|
||||
text=text, language="en", **kwargs
|
||||
text=text, language="en", **text_analyzer_kwargs
|
||||
)
|
||||
bboxes = self.map_analyzer_results_to_bounding_boxes(
|
||||
analyzer_result, ocr_result, text
|
||||
)
|
||||
return bboxes
|
||||
|
||||
@staticmethod
|
||||
def threshold_ocr_result(ocr_result: dict, ocr_threshold: float) -> dict:
|
||||
"""Filter out OCR results below confidence threshold.
|
||||
|
||||
:param ocr_result: OCR results (raw).
|
||||
:param ocr_threshold: Threshold value between -1 and 100.
|
||||
|
||||
:return: OCR results with low confidence items removed.
|
||||
"""
|
||||
if ocr_threshold < -1 or ocr_threshold > 100:
|
||||
raise ValueError("ocr_threshold must be between -1 and 100")
|
||||
|
||||
# Get indices of items above threshold
|
||||
idx = list()
|
||||
for i, val in enumerate(ocr_result["conf"]):
|
||||
if float(val) >= ocr_threshold:
|
||||
idx.append(i)
|
||||
|
||||
# Only retain high confidence items
|
||||
filtered_ocr_result = {}
|
||||
for key in list(ocr_result.keys()):
|
||||
filtered_ocr_result[key] = [ocr_result[key][i] for i in idx]
|
||||
|
||||
return filtered_ocr_result
|
||||
|
||||
@staticmethod
|
||||
def map_analyzer_results_to_bounding_boxes(
|
||||
text_analyzer_results: List[RecognizerResult], ocr_result: dict, text: str
|
||||
@@ -116,3 +157,25 @@ class ImageAnalyzerEngine:
|
||||
pos += len(word) + 1
|
||||
|
||||
return bboxes
|
||||
|
||||
@staticmethod
|
||||
def _parse_ocr_kwargs(ocr_kwargs: dict) -> Tuple[dict, float]:
|
||||
"""Parse the OCR-related kwargs.
|
||||
|
||||
:param ocr_kwargs: Parameters for OCR operations.
|
||||
|
||||
:return: Params for ocr.perform_ocr and ocr_threshold
|
||||
"""
|
||||
ocr_threshold = None
|
||||
if ocr_kwargs is not None:
|
||||
if "ocr_threshold" in ocr_kwargs:
|
||||
ocr_threshold = ocr_kwargs["ocr_threshold"]
|
||||
ocr_kwargs = {
|
||||
key: value
|
||||
for key, value in ocr_kwargs.items()
|
||||
if key != "ocr_threshold"
|
||||
}
|
||||
else:
|
||||
ocr_kwargs = {}
|
||||
|
||||
return ocr_kwargs, ocr_threshold
|
||||
|
||||
@@ -24,20 +24,27 @@ class ImagePiiVerifyEngine:
|
||||
image_analyzer_engine = ImageAnalyzerEngine()
|
||||
self.image_analyzer_engine = image_analyzer_engine
|
||||
|
||||
def verify(self, image: Image, **kwargs) -> Image:
|
||||
def verify(
|
||||
self, image: Image, ocr_kwargs: Optional[dict] = None, **text_analyzer_kwargs
|
||||
) -> Image:
|
||||
"""Annotate image with the detect PII entity.
|
||||
|
||||
Please notice, this method duplicates the image, creates a
|
||||
new instance and manipulate it.
|
||||
|
||||
:param image: PIL Image to be processed
|
||||
:param kwargs: Additional values for the analyze method in ImageAnalyzerEngine.
|
||||
:param image: PIL Image to be processed.
|
||||
:param ocr_kwargs: Additional params for OCR methods.
|
||||
:param text_analyzer_kwargs: Additional values for the analyze method
|
||||
in ImageAnalyzerEngine.
|
||||
|
||||
:return: the annotated image
|
||||
"""
|
||||
|
||||
image = ImageChops.duplicate(image)
|
||||
image_x, image_y = image.size
|
||||
bboxes = self.image_analyzer_engine.analyze(image, **kwargs)
|
||||
bboxes = self.image_analyzer_engine.analyze(
|
||||
image, ocr_kwargs, **text_analyzer_kwargs
|
||||
)
|
||||
fig, ax = plt.subplots()
|
||||
image_r = 70
|
||||
fig.set_size_inches(image_x / image_r, image_y / image_r)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Union, Tuple
|
||||
from typing import Union, Tuple, Optional
|
||||
|
||||
from PIL import Image, ImageDraw, ImageChops
|
||||
|
||||
@@ -18,25 +18,31 @@ class ImageRedactorEngine:
|
||||
self.image_analyzer_engine = image_analyzer_engine
|
||||
|
||||
def redact(
|
||||
self, image: Image,
|
||||
self,
|
||||
image: Image,
|
||||
fill: Union[int, Tuple[int, int, int]] = (0, 0, 0),
|
||||
**kwargs,
|
||||
ocr_kwargs: Optional[dict] = None,
|
||||
**text_analyzer_kwargs,
|
||||
) -> Image:
|
||||
"""Redact method to redact the given image.
|
||||
|
||||
Please notice, this method duplicates the image, creates a new instance and
|
||||
manipulate it.
|
||||
:param image: PIL Image to be processed
|
||||
:param image: PIL Image to be processed.
|
||||
:param fill: colour to fill the shape - int (0-255) for
|
||||
grayscale or Tuple(R, G, B) for RGB
|
||||
:param kwargs: Additional values for the analyze method in AnalyzerEngine
|
||||
grayscale or Tuple(R, G, B) for RGB.
|
||||
:param ocr_kwargs: Additional params for OCR methods.
|
||||
:param text_analyzer_kwargs: Additional values for the analyze method
|
||||
in AnalyzerEngine.
|
||||
|
||||
:return: the redacted image
|
||||
"""
|
||||
|
||||
image = ImageChops.duplicate(image)
|
||||
|
||||
bboxes = self.image_analyzer_engine.analyze(image, **kwargs)
|
||||
bboxes = self.image_analyzer_engine.analyze(
|
||||
image, ocr_kwargs, **text_analyzer_kwargs
|
||||
)
|
||||
draw = ImageDraw.Draw(image)
|
||||
|
||||
for box in bboxes:
|
||||
|
||||
@@ -5,10 +5,11 @@ class OCR(ABC):
|
||||
"""OCR class that performs OCR on a given image."""
|
||||
|
||||
@abstractmethod
|
||||
def perform_ocr(self, image: object) -> dict:
|
||||
def perform_ocr(self, image: object, **kwargs) -> dict:
|
||||
"""Perform OCR on a given image.
|
||||
|
||||
:param image: PIL Image/numpy array or file path(str) to be processed
|
||||
:param kwargs: Additional values for perform OCR method
|
||||
|
||||
:return: results dictionary containing bboxes and text for each detected word
|
||||
"""
|
||||
|
||||
@@ -6,12 +6,13 @@ from presidio_image_redactor import OCR
|
||||
class TesseractOCR(OCR):
|
||||
"""OCR class that performs OCR on a given image."""
|
||||
|
||||
def perform_ocr(self, image: object) -> dict:
|
||||
def perform_ocr(self, image: object, **kwargs) -> dict:
|
||||
"""Perform OCR on a given image.
|
||||
|
||||
:param image: PIL Image/numpy array or file path(str) to be processed
|
||||
:param kwargs: Additional values for OCR image_to_data
|
||||
|
||||
:return: results dictionary containing bboxes and text for each detected word
|
||||
"""
|
||||
output_type = pytesseract.Output.DICT
|
||||
return pytesseract.image_to_data(image, output_type=output_type)
|
||||
return pytesseract.image_to_data(image, output_type=output_type, **kwargs)
|
||||
|
||||
@@ -90,7 +90,7 @@ def test_get_all_dcm_files_happy_path(
|
||||
print(expected_list)
|
||||
|
||||
# Assert
|
||||
assert test_files == expected_list
|
||||
assert set(test_files) == set(expected_list)
|
||||
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
@@ -113,3 +113,38 @@ def test_given_dif_len_entities_then_map_analyzer_returns_correct_outputand_len(
|
||||
|
||||
assert len(expected_result) == len(mapped_entities)
|
||||
assert expected_result == mapped_entities
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"ocr_threshold, expected_length",
|
||||
[(-1, 9), (50, 7), (80, 2), (100, 0)],
|
||||
)
|
||||
def test_threshold_ocr_result_returns_expected_results(
|
||||
image_analyzer_engine, ocr_threshold, expected_length
|
||||
):
|
||||
# Assign
|
||||
ocr_result = {}
|
||||
ocr_result["text"] = [
|
||||
"",
|
||||
"Homey",
|
||||
"Interiors",
|
||||
"was",
|
||||
"created",
|
||||
"by",
|
||||
"Katie",
|
||||
"",
|
||||
"Cromley.",
|
||||
]
|
||||
ocr_result["left"] = [143, 143, 322, 530, 634, 827, 896, 141, 141]
|
||||
ocr_result["top"] = [64, 67, 67, 76, 64, 64, 64, 134, 134]
|
||||
ocr_result["width"] = [936, 160, 191, 87, 172, 51, 183, 801, 190]
|
||||
ocr_result["height"] = [50, 47, 37, 28, 40, 50, 40, 50, 50]
|
||||
ocr_result["conf"] = [-1, 99.5, 92.3, 42.7, 66.1, 51.2, 79.7, 64.0, 70.3]
|
||||
|
||||
# Act
|
||||
test_filtered = image_analyzer_engine.threshold_ocr_result(
|
||||
ocr_result, ocr_threshold
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert len(test_filtered["conf"]) == expected_length
|
||||
|
||||
Reference in New Issue
Block a user