Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding QR codes support in the ImageRedactorEngine #1036

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

### Added
#### Image redactor
* Added abstract class `QRRecognizer` for QR code recognizers
* Added `OpenCVQRRecongnizer` which uses OpenCV to recognize QR codes
* Added `QRImageAnalyzerEngine` which uses `QRRecognizer` for QR code recognition and `AnalyzerEngine` to analyze its contents for PII entities

### Changed
#### Image redactor
* Modified `ImagePiiVerifyEngine` and `ImageRedactorEngine` to allow using `QRImageAnalyzerEngine` as an alternative to `ImageAnalyzerEngine`

## [2.2.32] - 25.01.2023
### Changed
#### General
Expand Down
Binary file added docs/assets/qr-image-redactor-design.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion presidio-image-redactor/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ ENV PIP_NO_CACHE_DIR=1
WORKDIR /usr/bin/${NAME}

RUN apt-get update \
&& apt-get install tesseract-ocr -y \
&& apt-get install tesseract-ocr ffmpeg libsm6 libxext6 -y \
&& rm -rf /var/lib/apt/lists/* \
&& tesseract -v

Expand Down
2 changes: 2 additions & 0 deletions presidio-image-redactor/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ pydicom = ">=2.3.0"
pypng = ">=0.20220715.0"
matplotlib = "==3.6.2"
typing-extensions = "*"
opencv-python = ">=4.5.0"
omri374 marked this conversation as resolved.
Show resolved Hide resolved
importlib-resources = "*"

[dev-packages]
pytest = "*"
Expand Down
25 changes: 24 additions & 1 deletion presidio-image-redactor/Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions presidio-image-redactor/README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ Process for standard images:

![Image Redactor Design](../docs/assets/image-redactor-design.png)

Process for images with QR codes:

![QRImage Redactor Design](../docs/assets/qr-image-redactor-design.png)

Process for DICOM files:

![DICOM image Redactor Design](../docs/assets/dicom-image-redactor-design.png)
Expand Down Expand Up @@ -117,6 +121,30 @@ curl -XPOST "http://localhost:3000/redact" -H "content-type: multipart/form-data
Python script example can be found under:
/presidio/e2e-tests/tests/test_image_redactor.py

## Getting started (images with QR codes)

`QRImageAnalyzerEngine` is used by `ImageRedactorEngineto` to redact QR codes.

```python
from PIL import Image
from presidio_image_redactor import ImageRedactorEngine
from presidio_image_redactor import QRImageAnalyzerEngine

# Get the image to redact using PIL lib (pillow)
image = Image.open("presidio-image-redactor/tests/integration/resources/qr.png")

# Initialize the engine
engine = ImageRedactorEngine(image_analyzer_engine=QRImageAnalyzerEngine())

# Redact the image with pink color
redacted_image = engine.redact(image, (255, 192, 203))

# save the redacted image
redacted_image.save("new_image.png")
# uncomment to open the image for viewing
# redacted_image.show()
```

## Getting started (DICOM images)

This module only redacts pixel data and does not scrub text PHI which may exist in the DICOM metadata.
Expand Down
3 changes: 3 additions & 0 deletions presidio-image-redactor/presidio_image_redactor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
from .tesseract_ocr import TesseractOCR
from .bbox import BboxProcessor
from .image_analyzer_engine import ImageAnalyzerEngine
from .qr_image_analyzer_engine import QRImageAnalyzerEngine
from .image_redactor_engine import ImageRedactorEngine
from .image_pii_verify_engine import ImagePiiVerifyEngine
from .dicom_image_redactor_engine import DicomImageRedactorEngine
from .dicom_image_pii_verify_engine import DicomImagePiiVerifyEngine


# Set up default logging (with NullHandler)
logging.getLogger("presidio-image-redactor").addHandler(logging.NullHandler())

Expand All @@ -18,6 +20,7 @@
"TesseractOCR",
"BboxProcessor",
"ImageAnalyzerEngine",
"QRImageAnalyzerEngine",
"ImageRedactorEngine",
"ImagePiiVerifyEngine",
"DicomImageRedactorEngine",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from PIL import Image, ImageChops
from presidio_image_redactor.image_analyzer_engine import ImageAnalyzerEngine
from presidio_image_redactor import QRImageAnalyzerEngine
import matplotlib
import io
from matplotlib import pyplot as plt
from typing import Optional
from typing import Optional, Union


def fig2img(fig):
Expand All @@ -19,7 +20,10 @@ def fig2img(fig):
class ImagePiiVerifyEngine:
"""ImagePiiVerifyEngine class only supporting Pii verification currently."""

def __init__(self, image_analyzer_engine: Optional[ImageAnalyzerEngine] = None):
def __init__(
self,
image_analyzer_engine: Union[ImageAnalyzerEngine, QRImageAnalyzerEngine] = None,
):
if not image_analyzer_engine:
image_analyzer_engine = ImageAnalyzerEngine()
self.image_analyzer_engine = image_analyzer_engine
Expand All @@ -42,9 +46,12 @@ def verify(

image = ImageChops.duplicate(image)
image_x, image_y = image.size
bboxes = self.image_analyzer_engine.analyze(
image, ocr_kwargs, **text_analyzer_kwargs
)
if isinstance(self.image_analyzer_engine, QRImageAnalyzerEngine):
bboxes = self.image_analyzer_engine.analyze(image, **text_analyzer_kwargs)
else:
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

from PIL import Image, ImageDraw, ImageChops

from presidio_image_redactor import ImageAnalyzerEngine, BboxProcessor
from presidio_image_redactor import (
ImageAnalyzerEngine,
QRImageAnalyzerEngine,
BboxProcessor,
)


class ImageRedactorEngine:
Expand All @@ -11,7 +15,10 @@ class ImageRedactorEngine:
:param image_analyzer_engine: Engine which performs OCR + PII detection.
"""

def __init__(self, image_analyzer_engine: ImageAnalyzerEngine = None):
def __init__(
self,
image_analyzer_engine: Union[ImageAnalyzerEngine, QRImageAnalyzerEngine] = None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If QRImageAnalyzerEngine inherits from ImageAnalyzerEngine, then this class could be independent of the QR implementation

):
if not image_analyzer_engine:
self.image_analyzer_engine = ImageAnalyzerEngine()
else:
Expand Down Expand Up @@ -42,9 +49,12 @@ def redact(

image = ImageChops.duplicate(image)

bboxes = self.image_analyzer_engine.analyze(
image, ocr_kwargs, **text_analyzer_kwargs
)
if isinstance(self.image_analyzer_engine, QRImageAnalyzerEngine):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@omri374 @vpvpvpvp Any idea on making it more open-close?
Maybe a single ImageAnalyzerEngine that we inherit from with optional **ocr_kwargs?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case of direct inheritance of QRImageAnalyzerEngine from ImageAnalyzerEngine, it would only need to add ocr_kwars to the analyze method of QRImageAnalyzerEngine. This is probably the easiest way.

Potentially, it seems like the most optimal implementation when ImageAnalyzerEngine is used for orchestrating different recognizers (ocr recognizer, QR recognizer, etc.). In the vein of what was suggested earlier #1036 (comment).

bboxes = self.image_analyzer_engine.analyze(image, **text_analyzer_kwargs)
else:
bboxes = self.image_analyzer_engine.analyze(
image, ocr_kwargs, **text_analyzer_kwargs
)
draw = ImageDraw.Draw(image)

for box in bboxes:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from typing import List, Optional

from presidio_analyzer import AnalyzerEngine

from presidio_image_redactor.entities import ImageRecognizerResult
from presidio_image_redactor.qr_recognizer import QRRecognizer
from presidio_image_redactor.qr_recognizer import OpenCVQRRecongnizer


class QRImageAnalyzerEngine:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this class be inherited from ImageAnalyzerEngine? Just a question, to see if we can simplify the design instead of extending it to a new set of independent classes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was thinking exactly the same, see below.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it can be inherited from ImageAnalyzerEngine. My concern is that in this case, QRImageAnalyzerEngine will also inherit the logic of working with ocr tools not related to QR code recognition.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's my concern too. As the package is still in beta, we should (carefully) consider breaking backward compatibility. We'll do some thinking on this and get back to you. We can also have a quick design session together over video if you're interested.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that sounds interesting. If you have time, we could do that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. To avoid putting personal emails on GH, could you please email presidio@microsoft.com and we'll continue the discussion over email?

"""QRImageAnalyzerEngine class.

:param analyzer_engine: The Presidio AnalyzerEngine instance
to be used to detect PII in text
:param qr: the QRRecognizer object to detect and decode text in QR codes
"""

def __init__(
self,
analyzer_engine: Optional[AnalyzerEngine] = None,
qr: Optional[QRRecognizer] = None,
):
if not analyzer_engine:
analyzer_engine = AnalyzerEngine()
self.analyzer_engine = analyzer_engine

if not qr:
qr = OpenCVQRRecongnizer()
self.qr = qr

def analyze(
self, image: object, **text_analyzer_kwargs
) -> List[ImageRecognizerResult]:
"""Analyse method to analyse the given image.

:param image: PIL Image/numpy array to be processed.
:param text_analyzer_kwargs: Additional values for the analyze method
in AnalyzerEngine.

:return: List of the extract entities with image bounding boxes.
"""
bboxes = []

qr_result = self.qr.recognize(image)
for qr_code in qr_result:
analyzer_result = self.analyzer_engine.analyze(
text=qr_code.text, language="en", **text_analyzer_kwargs
)
for res in analyzer_result:
bboxes.append(
ImageRecognizerResult(
res.entity_type,
res.start,
res.end,
res.score,
qr_code.bbox[0],
qr_code.bbox[1],
qr_code.bbox[2],
qr_code.bbox[3],
)
)
return bboxes
Loading