You can create an AWS Lambda function that detects personal protective equipment (PPE) in images located in an Amazon Simple Storage Service (Amazon S3) bucket. For example, assume you run the Lambda function and you have this image in an Amazon S3 bucket.
After you execute the Lambda function, it detects PPE information in the image using the Amazon Rekognition service and creates a record in an Amazon DynamoDB table, as shown in this illustration.
In addition, the Lambda function creates a list of all images with PPE and emails the list by using the Amazon Simple Email (Amazon SES) service, as shown in this illustration.
As an Amazon Web Services API developer, you can create a Lambda function by using the AWS Lambda Java runtime API. Lambda is a compute service that enables you to run code without provisioning or managing servers. You can create Lambda functions in various programming languages. For more information about AWS Lambda, see What is AWS Lambda.
This tutorial shows you how to use the AWS SDK for Java V2 API to invoke these AWS services:
- Amazon S3 service
- Amazon Rekognition service
- DynamoDB service
- Amazon Simple Email service
- Prerequisites.
- Create an AWS Identity and Access Management (IAM) role that is used to execute Lambda functions.
- Create an IntelliJ project.
- Add the POM dependencies to your project.
- Create a Lambda function by using the Lambda runtime API.
- Package the project that contains the Lambda function.
- Deploy the Lambda function.
To follow along with this tutorial, you need the following:
- An AWS Account with proper credentials.
- A Java IDE (for this tutorial, the IntelliJ IDE is used).
- Java 17 JDK.
- Maven 3.6 or higher.
- The AWS services included in this document are included in the AWS Free Tier.
- This code has not been tested in all AWS Regions. Some AWS services are available only in specific regions. For more information, see AWS Regional Services.
- Running this code might result in charges to your AWS account.
- Be sure to terminate all of the resources you create while going through this tutorial to ensure that you’re not charged.
Create an Amazon S3 bucket with 5-7 PPE images. These images are read by the Lambda function.
Create an Amazon DynamoDB table named Gear with a key named id. For information, see Create a table.
Create the following IAM role:
- lambda-support - Used to invoke Lamdba functions.
This tutorial uses the Amazon Rekognition, DynamoDB, Amazon SES, and Amazon S3 services. The lambda-support role has to have policies that enable it to invoke these services.
-
Open the AWS Management Console. When the page loads, enter IAM in the search box, and then choose IAM to open the IAM console.
-
In the navigation pane, choose Roles, and on the Roles page, choose Create Role.
-
Choose AWS service, and then choose Lambda.
-
Choose Permissions.
-
Search for AWSLambdaBasicExecutionRole.
-
Choose Next Tags.
-
Choose Review.
-
Name the role lambda-support.
-
Choose Create role.
-
Choose lambda-support to view the overview page.
-
Choose Attach Policies.
-
Search for AmazonRekognitionFullAccess, and then choose Attach policy.
-
Search for AmazonS3FullAccess, and then choose Attach policy.
-
Search for AmazonDynamoDBFullAccess, and then choose Attach policy.
-
Search for AmazonSESFullAccess, and then choose Attach policy. When you're done, you can see the permissions.
-
In the IntelliJ IDE, choose File, New, Project.
-
In the New Project dialog box, choose Maven, and then choose Next.
-
For GroupId, enter WorkflowPPE.
-
For ArtifactId, enter WorkflowPPE.
-
Choose Next.
-
Choose Finish.
At this point, you have a new project named WorkflowPPE.
Make sure that your project's pom.xml file looks like the POM file in this Github repository.
Use the AWS Lambda runtime Java API to create the Java class that defines the Lamdba function. In this example, there is one Java class for the Lambda function named PPEHandler and additional classes required for this use case. The following figure shows the Java classes in the project. Notice that all Java classes are located in a package named com.example.ppe.
Create these Java classes:
- AnalyzePhotos - uses the Amazon Rekognition API to analyze the images and detect PPE images.
- DynamoDBService - uses the Amazon DynamoDB API to insert PPE records in the DynamoDB table.
- Gear - defines a model that is used with the DynamoDB enhanced client.
- GearItem - defines a model that stores PPE information.
- PPEHandler - uses the Lambda Java run-time API and performs the use case described in this AWS tutorial. The executable application logic is in the handleRequest method.
- S3Service - uses the Amazon S3 API to perform S3 operations.
- SendEmail - uses the SES API to send email messages.
The following Java code represents the AnalyzePhotos class. This class uses the Amazon Rekognition API to analyze the images and detect PPE information.
package com.example.ppe;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.rekognition.RekognitionClient;
import software.amazon.awssdk.services.rekognition.model.Image;
import software.amazon.awssdk.services.rekognition.model.*;
import software.amazon.awssdk.services.rekognition.model.RekognitionException;
import java.util.ArrayList;
import java.util.List;
public class AnalyzePhotos {
// Returns a list of GearItem objects that contains PPE information.
public ArrayList<GearItem> detectLabels(byte[] bytes, String key) {
Region region = Region.US_EAST_2;
RekognitionClient rekClient = RekognitionClient.builder()
.region(region)
.build();
ArrayList<GearItem> gearList = new ArrayList<>();
try {
SdkBytes sourceBytes = SdkBytes.fromByteArray(bytes);
// Create an Image object for the source image.
Image souImage = Image.builder()
.bytes(sourceBytes)
.build();
ProtectiveEquipmentSummarizationAttributes summarizationAttributes = ProtectiveEquipmentSummarizationAttributes.builder()
.minConfidence(80F)
.requiredEquipmentTypesWithStrings("FACE_COVER", "HAND_COVER", "HEAD_COVER")
.build();
DetectProtectiveEquipmentRequest request = DetectProtectiveEquipmentRequest.builder()
.image(souImage)
.summarizationAttributes(summarizationAttributes)
.build();
DetectProtectiveEquipmentResponse result = rekClient.detectProtectiveEquipment(request);
List<ProtectiveEquipmentPerson> persons = result.persons();
// Create a GearItem object
GearItem gear;
for (ProtectiveEquipmentPerson person : persons) {
System.out.println("ID: " + person.id());
List<ProtectiveEquipmentBodyPart> bodyParts = person.bodyParts();
if (bodyParts.isEmpty()) {
System.out.println("\tNo body parts detected");
} else
for (ProtectiveEquipmentBodyPart bodyPart : bodyParts) {
List<EquipmentDetection> equipmentDetections = bodyPart.equipmentDetections();
if (equipmentDetections.isEmpty()) {
System.out.println("\t\tNo PPE Detected on " + bodyPart.name());
} else {
for (EquipmentDetection item : equipmentDetections) {
// Set the objcet here
gear = new GearItem();
gear.setKey(key);
String itemType = item.type().toString();
String confidence = item.confidence().toString();
String myDesc = "Item: " + item.type() + ". Confidence: " + item.confidence().toString();
String bodyPartDes = "Covers body part: "
+ item.coversBodyPart().value().toString() + ". Confidence: " + item.coversBodyPart().confidence().toString();
gear.setName(itemType);
gear.setConfidence(confidence);
gear.setItemDescription(myDesc);
gear.setBodyCoverDescription(bodyPartDes);
//push the object
gearList.add(gear);
}
}
}
}
if (gearList.isEmpty())
return null ;
else
return gearList;
} catch (RekognitionException e) {
System.out.println(e.getMessage());
System.exit(1);
}
return null;
}
}
The DynamoDBService class uses the AWS SDK for Java V2 DynamoDB API to add a record to the Gear table by using the enhanced client. For more information about the enhanced client, see Map items in DynamoDB tables.
package com.example.ppe;
import software.amazon.awssdk.enhanced.dynamodb.*;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.*;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class DynamoDBService {
private DynamoDbClient getClient() {
// Create a DynamoDbClient object.
Region region = Region.US_EAST_1;
DynamoDbClient ddb = DynamoDbClient.builder()
.region(region)
.build();
return ddb;
}
// Persist the PPE items into the Gear table.
public void persistItem(List<ArrayList<GearItem>> gearList) {
DynamoDbClient ddb = getClient();
try {
DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder()
.dynamoDbClient(ddb)
.build();
DynamoDbTable<Gear> gearTable = enhancedClient.table("Gear", TableSchema.fromBean(Gear.class));
Gear gearRecord;
// Create an Instant object.
LocalDateTime now = LocalDateTime.now(); // current date and time
LocalDateTime timeVal = now.toLocalDate().atStartOfDay();
Instant instant = timeVal.toInstant(ZoneOffset.UTC);
// Persist the data into a DynamoDB table.
for (Object o : gearList) {
//Need to get the WorkItem from each list.
List innerList = (List) o;
for (Object value : innerList) {
gearRecord = new Gear();
UUID uuid = UUID.randomUUID();
GearItem gearItem = (GearItem) value;
gearRecord.setId(uuid.toString());
gearRecord.setKey(gearItem.getKey());
gearRecord.setDate(instant.toString());
gearRecord.setItem(gearItem.getName());
gearRecord.setCoverDescription(gearItem.getBodyCoverDescription());
gearRecord.setItemDescription(gearItem.getItemDescription());
gearRecord.setConfidence(gearItem.getConfidence());
// Put the PPE data into a DynamoDB table.
gearTable.putItem(gearRecord);
}
}
} catch (DynamoDbException e) {
System.err.println(e.getMessage());
System.exit(1);
}
}
}
The Gear class is responsible for mapping an object to the Gear table using the enhanced client. Notice the use of the @DynamoDbBean annotation.
package com.example.ppe;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
@DynamoDbBean
public class Gear {
private String id;
private String date;
private String item ;
private String key;
private String itemDescription;
private String coverDescription ;
private String confidence ;
@DynamoDbPartitionKey
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
public void setKey(String key) {
this.key = key;
}
public String getKey() {
return this.key;
}
public void setDate(String date) {
this.date = date;
}
public String getDate() {
return this.date;
}
public String getItem() {
return this.item;
}
public void setItem(String item) {
this.item = item;
}
public String getItemDescription() {
return this.itemDescription;
}
public void setItemDescription(String itemDescription) {
this.itemDescription = itemDescription;
}
public String getCoverDescription() {
return this.coverDescription;
}
public void setCoverDescription(String coverDescription) {
this.coverDescription = coverDescription;
}
public String getConfidence() {
return this.confidence;
}
public void setConfidence(String confidence) {
this.confidence = confidence;
}
}
The GearIten class represents the model in this use case. Its stores data retrieved from the Amazon Rekognition service.
package com.example.ppe;
public class GearItem {
private String key;
private String name;
private String itemDescription;
private String bodyCoverDescription;
private String confidence ;
public void setItemDescription (String itemDescription) {
this.itemDescription = itemDescription;
}
public String getItemDescription() {
return this.itemDescription;
}
public void setBodyCoverDescription (String bodyCoverDescription) {
this.bodyCoverDescription = bodyCoverDescription;
}
public String getBodyCoverDescription() {
return this.bodyCoverDescription;
}
public void setKey (String key) {
this.key = key;
}
public String getKey() {
return this.key;
}
public void setName (String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setConfidence (String confidence) {
this.confidence = confidence;
}
public String getConfidence() {
return this.confidence;
}
}
This Java code represents the PPEHandler class. This class reads a value that specifies which Amazon S3 bucket to read the images from. The s3Service.ListBucketObjects method returns a List object where each element is a string value that represents the object key. For each image in the bucket, the s3Service.getObjectBytes method returns a byte array. Then an ArrayList is obtained by calling the photos.detectLabels method. Finally the ArrayList object is added to another collection and the data that specifies PPE gear is persisted in a DynamoDB table and emailed to a user.
The following Java code represents the PPEHandler class.
package com.example.ppe;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
public class PPEHandler implements RequestHandler<Map<String,String>, String> {
@Override
public String handleRequest(Map<String, String> event, Context context) {
LambdaLogger logger = context.getLogger();
String bucketName = event.get("bucketName");
logger.log("Bucket name is: " + bucketName);
S3Service s3Service = new S3Service() ;
DynamoDBService ddb = new DynamoDBService();
AnalyzePhotos photos = new AnalyzePhotos();
SendEmail email = new SendEmail();
List<String> items = s3Service.listBucketObjects(bucketName);
List<ArrayList<GearItem>> myList = new ArrayList<>();
for (String item : items) {
byte[] keyData = s3Service.getObjectBytes(bucketName, item);
// Analyze the photo and return a list where each element is a GearItem.
ArrayList<GearItem> gearItem = photos.detectLabels(keyData, item);
// Only add a list with items
if (gearItem != null)
myList.add(gearItem);
}
ddb.persistItem(myList);
// Create a new list with only unique keys to email.
Set<String> unqiueKeys = createUniqueList(myList);
email.sendMsg(unqiueKeys);
logger.log("Updated the DynamoDB table with PPE data");
return bucketName;
}
// Create a list of unique keys.
private static Set<String> createUniqueList(List<ArrayList<GearItem>> gearList) {
List<String> keys = new ArrayList<>();
// Persist the data into a DynamoDB table.
for (Object o : gearList) {
//Need to get the WorkItem from each list.
List innerList = (List) o;
for (Object value : innerList) {
GearItem gearItem = (GearItem) value;
keys.add(gearItem.getKey());
}
}
// Create list without duplicates.
Set<String> uniqueKeys = new HashSet<String>(keys);
return uniqueKeys;
}
}
The following class uses the Amazon S3 API to perform S3 operations. For example, the getObjectBytes method returns a byte array that represents the image. Likewise, the listBucketObjects method returns a List object where each element is a string value that specifies the key name.
package com.example.ppe;
import software.amazon.awssdk.core.ResponseBytes;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.services.s3.model.ListObjectsResponse;
import software.amazon.awssdk.services.s3.model.S3Object;
import software.amazon.awssdk.services.s3.model.ListObjectsRequest;
import java.util.ArrayList;
import java.util.List;
public class S3Service {
private S3Client getClient() {
Region region = Region.US_WEST_2;
return S3Client.builder()
.region(region)
.build();
}
public byte[] getObjectBytes(String bucketName, String keyName) {
S3Client s3 = getClient();
try {
GetObjectRequest objectRequest = GetObjectRequest
.builder()
.key(keyName)
.bucket(bucketName)
.build();
// Return the byte[] from this object.
ResponseBytes<GetObjectResponse> objectBytes = s3.getObjectAsBytes(objectRequest);
return objectBytes.asByteArray();
} catch (S3Exception e) {
System.err.println(e.awsErrorDetails().errorMessage());
System.exit(1);
}
return null;
}
// Returns the names of all images in the given bucket.
public List<String> listBucketObjects(String bucketName) {
S3Client s3 = getClient();
String keyName;
List<String> keys = new ArrayList<>();
try {
ListObjectsRequest listObjects = ListObjectsRequest
.builder()
.bucket(bucketName)
.build();
ListObjectsResponse res = s3.listObjects(listObjects);
List<S3Object> objects = res.contents();
for (S3Object myValue: objects) {
keyName = myValue.key();
keys.add(keyName);
}
return keys;
} catch (S3Exception e) {
System.err.println(e.awsErrorDetails().errorMessage());
System.exit(1);
}
return null;
}
}
The following class uses the Amazon SES Java API to send email messages that specify which images contain PPE.
package com.example.ppe;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ses.SesClient;
import software.amazon.awssdk.services.ses.model.*;
import software.amazon.awssdk.services.ses.model.Message;
import software.amazon.awssdk.services.ses.model.Body;
import java.util.Set;
public class SendEmail {
public void sendMsg(Set<String> unqiueKeys) {
Region region = Region.US_EAST_1;
SesClient client = SesClient.builder()
.region(region)
.build();
String sender = "<Enter the sender email address>";
String recipient = "<Enter the recipient email address>";
// Set the HTML body.
String bodyHTML = "<html> <head></head> <body><p> The following images contains PPE gear " +
"<ol> ";
// Persist the data into a DynamoDB table.
for (String myKey : unqiueKeys) {
bodyHTML = bodyHTML + "<li> " + myKey + "</li>";
}
bodyHTML = bodyHTML + "</ol></p></body></html>" ;
Destination destination = Destination.builder()
.toAddresses(recipient)
.build();
Content content = Content.builder()
.data(bodyHTML)
.build();
Content sub = Content.builder()
.data("PPE Information")
.build();
Body body = Body.builder()
.html(content)
.build();
Message msg = Message.builder()
.subject(sub)
.body(body)
.build();
SendEmailRequest emailRequest = SendEmailRequest.builder()
.destination(destination)
.message(msg)
.source(sender)
.build();
try {
System.out.println("Attempting to send an email through Amazon SES " + "using the AWS SDK for Java...");
client.sendEmail(emailRequest);
} catch (SesException e) {
System.err.println(e.awsErrorDetails().errorMessage());
System.exit(1);
}
}
}
Note: Set email addresses for the sender and recipient variables.
Package up the project into a .jar (JAR) file that you can deploy as a Lambda function by using the following Maven command.
mvn package
The JAR file is located in the target folder (which is a child folder of the project folder).
Note: Notice the use of the maven-shade-plugin in the project’s POM file. This plugin is responsible for creating a JAR that contains the required dependencies. If you attempt to package up the project without this plugin, the required dependencies are not included in the JAR file and you will encounter a ClassNotFoundException.
-
Open the Lambda console at https://us-east-1.console.aws.amazon.com/lambda/home.
-
Choose Create Function.
-
Choose Author from scratch.
-
In the Basic information section, enter PPEGear as the name.
-
In the Runtime, choose Java 8.
-
Choose Use an existing role, and then choose lambda-support (the IAM role that you created).
-
Choose Create function.
-
For Code entry type, choose Upload a .zip or .jar file.
-
Choose Upload, and then browse to the JAR file that you created.
-
For Handler, enter the fully qualified name of the function, for example, com.example.ppe.PPEHandler:handleRequest (com.example.ppe specifies the package, PPEHandler is the class followed by :: and method name).
-
Choose Save.
At this point in the tutorial, you can test the Lambda function. Click the Test tab and then enter the following JSON.
{
"bucketName": "<Bucket name>"
}
Note: Be sure that you specify the name of the Amazon S3 bucket that contains the PPE images.
Choose the Invoke button. After the Lambda function is invoked, you see a successful message.
Note: You may have to set a longer timeout period for the Lambda function. For information, see Configuring functions in the console.
Congratulations, you have created an AWS Lambda function that detects PPE in images located in an Amazon S3 bucket. As stated at the beginning of this tutorial, be sure to terminate all of the resources you created while going through this tutorial to ensure that you’re not charged.
For more AWS multiservice examples, see usecases.