2021-04-15 11:45:07 -07:00
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
2021-10-28 15:43:09 -07:00
2021-04-15 11:45:07 -07:00
"""
2021-10-28 15:43:09 -07:00
Purpose
2021-05-04 13:49:38 -07:00
Uses a trained Amazon Lookout for Vision model to detect anomalies
in an image. The image can be local or in an S3 bucket.
2021-04-15 11:45:07 -07:00
"""
import argparse
import logging
import os
2022-08-09 13:53:15 -07:00
import json
import imghdr
2021-04-15 11:45:07 -07:00
import boto3
from botocore . exceptions import ClientError
logger = logging . getLogger ( __name__ )
2021-10-28 15:43:09 -07:00
# snippet-start:[python.example_code.lookoutvision.DetectAnomalies]
2021-04-15 11:45:07 -07:00
class Inference :
"""
2021-10-28 15:43:09 -07:00
Shows how to detect anomalies in an image using a trained Lookout for Vision model.
2021-04-15 11:45:07 -07:00
"""
2021-10-28 15:43:09 -07:00
2021-04-15 11:45:07 -07:00
@staticmethod
def detect_anomalies ( lookoutvision_client , project_name , model_version , photo ) :
"""
2022-08-18 11:55:20 -07:00
Calls DetectAnomalies using the supplied project, model version, and image.
2022-08-09 13:53:15 -07:00
:param lookoutvision_client: A Lookout for Vision Boto3 client.
:param project: The project that contains the model that you want to use.
2021-05-04 13:49:38 -07:00
:param model_version: The version of the model that you want to use.
2022-08-09 13:53:15 -07:00
:param photo: The photo that you want to analyze.
:return: The DetectAnomalyResult object that contains the analysis results.
2021-04-15 11:45:07 -07:00
"""
2022-08-09 13:53:15 -07:00
image_type = imghdr . what ( photo )
if image_type == " jpeg " :
content_type = " image/jpeg "
elif image_type == " png " :
content_type = " image/png "
2021-10-28 15:43:09 -07:00
else :
2022-08-23 16:03:00 -07:00
logger . info ( " Image type not valid for %s " , photo )
2022-08-09 13:53:15 -07:00
raise ValueError (
2023-10-18 10:35:05 -07:00
f " File format not valid. Supply a jpeg or png format file: { photo } "
)
2022-08-09 13:53:15 -07:00
2022-08-23 16:03:00 -07:00
# Get images bytes for call to detect_anomalies.
2022-08-09 13:53:15 -07:00
with open ( photo , " rb " ) as image :
response = lookoutvision_client . detect_anomalies (
ProjectName = project_name ,
ContentType = content_type ,
Body = image . read ( ) ,
2023-10-18 10:35:05 -07:00
ModelVersion = model_version ,
)
2022-08-09 13:53:15 -07:00
2023-10-18 10:35:05 -07:00
return response [ " DetectAnomalyResult " ]
2021-04-15 11:45:07 -07:00
@staticmethod
2021-05-04 13:49:38 -07:00
def download_from_s3 ( s3_resource , photo ) :
2021-04-15 11:45:07 -07:00
"""
Downloads an image from an S3 bucket.
2021-10-28 15:43:09 -07:00
:param s3_resource: A Boto3 Amazon S3 resource.
:param photo: The Amazon S3 path of a photo to download.
2021-04-15 11:45:07 -07:00
return: The local path to the downloaded file.
"""
try :
bucket , key = photo . replace ( " s3:// " , " " ) . split ( " / " , 1 )
local_file = os . path . basename ( photo )
2022-08-09 13:53:15 -07:00
except ValueError :
2022-02-03 16:28:27 -08:00
logger . exception ( " Couldn ' t get S3 info for %s " , photo )
2021-10-28 15:43:09 -07:00
raise
2021-04-15 11:45:07 -07:00
try :
logger . info ( " Downloading %s " , photo )
2021-10-28 15:43:09 -07:00
s3_resource . Bucket ( bucket ) . download_file ( key , local_file )
except ClientError :
2021-04-15 11:45:07 -07:00
logger . exception ( " Couldn ' t download %s from S3. " , photo )
raise
return local_file
2022-08-09 13:53:15 -07:00
@staticmethod
def reject_on_classification ( image , prediction , confidence_limit ) :
"""
2023-10-18 10:35:05 -07:00
Returns True if the anomaly confidence is greater than or equal to
2022-08-18 11:55:20 -07:00
the supplied confidence limit.
2022-08-09 13:53:15 -07:00
:param image: The name of the image file that was analyzed.
2022-08-23 16:03:00 -07:00
:param prediction: The DetectAnomalyResult object returned from DetectAnomalies.
:param confidence_limit: The minimum acceptable confidence (float 0 - 1).
2022-08-09 13:53:15 -07:00
:return: True if the error condition indicates an anomaly, otherwise False.
"""
reject = False
logger . info ( " Checking classification for %s " , image )
2023-10-18 10:35:05 -07:00
if prediction [ " IsAnomalous " ] and prediction [ " Confidence " ] > = confidence_limit :
2022-08-09 13:53:15 -07:00
reject = True
2023-10-18 10:35:05 -07:00
reject_info = (
f " Rejected: Anomaly confidence ( { prediction [ ' Confidence ' ] : .2% } ) is greater "
f " than limit ( { confidence_limit : .2% } ) "
)
2022-08-09 13:53:15 -07:00
logger . info ( " %s " , reject_info )
if not reject :
logger . info ( " No anomalies found. " )
return reject
@staticmethod
2023-10-18 10:35:05 -07:00
def reject_on_anomaly_types (
image , prediction , confidence_limit , anomaly_types_limit
) :
2022-08-09 13:53:15 -07:00
"""
2022-08-23 16:03:00 -07:00
Checks if the number of anomaly types is greater than the anomaly types
2022-08-18 11:55:20 -07:00
limit and if the prediction confidence is greater than the confidence limit.
2022-08-09 13:53:15 -07:00
:param image: The name of the image file that was analyzed.
2022-08-23 16:03:00 -07:00
:param prediction: The DetectAnomalyResult object returned from DetectAnomalies.
:param confidence: The minimum acceptable confidence (float 0 - 1).
:param anomaly_types_limit: The maximum number of allowable anomaly types (int).
2022-08-09 13:53:15 -07:00
:return: True if the error condition indicates an anomaly, otherwise False.
"""
2023-10-18 10:35:05 -07:00
logger . info ( " Checking number of anomaly types for %s " , image )
2022-08-09 13:53:15 -07:00
reject = False
2023-10-18 10:35:05 -07:00
if prediction [ " IsAnomalous " ] and prediction [ " Confidence " ] > = confidence_limit :
anomaly_types = {
anomaly [ " Name " ]
for anomaly in prediction [ " Anomalies " ]
if anomaly [ " Name " ] != " background "
}
if len ( anomaly_types ) > anomaly_types_limit :
reject = True
reject_info = (
f " Rejected: Anomaly confidence ( { prediction [ ' Confidence ' ] : .2% } ) "
2022-08-18 11:55:20 -07:00
f " is greater than limit ( { confidence_limit : .2% } ) and "
f " the number of anomaly types ( { len ( anomaly_types ) - 1 } ) is "
2023-10-18 10:35:05 -07:00
f " greater than the limit ( { anomaly_types_limit } ) "
)
2022-08-18 11:55:20 -07:00
2023-10-18 10:35:05 -07:00
logger . info ( " %s " , reject_info )
2022-08-09 13:53:15 -07:00
if not reject :
logger . info ( " No anomalies found. " )
return reject
@staticmethod
2023-10-18 10:35:05 -07:00
def reject_on_coverage (
image , prediction , confidence_limit , anomaly_label , coverage_limit
) :
2022-08-09 13:53:15 -07:00
"""
2022-08-18 11:55:20 -07:00
Checks if the coverage area of an anomaly is greater than the coverage limit and if
the prediction confidence is greater than the confidence limit.
2022-08-09 13:53:15 -07:00
:param image: The name of the image file that was analyzed.
2022-08-23 16:03:00 -07:00
:param prediction: The DetectAnomalyResult object returned from DetectAnomalies.
2022-08-18 11:55:20 -07:00
:param confidence_limit: The minimum acceptable confidence (float 0-1).
2022-08-09 13:53:15 -07:00
:anomaly_label: The anomaly label for the type of anomaly that you want to check.
2022-08-18 11:55:20 -07:00
:coverage_limit: The maximum acceptable percentage coverage of an anomaly (float 0-1).
2022-08-09 13:53:15 -07:00
:return: True if the error condition indicates an anomaly, otherwise False.
"""
reject = False
logger . info ( " Checking coverage for %s " , image )
2023-10-18 10:35:05 -07:00
if prediction [ " IsAnomalous " ] and prediction [ " Confidence " ] > = confidence_limit :
for anomaly in prediction [ " Anomalies " ] :
if anomaly [ " Name " ] == anomaly_label and anomaly [ " PixelAnomaly " ] [
" TotalPercentageArea "
] > ( coverage_limit ) :
2022-08-09 13:53:15 -07:00
reject = True
2023-10-18 10:35:05 -07:00
reject_info = (
f " Rejected: Anomaly confidence ( { prediction [ ' Confidence ' ] : .2% } ) "
2022-08-18 11:55:20 -07:00
f " is greater than limit ( { confidence_limit : .2% } ) and { anomaly [ ' Name ' ] } "
f " coverage ( { anomaly [ ' PixelAnomaly ' ] [ ' TotalPercentageArea ' ] : .2% } ) "
2023-10-18 10:35:05 -07:00
f " is greater than limit ( { coverage_limit : .2% } ) "
)
2022-08-09 13:53:15 -07:00
logger . info ( " %s " , reject_info )
if not reject :
logger . info ( " No anomalies found. " )
return reject
@staticmethod
def analyze_image ( lookoutvision_client , image , config ) :
"""
Analyzes an image with an Amazon Lookout for Vision model. Also
runs a series of checks to determine if the contents of an image
should be rejected.
:param lookoutvision_client: A Lookout for Vision Boto3 client.
param image: A local image that you want to analyze.
param config: Configuration information for the model and reject
limits.
"""
2023-10-18 10:35:05 -07:00
project = config [ " project " ]
model_version = config [ " model_version " ]
confidence_limit = config [ " confidence_limit " ]
coverage_limit = config [ " coverage_limit " ]
anomaly_types_limit = config [ " anomaly_types_limit " ]
anomaly_label = config [ " anomaly_label " ]
2022-08-09 13:53:15 -07:00
# Get analysis results.
print ( f " Analyzing { image } . " )
prediction = Inference . detect_anomalies (
2023-10-18 10:35:05 -07:00
lookoutvision_client , project , model_version , image
)
2022-08-09 13:53:15 -07:00
anomalies = [ ]
2023-10-18 10:35:05 -07:00
reject = Inference . reject_on_classification ( image , prediction , confidence_limit )
2022-08-09 13:53:15 -07:00
if reject :
anomalies . append ( " Classification: An anomaly was found. " )
reject = Inference . reject_on_coverage (
2023-10-18 10:35:05 -07:00
image , prediction , confidence_limit , anomaly_label , coverage_limit
)
2022-08-09 13:53:15 -07:00
if reject :
anomalies . append ( " Coverage: Anomaly coverage too high. " )
reject = Inference . reject_on_anomaly_types (
2023-10-18 10:35:05 -07:00
image , prediction , confidence_limit , anomaly_types_limit
)
2022-08-09 13:53:15 -07:00
if reject :
2023-10-18 10:35:05 -07:00
anomalies . append ( " Anomaly type count: Too many anomaly types found. " )
2022-08-09 13:53:15 -07:00
print ( )
if len ( anomalies ) > 0 :
print ( f " Anomalies found in { image } " )
for anomaly in anomalies :
print ( f " { anomaly } " )
else :
print ( f " No anomalies found in { image } " )
2021-04-15 11:45:07 -07:00
def main ( ) :
"""
2021-10-28 15:43:09 -07:00
Detects anomalies in an image file.
2021-04-15 11:45:07 -07:00
"""
try :
2023-10-18 10:35:05 -07:00
logging . basicConfig ( level = logging . INFO , format = " %(levelname)s : %(message)s " )
2022-08-09 13:53:15 -07:00
parser = argparse . ArgumentParser (
2023-10-18 10:35:05 -07:00
description = " Find anomalies with Amazon Lookout for Vision. "
)
2021-10-28 15:43:09 -07:00
parser . add_argument (
" image " ,
help = " The file that you want to analyze. Supply a local file path or a "
2023-10-18 10:35:05 -07:00
" path to an S3 object. " ,
)
2022-08-09 13:53:15 -07:00
parser . add_argument (
2023-10-18 10:35:05 -07:00
" config " ,
help = (
" The configuration JSON file to use. "
" See https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/ "
" python/example_code/lookoutvision/README.md "
) ,
)
2022-08-09 13:53:15 -07:00
2021-04-15 11:45:07 -07:00
args = parser . parse_args ( )
2023-10-18 10:35:05 -07:00
session = boto3 . Session ( profile_name = " lookoutvision-access " )
2023-02-09 14:29:25 -08:00
lookoutvision_client = session . client ( " lookoutvision " )
2023-10-18 10:35:05 -07:00
s3_resource = session . resource ( " s3 " )
2022-08-09 13:53:15 -07:00
# Get configuration information.
2023-10-18 10:35:05 -07:00
with open ( args . config , encoding = " utf-8 " ) as config_file :
2022-08-09 13:53:15 -07:00
config = json . load ( config_file )
# Download image if located in S3 bucket.
2021-04-15 11:45:07 -07:00
if args . image . startswith ( " s3:// " ) :
2022-08-09 13:53:15 -07:00
image = Inference . download_from_s3 ( s3_resource , args . image )
2021-10-28 15:43:09 -07:00
else :
2022-08-09 13:53:15 -07:00
image = args . image
2021-04-15 11:45:07 -07:00
2022-08-09 13:53:15 -07:00
Inference . analyze_image ( lookoutvision_client , image , config )
2021-04-15 11:45:07 -07:00
2022-08-09 13:53:15 -07:00
# Delete image, if downloaded from S3 bucket.
2021-04-15 11:45:07 -07:00
if args . image . startswith ( " s3:// " ) :
2022-08-09 13:53:15 -07:00
os . remove ( image )
2021-04-15 11:45:07 -07:00
except ClientError as err :
2021-10-28 15:43:09 -07:00
print ( f " Service error: { err . response [ ' Error ' ] [ ' Message ' ] } " )
2021-04-15 11:45:07 -07:00
except FileNotFoundError as err :
2021-10-28 15:43:09 -07:00
print ( f " The supplied file couldn ' t be found: { err . filename } . " )
2021-04-15 11:45:07 -07:00
except ValueError as err :
2021-10-28 15:43:09 -07:00
print ( f " A value error occurred: { err } . " )
2021-04-15 11:45:07 -07:00
else :
2022-08-09 13:53:15 -07:00
print ( " \n Successfully completed analysis. " )
2021-04-15 11:45:07 -07:00
2021-10-28 15:43:09 -07:00
2021-04-15 11:45:07 -07:00
if __name__ == " __main__ " :
main ( )
2021-10-28 15:43:09 -07:00
# snippet-end:[python.example_code.lookoutvision.DetectAnomalies]