Subject: [PR] Adding Rekognition compare_faces, detect_labels and detect_text, Textract detect_document_text (#7161)

This commit is contained in:
akoshel 2023-12-30 17:24:42 +03:00 committed by GitHub
parent ba73f64e08
commit ba27f02225
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 539 additions and 0 deletions

View File

@ -46,6 +46,30 @@ class RekognitionBackend(BaseBackend):
self._text_model_version(),
)
def compare_faces(
self,
) -> Tuple[List[Dict[str, Any]], str, str, List[Dict[str, Any]], Dict[str, Any],]:
return (
self._face_matches(),
"ROTATE_90",
"ROTATE_90",
self._unmatched_faces(),
self.source_image_face(),
)
def detect_labels(self) -> Tuple[List[Dict[str, Any]], Dict[str, Any], str]:
return (
self._mobile_phone_label(),
self._image_properties(),
"3.0",
)
def detect_text(self) -> Tuple[List[Dict[str, Any]], str]:
return (
self._detect_text_text_detections(),
"3.0",
)
# private
def _job_id(self) -> str:
@ -339,5 +363,391 @@ class RekognitionBackend(BaseBackend):
},
]
def _face_matches(self) -> List[Dict[str, Any]]:
return [
{
"Face": {
"BoundingBox": {
"Width": 0.5521978139877319,
"Top": 0.1203877404332161,
"Left": 0.23626373708248138,
"Height": 0.3126954436302185,
},
"Confidence": 99.98751068115234,
"Pose": {
"Yaw": -82.36799621582031,
"Roll": -62.13221740722656,
"Pitch": 0.8652129173278809,
},
"Quality": {
"Sharpness": 99.99880981445312,
"Brightness": 54.49755096435547,
},
"Landmarks": [
{
"Y": 0.2996366024017334,
"X": 0.41685718297958374,
"Type": "eyeLeft",
},
{
"Y": 0.2658946216106415,
"X": 0.4414493441581726,
"Type": "eyeRight",
},
{
"Y": 0.3465650677680969,
"X": 0.48636093735694885,
"Type": "nose",
},
{
"Y": 0.30935320258140564,
"X": 0.6251809000968933,
"Type": "mouthLeft",
},
{
"Y": 0.26942989230155945,
"X": 0.6454493403434753,
"Type": "mouthRight",
},
],
},
"Similarity": 100.0,
}
]
def _unmatched_faces(self) -> List[Dict[str, Any]]:
return [
{
"BoundingBox": {
"Width": 0.4890109896659851,
"Top": 0.6566604375839233,
"Left": 0.10989011079072952,
"Height": 0.278298944234848,
},
"Confidence": 99.99992370605469,
"Pose": {
"Yaw": 51.51519012451172,
"Roll": -110.32493591308594,
"Pitch": -2.322134017944336,
},
"Quality": {
"Sharpness": 99.99671173095703,
"Brightness": 57.23163986206055,
},
"Landmarks": [
{
"Y": 0.8288310766220093,
"X": 0.3133862614631653,
"Type": "eyeLeft",
},
{
"Y": 0.7632885575294495,
"X": 0.28091415762901306,
"Type": "eyeRight",
},
{"Y": 0.7417283654212952, "X": 0.3631140887737274, "Type": "nose"},
{
"Y": 0.8081989884376526,
"X": 0.48565614223480225,
"Type": "mouthLeft",
},
{
"Y": 0.7548204660415649,
"X": 0.46090251207351685,
"Type": "mouthRight",
},
],
}
]
def source_image_face(self) -> Dict[str, Any]:
return {
"BoundingBox": {
"Width": 0.5521978139877319,
"Top": 0.1203877404332161,
"Left": 0.23626373708248138,
"Height": 0.3126954436302185,
},
"Confidence": 99.98751068115234,
}
def _mobile_phone_label(self) -> List[Dict[str, Any]]:
return [
{
"Name": "Mobile Phone",
"Parents": [{"Name": "Phone"}],
"Aliases": [{"Name": "Cell Phone"}],
"Categories": [{"Name": "Technology and Computing"}],
"Confidence": 99.9364013671875,
"Instances": [
{
"BoundingBox": {
"Width": 0.26779675483703613,
"Height": 0.8562285900115967,
"Left": 0.3604024350643158,
"Top": 0.09245597571134567,
},
"Confidence": 99.9364013671875,
"DominantColors": [
{
"Red": 120,
"Green": 137,
"Blue": 132,
"HexCode": "3A7432",
"SimplifiedColor": "red",
"CssColor": "fuscia",
"PixelPercentage": 40.10,
}
],
}
],
}
]
def _image_properties(self) -> Dict[str, Any]:
return {
"ImageProperties": {
"Quality": {
"Brightness": 40,
"Sharpness": 40,
"Contrast": 24,
},
"DominantColors": [
{
"Red": 120,
"Green": 137,
"Blue": 132,
"HexCode": "3A7432",
"SimplifiedColor": "red",
"CssColor": "fuscia",
"PixelPercentage": 40.10,
}
],
"Foreground": {
"Quality": {
"Brightness": 40,
"Sharpness": 40,
},
"DominantColors": [
{
"Red": 200,
"Green": 137,
"Blue": 132,
"HexCode": "3A7432",
"CSSColor": "",
"SimplifiedColor": "red",
"PixelPercentage": 30.70,
}
],
},
"Background": {
"Quality": {
"Brightness": 40,
"Sharpness": 40,
},
"DominantColors": [
{
"Red": 200,
"Green": 137,
"Blue": 132,
"HexCode": "3A7432",
"CSSColor": "",
"SimplifiedColor": "Red",
"PixelPercentage": 10.20,
}
],
},
}
}
def _detect_text_text_detections(self) -> List[Dict[str, Any]]:
return [
{
"Confidence": 99.35693359375,
"DetectedText": "IT'S",
"Geometry": {
"BoundingBox": {
"Height": 0.09988046437501907,
"Left": 0.6684935688972473,
"Top": 0.18226495385169983,
"Width": 0.1461552083492279,
},
"Polygon": [
{"X": 0.6684935688972473, "Y": 0.1838926374912262},
{"X": 0.8141663074493408, "Y": 0.18226495385169983},
{"X": 0.8146487474441528, "Y": 0.28051772713661194},
{"X": 0.6689760088920593, "Y": 0.2821454107761383},
],
},
"Id": 0,
"Type": "LINE",
},
{
"Confidence": 99.6207275390625,
"DetectedText": "MONDAY",
"Geometry": {
"BoundingBox": {
"Height": 0.11442459374666214,
"Left": 0.5566731691360474,
"Top": 0.3525116443634033,
"Width": 0.39574965834617615,
},
"Polygon": [
{"X": 0.5566731691360474, "Y": 0.353712260723114},
{"X": 0.9522717595100403, "Y": 0.3525116443634033},
{"X": 0.9524227976799011, "Y": 0.4657355844974518},
{"X": 0.5568241477012634, "Y": 0.46693623065948486},
],
},
"Id": 1,
"Type": "LINE",
},
{
"Confidence": 99.6160888671875,
"DetectedText": "but keep",
"Geometry": {
"BoundingBox": {
"Height": 0.08314694464206696,
"Left": 0.6398131847381592,
"Top": 0.5267938375473022,
"Width": 0.2021435648202896,
},
"Polygon": [
{"X": 0.640289306640625, "Y": 0.5267938375473022},
{"X": 0.8419567942619324, "Y": 0.5295097827911377},
{"X": 0.8414806723594666, "Y": 0.609940767288208},
{"X": 0.6398131847381592, "Y": 0.6072247624397278},
],
},
"Id": 2,
"Type": "LINE",
},
{
"Confidence": 88.95134735107422,
"DetectedText": "Smiling",
"Geometry": {
"BoundingBox": {
"Height": 0.4326171875,
"Left": 0.46289217472076416,
"Top": 0.5634765625,
"Width": 0.5371078252792358,
},
"Polygon": [
{"X": 0.46289217472076416, "Y": 0.5634765625},
{"X": 1.0, "Y": 0.5634765625},
{"X": 1.0, "Y": 0.99609375},
{"X": 0.46289217472076416, "Y": 0.99609375},
],
},
"Id": 3,
"Type": "LINE",
},
{
"Confidence": 99.35693359375,
"DetectedText": "IT'S",
"Geometry": {
"BoundingBox": {
"Height": 0.09988046437501907,
"Left": 0.6684935688972473,
"Top": 0.18226495385169983,
"Width": 0.1461552083492279,
},
"Polygon": [
{"X": 0.6684935688972473, "Y": 0.1838926374912262},
{"X": 0.8141663074493408, "Y": 0.18226495385169983},
{"X": 0.8146487474441528, "Y": 0.28051772713661194},
{"X": 0.6689760088920593, "Y": 0.2821454107761383},
],
},
"Id": 4,
"ParentId": 0,
"Type": "WORD",
},
{
"Confidence": 99.6207275390625,
"DetectedText": "MONDAY",
"Geometry": {
"BoundingBox": {
"Height": 0.11442466825246811,
"Left": 0.5566731691360474,
"Top": 0.35251158475875854,
"Width": 0.39574965834617615,
},
"Polygon": [
{"X": 0.5566731691360474, "Y": 0.3537122905254364},
{"X": 0.9522718787193298, "Y": 0.35251158475875854},
{"X": 0.9524227976799011, "Y": 0.4657355546951294},
{"X": 0.5568241477012634, "Y": 0.46693626046180725},
],
},
"Id": 5,
"ParentId": 1,
"Type": "WORD",
},
{
"Confidence": 99.96778869628906,
"DetectedText": "but",
"Geometry": {
"BoundingBox": {
"Height": 0.0625,
"Left": 0.6402802467346191,
"Top": 0.5283203125,
"Width": 0.08027780801057816,
},
"Polygon": [
{"X": 0.6402802467346191, "Y": 0.5283203125},
{"X": 0.7205580472946167, "Y": 0.5283203125},
{"X": 0.7205580472946167, "Y": 0.5908203125},
{"X": 0.6402802467346191, "Y": 0.5908203125},
],
},
"Id": 6,
"ParentId": 2,
"Type": "WORD",
},
{
"Confidence": 99.26438903808594,
"DetectedText": "keep",
"Geometry": {
"BoundingBox": {
"Height": 0.0818721204996109,
"Left": 0.7344760298728943,
"Top": 0.5280686020851135,
"Width": 0.10748066753149033,
},
"Polygon": [
{"X": 0.7349520921707153, "Y": 0.5280686020851135},
{"X": 0.8419566750526428, "Y": 0.5295097827911377},
{"X": 0.8414806127548218, "Y": 0.6099407076835632},
{"X": 0.7344760298728943, "Y": 0.6084995269775391},
],
},
"Id": 7,
"ParentId": 2,
"Type": "WORD",
},
{
"Confidence": 88.95134735107422,
"DetectedText": "Smiling",
"Geometry": {
"BoundingBox": {
"Height": 0.4326171875,
"Left": 0.46289217472076416,
"Top": 0.5634765625,
"Width": 0.5371078252792358,
},
"Polygon": [
{"X": 0.46289217472076416, "Y": 0.5634765625},
{"X": 1.0, "Y": 0.5634765625},
{"X": 1.0, "Y": 0.99609375},
{"X": 0.46289217472076416, "Y": 0.99609375},
],
},
"Id": 8,
"ParentId": 3,
"Type": "WORD",
},
]
rekognition_backends = BackendDict(RekognitionBackend, "rekognition")

