Presidio Image Redactor API Reference
ImageRedactorEngine class
ImageRedactorEngine performs OCR + PII detection + bounding box redaction.
:param image_analyzer_engine: Engine which performs OCR + PII detection.
Source code in presidio_image_redactor/image_redactor_engine.py
class ImageRedactorEngine:
"""ImageRedactorEngine performs OCR + PII detection + bounding box redaction.
:param image_analyzer_engine: Engine which performs OCR + PII detection.
"""
def __init__(self, image_analyzer_engine: ImageAnalyzerEngine = None):
if not image_analyzer_engine:
self.image_analyzer_engine = ImageAnalyzerEngine()
else:
self.image_analyzer_engine = image_analyzer_engine
self.bbox_processor = BboxProcessor()
def redact(
self,
image: Image,
fill: Union[int, Tuple[int, int, int]] = (0, 0, 0),
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 fill: colour to fill the shape - int (0-255) for
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, ocr_kwargs, **text_analyzer_kwargs
)
draw = ImageDraw.Draw(image)
for box in bboxes:
x0 = box.left
y0 = box.top
x1 = x0 + box.width
y1 = y0 + box.height
draw.rectangle([x0, y0, x1, y1], fill=fill)
return image
redact(self, image, fill=(0, 0, 0), ocr_kwargs=None, **text_analyzer_kwargs)
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 fill: colour to fill the shape - int (0-255) for 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
Source code in presidio_image_redactor/image_redactor_engine.py
def redact(
self,
image: Image,
fill: Union[int, Tuple[int, int, int]] = (0, 0, 0),
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 fill: colour to fill the shape - int (0-255) for
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, ocr_kwargs, **text_analyzer_kwargs
)
draw = ImageDraw.Draw(image)
for box in bboxes:
x0 = box.left
y0 = box.top
x1 = x0 + box.width
y1 = y0 + box.height
draw.rectangle([x0, y0, x1, y1], fill=fill)
return image
ImageAnalyzerEngine class
ImageAnalyzerEngine class.
:param analyzer_engine: The Presidio AnalyzerEngine instance to be used to detect PII in text :param ocr: the OCR object to be used to detect text in images.
Source code in presidio_image_redactor/image_analyzer_engine.py
class ImageAnalyzerEngine:
"""ImageAnalyzerEngine class.
:param analyzer_engine: The Presidio AnalyzerEngine instance
to be used to detect PII in text
:param ocr: the OCR object to be used to detect text in images.
"""
def __init__(
self,
analyzer_engine: Optional[AnalyzerEngine] = None,
ocr: Optional[OCR] = None,
):
if not analyzer_engine:
analyzer_engine = AnalyzerEngine()
self.analyzer_engine = analyzer_engine
if not ocr:
ocr = TesseractOCR()
self.ocr = ocr
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 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.
"""
# 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", **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
) -> List[ImageRecognizerResult]:
"""Map extracted PII entities to image bounding boxes.
Matching is based on the position of the recognized entity from analyzer
and word (in ocr dict) in the text.
:param text_analyzer_results: PII entities recognized by presidio analyzer
:param ocr_result: dict results with words and bboxes from OCR
:param text: text the results are based on
return: list of extracted entities with image bounding boxes
"""
if (not ocr_result) or (not text_analyzer_results):
return []
bboxes = []
proc_indexes = 0
indexes = len(text_analyzer_results)
pos = 0
iter_ocr = enumerate(ocr_result["text"])
for index, word in iter_ocr:
if not word:
pos += 1
else:
for element in text_analyzer_results:
text_element = text[element.start : element.end]
# check position and text of ocr word matches recognized entity
if (
max(pos, element.start) < min(element.end, pos + len(word))
) and ((text_element in word) or (word in text_element)):
bboxes.append(
ImageRecognizerResult(
element.entity_type,
element.start,
element.end,
element.score,
ocr_result["left"][index],
ocr_result["top"][index],
ocr_result["width"][index],
ocr_result["height"][index],
)
)
# add bounding boxes for all words in ocr dict
# contained within the text of recognized entity
# based on relative position in the full text
while pos + len(word) < element.end:
prev_word = word
index, word = next(iter_ocr)
if word:
bboxes.append(
ImageRecognizerResult(
element.entity_type,
element.start,
element.end,
element.score,
ocr_result["left"][index],
ocr_result["top"][index],
ocr_result["width"][index],
ocr_result["height"][index],
)
)
pos += len(prev_word) + 1
proc_indexes += 1
if proc_indexes == indexes:
break
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
analyze(self, image, ocr_kwargs=None, **text_analyzer_kwargs)
Analyse method to analyse the given image.
: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.
Source code in presidio_image_redactor/image_analyzer_engine.py
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 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.
"""
# 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", **text_analyzer_kwargs
)
bboxes = self.map_analyzer_results_to_bounding_boxes(
analyzer_result, ocr_result, text
)
return bboxes
map_analyzer_results_to_bounding_boxes(text_analyzer_results, ocr_result, text)
staticmethod
Map extracted PII entities to image bounding boxes.
Matching is based on the position of the recognized entity from analyzer and word (in ocr dict) in the text.
:param text_analyzer_results: PII entities recognized by presidio analyzer :param ocr_result: dict results with words and bboxes from OCR :param text: text the results are based on
return: list of extracted entities with image bounding boxes
Source code in presidio_image_redactor/image_analyzer_engine.py
@staticmethod
def map_analyzer_results_to_bounding_boxes(
text_analyzer_results: List[RecognizerResult], ocr_result: dict, text: str
) -> List[ImageRecognizerResult]:
"""Map extracted PII entities to image bounding boxes.
Matching is based on the position of the recognized entity from analyzer
and word (in ocr dict) in the text.
:param text_analyzer_results: PII entities recognized by presidio analyzer
:param ocr_result: dict results with words and bboxes from OCR
:param text: text the results are based on
return: list of extracted entities with image bounding boxes
"""
if (not ocr_result) or (not text_analyzer_results):
return []
bboxes = []
proc_indexes = 0
indexes = len(text_analyzer_results)
pos = 0
iter_ocr = enumerate(ocr_result["text"])
for index, word in iter_ocr:
if not word:
pos += 1
else:
for element in text_analyzer_results:
text_element = text[element.start : element.end]
# check position and text of ocr word matches recognized entity
if (
max(pos, element.start) < min(element.end, pos + len(word))
) and ((text_element in word) or (word in text_element)):
bboxes.append(
ImageRecognizerResult(
element.entity_type,
element.start,
element.end,
element.score,
ocr_result["left"][index],
ocr_result["top"][index],
ocr_result["width"][index],
ocr_result["height"][index],
)
)
# add bounding boxes for all words in ocr dict
# contained within the text of recognized entity
# based on relative position in the full text
while pos + len(word) < element.end:
prev_word = word
index, word = next(iter_ocr)
if word:
bboxes.append(
ImageRecognizerResult(
element.entity_type,
element.start,
element.end,
element.score,
ocr_result["left"][index],
ocr_result["top"][index],
ocr_result["width"][index],
ocr_result["height"][index],
)
)
pos += len(prev_word) + 1
proc_indexes += 1
if proc_indexes == indexes:
break
pos += len(word) + 1
return bboxes
threshold_ocr_result(ocr_result, ocr_threshold)
staticmethod
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.
Source code in presidio_image_redactor/image_analyzer_engine.py
@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