View File

@ -58,6 +58,51 @@ class RekognitionResponse(BaseResponse):
)
)
def compare_faces(self) -> str:
(
face_matches,
source_image_orientation_correction,
target_image_orientation_correction,
unmatched_faces,
source_image_face,
) = self.rekognition_backend.compare_faces()
return json.dumps(
dict(
FaceMatches=face_matches,
SourceImageOrientationCorrection=source_image_orientation_correction,
TargetImageOrientationCorrection=target_image_orientation_correction,
UnmatchedFaces=unmatched_faces,
SourceImageFace=source_image_face,
)
)
def detect_labels(self) -> str:
(
labels,
image_properties,
label_model_version,
) = self.rekognition_backend.detect_labels()
return json.dumps(
dict(
Labels=labels,
ImageProperties=image_properties,
LabelModelVersion=label_model_version,
)
)
def detect_text(self) -> str:
(
text_detections,
text_model_version,
) = self.rekognition_backend.detect_text()
return json.dumps(
dict(
TextDetections=text_detections,
TextModelVersion=text_model_version,
)
)
def start_face_search(self) -> TYPE_RESPONSE:
headers = {"Content-Type": "application/x-amz-json-1.1"}
job_id = self.rekognition_backend.start_face_search()

View File

@ -42,6 +42,13 @@ class TextractBackend(BaseBackend):
raise InvalidJobIdException()
return job
def detect_document_text(self) -> Dict[str, Any]:
return {
"Blocks": TextractBackend.BLOCKS,
"DetectDocumentTextModelVersion": "1.0",
"DocumentMetadata": {"Pages": TextractBackend.PAGES},
}
def start_document_text_detection(self, document_location: str) -> str:
"""
The following parameters have not yet been implemented: ClientRequestToken, JobTag, NotificationChannel, OutputConfig, KmsKeyID

View File

@ -23,6 +23,10 @@ class TextractResponse(BaseResponse):
job = self.textract_backend.get_document_text_detection(job_id=job_id).to_dict()
return json.dumps(job)
def detect_document_text(self) -> str:
result = self.textract_backend.detect_document_text()
return json.dumps(result)
def start_document_text_detection(self) -> str:
params = json.loads(self.body)
document_location = params.get("DocumentLocation")

View File

@ -43,6 +43,48 @@ def test_start_text_detection():
assert "JobId" in resp
@mock_rekognition
def test_compare_faces():
client = boto3.client("rekognition", region_name="ap-southeast-1")
sourceimage = {
"S3Object": {"Bucket": "string", "Name": "string", "Version": "string"}
}
targetimage = {
"S3Object": {"Bucket": "string", "Name": "string", "Version": "string"}
}
resp = client.compare_faces(
SimilarityThreshold=80, SourceImage=sourceimage, TargetImage=targetimage
)
assert resp["ResponseMetadata"]["HTTPStatusCode"] == 200
assert "FaceMatches" in resp
@mock_rekognition
def test_detect_labels():
client = boto3.client("rekognition", region_name="ap-southeast-1")
resp = client.detect_labels(
Image={"S3Object": {"Bucket": "string", "Name": "name.jpg"}}, MaxLabels=10
)
assert resp["ResponseMetadata"]["HTTPStatusCode"] == 200
assert "Labels" in resp
@mock_rekognition
def test_detect_text():
client = boto3.client("rekognition", region_name="ap-southeast-1")
resp = client.detect_text(
Image={"S3Object": {"Bucket": "string", "Name": "name.jpg"}}
)
assert resp["ResponseMetadata"]["HTTPStatusCode"] == 200
assert "TextDetections" in resp
@mock_rekognition
def test_get_face_search():
client = boto3.client("rekognition", region_name="us-east-2")

View File

@ -23,6 +23,22 @@ def test_textract_start_text_detection():
assert isinstance(data["JobId"], str)
@mock_textract
def test_detect_document_text():
backend = server.create_backend_app("textract")
test_client = backend.test_client()
headers = {"X-Amz-Target": "X-Amz-Target=Textract.DetectDocumentText"}
request_body = {
"DocumentLocation": {
"S3Object": {"Bucket": "bucket", "Name": "name", "Version": "version"}
}
}
resp = test_client.post("/", headers=headers, json=request_body)
data = json.loads(resp.data.decode("utf-8"))
assert resp.status_code == 200
assert isinstance(data["Blocks"], list)
@mock_textract
def test_textract_start_text_detection_without_document_location():
backend = server.create_backend_app("textract")

View File

@ -84,3 +84,18 @@ def test_get_document_text_detection_without_document_location():
'Parameter validation failed:\nMissing required parameter in input: "DocumentLocation"'
in e.value.args
)
@mock_textract
def test_detect_document_text():
client = boto3.client("textract", region_name="us-east-1")
result = client.detect_document_text(
Document={
"S3Object": {
"Bucket": "bucket",
"Name": "name.jpg",
}
}
)
assert isinstance(result["Blocks"], list)
assert result["DetectDocumentTextModelVersion"] == "1.0"