Welcome Back!!
We are spoiled with the available services across AWS and most of them we can play with for free or for a limited cost if you are willing to invest some money. I have been guilty of shying away from trying to cover more complex subjects because i struggle where there is any coding involved.
However with Amazon Q Developer this has really helped me with this element where I struggle. Now don't get me wrong Q Developer is not a silver bullet to all your problems and during the creation of this blog I came very close a few times to finding something else to write about because it wouldn't give me working solutions from the off.
A lesson I have learnt creating this blog is to break down what you are trying to achieve into its simplest forms to start with and then iterate as you go to get more advanced elements. It is easier to iterate on top of a simple working example to get to where you want to be.
This blog is going to explore a simple event driven architecture to analyse images uploaded to an S3 bucket using Amazon Rekogniton. Upon an image being uploaded to S3, EventBridge will trigger a Lambda function that calls the Rekognition API to analyse the Image and provide Labels that are at least 95% confidence rating, and then add these to the S3 Object as meta data.
The final architecture will look like this
Now as I mentioned earlier, I was a bit naive when I started this blog, I thought to myself this should be easy I just need an S3 Bucket, an EventBridge Trigger and a Lambda Function (plus some IAM and Bucket polices). So I configured an S3 Bucket, an EventBridge trigger, then I asked Q Developer to write me a lambda function that took the input from an EventBridge Event (Bucket Name, Object Name), started Amazon Recognition and analysed the image and passed labels back to Lambda to apply to the S3 Object as meta data.
Wow watch Q Developer go.... spitting out Python code and instructions... I was thinking i'll be done in no time at all... LOL!!! yeah thats not how this went, I implemented the lambda code and instructions from Q Developer and it failed, I spent a lot of time troubleshooting and got to the point where the Lambda would run but nothing really happened it just ran and ended... Too much time later I scrapped it all and went and did something else to let my brain reset.
Start from a working point!
So after a break I decided to start again, but this time I looked for an AWS tutorial that was similar to what I was trying to achieve and I found this one
https://docs.aws.amazon.com/rekognition/latest/dg/lambda-s3-tutorial-python.html
This tutorial takes you through setting up and S3 Bucket, a Lambda function to call the Rekognition detect Labels function and a Client Python code to provide the S3 bucket and object name and invoke the Lambda function and receive back all the labels Rekognition detected.
After the initial implementation, it didnt run, and the only reason was due to me not updating the name of the lambda function in my client code and I hadnt created an aws cli profile to allow the python code to run and login to my AWS account.. Once I corrected this BAM!!! it worked..
I used this picture of my Dog Cooper
and the process returned this extract of labels
Analyzing image in S3 bucket:
{'body': '{"Labels": [{"Name": "Soil", "Confidence": 99.9998779296875, '
'"Instances": [], "Parents": [], "Aliases": [], "Categories": '
'[{"Name": "Nature and Outdoors"}]}, {"Name": "Animal", "Confidence": '
'99.24742889404297, "Instances": [], "Parents": [], "Aliases": [], '
'"Categories": [{"Name": "Animals and Pets"}]}, {"Name": "Canine", '
'"Confidence": 99.24742889404297, "Instances": [], "Parents": '
'[{"Name": "Animal"}, {"Name": "Mammal"}], "Aliases": [], '
'"Categories": [{"Name": "Animals and Pets"}]}, {"Name": "Dog", '
'"Confidence": 99.24742889404297, "Instances": [{"BoundingBox": '
'{"Width": 0.23906654119491577, "Height": 0.12388644367456436, '
'"Left": 0.45508497953414917, "Top": 0.3805483877658844}, '
'"Confidence": 99.24742889404297}], "Parents": [{"Name": "Animal"}, '
'{"Name": "Canine"}, {"Name": "Mammal"}, {"Name": "Pet"}], "Aliases": '
'[], "Categories": [{"Name": "Animals and Pets"}]}, {"Name": '
'"Mammal", "Confidence": 99.24742889404297, "Instances": [], '
'"Parents": [{"Name": "Animal"}], "Aliases": [], "Categories": '
'[{"Name": "Animals and Pets"}]}, {"Name": "Pet", "Confidence": '
'99.24742889404297, "Instances": [], "Parents": [{"Name": "Animal"}], '
'"Aliases": [], "Categories": [{"Name": "Animals and Pets"}]}, '
'{"Name": "Puppy", "Confidence": 99.22718048095703, "Instances": [], '
'"Parents": [{"Name": "Animal"}, {"Name": "Canine"}, {"Name": "Dog"}, '
'{"Name": "Mammal"}, {"Name": "Pet"}], "Aliases": [], "Categories": '
'[{"Name": "Animals and Pets"}]}, {"Name": "Nature", "Confidence": '
'98.79219055175781, "Instances": [], "Parents": [{"Name": '
'"Outdoors"}], "Aliases": [], "Categories": [{"Name": "Nature and '
'Outdoors"}]}, {"Name": "Outdoors", "Confidence": 98.79219055175781, '
'"Instances": [], "Parents": [], "Aliases": [], "Categories": '
'[{"Name": "Nature and Outdoors"}]}, {"Name": "Sky", "Confidence": '
'98.79219055175781, "Instances": [], "Parents": [{"Name": "Nature"}, '
'{"Name": "Outdoors"}], "Aliases": [], "Categories": [{"Name": '
'"Nature and Outdoors"}]}, {"Name": "Rock", "Confidence": '
'98.52386474609375, "Instances": [], "Parents": [], "Aliases": [], '
'"Categories": [{"Name": "Nature and Outdoors"}]}, {"Name": "Field", '
'"Confidence": 98.21697998046875, "Instances": [], "Parents": [], '
'"Aliases": [], "Categories": [{"Name": "Nature and Outdoors"}]}, '
'{"Name": "Grassland", "Confidence": 98.21697998046875, "Instances": '
'[], "Parents": [{"Name": "Field"}, {"Name": "Nature"}, {"Name": '
'"Outdoors"}], "Aliases": [], "Categories": [{"Name": "Nature and '
'Outdoors"}]}, {"Name": "Land", "Confidence": 98.10195922851562, '
'"Instances": [], "Parents": [{"Name": "Nature"}, {"Name": '
'"Outdoors"}], "Aliases": [], "Categories": [{"Name": "Nature and '
'Outdoors"}]}, {"Name": "Person", "Confidence": 97.86608123779297, '
'"Instances": [{"BoundingBox": {"Width": 0.02859390340745449, '
'"Height": 0.04032939299941063, "Left": 0.22646482288837433, "Top": '
'0.15954364836215973}, "Confidence": 97.86608123779297}], "Parents": '
'[], "Aliases": [{"Name": "Human"}], "Categories": [{"Name": "Person '
'Description"}]}, {"Name": "Plant", "Confidence": 95.07865142822266, '
'"Instances": [], "Parents": [], "Aliases": [], "Categories": '
'[{"Name": "Plants and Flowers"}]}, {"Name": "Vegetation", '
'"Confidence": 95.07865142822266, "Instances": [], "Parents": '
'[{"Name": "Plant"}], "Aliases": [], "Categories": [{"Name": "Nature '
'and Outdoors"}]}, {"Name": "Grass", "Confidence": 94.17696380615234, '
'"Instances": [], "Parents": [{"Name": "Plant"}], "Aliases": [], '
'"Categories": [{"Name": "Plants and Flowers"}]}, {"Name": "Hound", '
'"Confidence": 92.6964111328125, "Instances": [], "Parents": '
'[{"Name": "Animal"}, {"Name": "Canine"}, {"Name": "Dog"}, {"Name": '
'"Mammal"}, {"Name": "Pet"}], "Aliases": [], "Categories": [{"Name": '
'"Animals and Pets"}]}, {"Name": "Ball", "Confidence": '
'83.73759460449219, "Instances": [], "Parents": [], "Aliases": [], '
'"Categories": [{"Name": "Sports"}]}, {"Name": "Sport", "Confidence": '
'83.73759460449219, "Instances": [], "Parents": [], "Aliases": '
'[{"Name": "Sports"}], "Categories": [{"Name": "Sports"}]}, {"Name": '
'"Tennis", "Confidence": 83.73759460449219, "Instances": [], '
'"Parents": [{"Name": "Sport"}], "Aliases": [], "Categories": '
'[{"Name": "Sports"}]}, {"Name": "Tennis Ball", "Confidence": '
'83.73759460449219, "Instances": [], "Parents": [{"Name": "Ball"}, '
'{"Name": "Sport"}, {"Name": "Tennis"}], "Aliases": [], "Categories": '
'[{"Name": "Sports"}]}, {"Name": "Road", "Confidence": '
'79.90119934082031, "Instances": [], "Parents": [], "Aliases": [], '
'"Categories": [{"Name": "Transport and Logistics"}]}, {"Name": '
'"Tree", "Confidence": 79.13050079345703, "Instances": [], "Parents": '
'[{"Name": "Plant"}], "Aliases": [], "Categories": [{"Name": "Nature '
'and Outdoors"}]}, {"Name": "Wilderness", "Confidence": '
'61.0156364440918, "Instances": [], "Parents": [{"Name": "Nature"}, '
'{"Name": "Outdoors"}], "Aliases": [], "Categories": [{"Name": '
'"Nature and Outdoors"}]}, {"Name": "Landscape", "Confidence": '
'57.71144104003906, "Instances": [], "Parents": [{"Name": "Nature"}, '
'{"Name": "Outdoors"}], "Aliases": [], "Categories": [{"Name": '
'"Nature and Outdoors"}]}, {"Name": "Ground", "Confidence": '
'57.36166763305664, "Instances": [], "Parents": [], "Aliases": [], '
'"Categories": [{"Name": "Nature and Outdoors"}]}, {"Name": "Ice", '
'"Confidence": 56.176082611083984, "Instances": [], "Parents": [], '
'"Aliases": [], "Categories": [{"Name": "Nature and Outdoors"}]}, '
'{"Name": "Cloud", "Confidence": 55.94695281982422, "Instances": [], '
'"Parents": [{"Name": "Nature"}, {"Name": "Outdoors"}, {"Name": '
'"Sky"}], "Aliases": [], "Categories": [{"Name": "Nature and '
'Outdoors"}]}, {"Name": "Gravel", "Confidence": 55.84575653076172, '
'"Instances": [], "Parents": [{"Name": "Road"}], "Aliases": [{"Name": '
'"Dirt Road"}], "Categories": [{"Name": "Materials"}]}, {"Name": '
'"Water", "Confidence": 55.7204475402832, "Instances": [], "Parents": '
'[], "Aliases": [], "Categories": [{"Name": "Nature and Outdoors"}]}, '
'{"Name": "Pebble", "Confidence": 55.63995361328125, "Instances": [], '
'"Parents": [], "Aliases": [], "Categories": [{"Name": "Nature and '
'Outdoors"}]}, {"Name": "Face", "Confidence": 55.62419128417969, '
'"Instances": [], "Parents": [{"Name": "Head"}, {"Name": "Person"}], '
'"Aliases": [], "Categories": [{"Name": "Person Description"}]}, '
'{"Name": "Head", "Confidence": 55.62419128417969, "Instances": [], '
'"Parents": [{"Name": "Person"}], "Aliases": [], "Categories": '
'[{"Name": "Person Description"}]}, {"Name": "Photography", '
'"Confidence": 55.62419128417969, "Instances": [], "Parents": [], '
'"Aliases": [{"Name": "Photo"}], "Categories": [{"Name": "Hobbies and '
'Interests"}]}, {"Name": "Portrait", "Confidence": 55.62419128417969, '
'"Instances": [], "Parents": [{"Name": "Face"}, {"Name": "Head"}, '
'{"Name": "Person"}, {"Name": "Photography"}], "Aliases": [], '
'"Categories": [{"Name": "Hobbies and Interests"}]}, {"Name": "Snow", '
'"Confidence": 55.35792541503906, "Instances": [], "Parents": '
'[{"Name": "Nature"}, {"Name": "Outdoors"}], "Aliases": [], '
'"Categories": [{"Name": "Nature and Outdoors"}]}, {"Name": '
'"Weather", "Confidence": 55.241214752197266, "Instances": [], '
'"Parents": [{"Name": "Nature"}, {"Name": "Outdoors"}], "Aliases": '
'[], "Categories": [{"Name": "Nature and Outdoors"}]}, {"Name": '
'"Grove", "Confidence": 55.16259002685547, "Instances": [], '
'"Parents": [{"Name": "Land"}, {"Name": "Nature"}, {"Name": '
'"Outdoors"}, {"Name": "Plant"}, {"Name": "Tree"}, {"Name": '
'"Vegetation"}, {"Name": "Woodland"}], "Aliases": [], "Categories": '
'[{"Name": "Plants and Flowers"}]}, {"Name": "Woodland", '
'"Confidence": 55.16259002685547, "Instances": [], "Parents": '
'[{"Name": "Land"}, {"Name": "Nature"}, {"Name": "Outdoors"}, '
'{"Name": "Plant"}, {"Name": "Tree"}, {"Name": "Vegetation"}], '
'"Aliases": [{"Name": "Forest"}], "Categories": [{"Name": "Nature and '
'Outdoors"}]}], "LabelModelVersion": "3.0", "ResponseMetadata": '
'{"RequestId": "0e2ee3f5-113d-489f-b922-f8568c023dc8", '
'"HTTPStatusCode": 200, "HTTPHeaders": {"x-amzn-requestid": '
'"0e2ee3f5-113d-489f-b922-f8568c023dc8", "content-type": '
'"application/x-amz-json-1.1", "content-length": "7061", "date": '
'"Thu, 06 Feb 2025 17:14:20 GMT"}, "RetryAttempts": 0}}',
'"Confidence": 55.16259002685547, "Instances": [], "Parents": '
'[{"Name": "Land"}, {"Name": "Nature"}, {"Name": "Outdoors"}, '
'{"Name": "Plant"}, {"Name": "Tree"}, {"Name": "Vegetation"}], '
'"Aliases": [{"Name": "Forest"}], "Categories": [{"Name": "Nature and '
'Outdoors"}]}], "LabelModelVersion": "3.0", "ResponseMetadata": '
'{"RequestId": "0e2ee3f5-113d-489f-b922-f8568c023dc8", '
'"Confidence": 55.16259002685547, "Instances": [], "Parents": '
'"Confidence": 55.16259002685547, "Instances": [], "Parents": '
'[{"Name": "Land"}, {"Name": "Nature"}, {"Name": "Outdoors"}, '
'{"Name": "Plant"}, {"Name": "Tree"}, {"Name": "Vegetation"}], '
'"Aliases": [{"Name": "Forest"}], "Categories": [{"Name": "Nature and '
'Outdoors"}]}], "LabelModelVersion": "3.0", "ResponseMetadata": '
'{"RequestId": "0e2ee3f5-113d-489f-b922-f8568c023dc8", '
'"HTTPStatusCode": 200, "HTTPHeaders": {"x-amzn-requestid": '
'"0e2ee3f5-113d-489f-b922-f8568c023dc8", "content-type": '
'"application/x-amz-json-1.1", "content-length": "7061", "date": '
'"Thu, 06 Feb 2025 17:14:20 GMT"}, "RetryAttempts": 0}}',
'statusCode': 200}
'"Confidence": 55.16259002685547, "Instances": [], "Parents": '
'[{"Name": "Land"}, {"Name": "Nature"}, {"Name": "Outdoors"}, '
'{"Name": "Plant"}, {"Name": "Tree"}, {"Name": "Vegetation"}], '
'"Aliases": [{"Name": "Forest"}], "Categories": [{"Name": "Nature and '
'Outdoors"}]}], "LabelModelVersion": "3.0", "ResponseMetadata": '
'{"RequestId": "0e2ee3f5-113d-489f-b922-f8568c023dc8", '
'"HTTPStatusCode": 200, "HTTPHeaders": {"x-amzn-requestid": '
'"0e2ee3f5-113d-489f-b922-f8568c023dc8", "content-type": '
'"Confidence": 55.16259002685547, "Instances": [], "Parents": '
'[{"Name": "Land"}, {"Name": "Nature"}, {"Name": "Outdoors"}, '
'{"Name": "Plant"}, {"Name": "Tree"}, {"Name": "Vegetation"}], '
'"Aliases": [{"Name": "Forest"}], "Categories": [{"Name": "Nature and '
'Outdoors"}]}], "LabelModelVersion": "3.0", "ResponseMetadata": '
'{"RequestId": "0e2ee3f5-113d-489f-b922-f8568c023dc8", '
'"Confidence": 55.16259002685547, "Instances": [], "Parents": '
'[{"Name": "Land"}, {"Name": "Nature"}, {"Name": "Outdoors"}, '
'{"Name": "Plant"}, {"Name": "Tree"}, {"Name": "Vegetation"}], '
'"Confidence": 55.16259002685547, "Instances": [], "Parents": '
'"Confidence": 55.16259002685547, "Instances": [], "Parents": '
'[{"Name": "Land"}, {"Name": "Nature"}, {"Name": "Outdoors"}, '
'{"Name": "Plant"}, {"Name": "Tree"}, {"Name": "Vegetation"}], '
'{"Name": "Plant"}, {"Name": "Tree"}, {"Name": "Vegetation"}], '
'"Aliases": [{"Name": "Forest"}], "Categories": [{"Name": "Nature and '
'Outdoors"}]}], "LabelModelVersion": "3.0", "ResponseMetadata": '
'{"RequestId": "0e2ee3f5-113d-489f-b922-f8568c023dc8", '
'"HTTPStatusCode": 200, "HTTPHeaders": {"x-amzn-requestid": '
'"0e2ee3f5-113d-489f-b922-f8568c023dc8", "content-type": '
'"application/x-amz-json-1.1", "content-length": "7061", "date": '
'"Thu, 06 Feb 2025 17:14:20 GMT"}, "RetryAttempts": 0}}',
'statusCode': 200}
Great we have now got something working!!
Let's Iterate...
So with the Lambda function working, I copied it back into VScode and asked Q Developer if it could modify the function so that it could take the Bucket and Object name from an EventBridge Event... so off it went and spit out modified function code.
So next I added the EventBridge Rule to track when object creation occurred in my S3 bucket and to trigger the Lambda function.
So with this in place I uploaded a new image to my S3 bucket this time i used a different image, this was of a lovely Mercedes AMG from when I recently attend a training event at Mercedes Benz World.
EventBridge triggered Lambda but it threw some errors... Oh no here we go again..
I copied the errors from CloudWatch and told Q Developer that these errors were occurring with the Lambda code, it immediately came back with
The error occurs because the event structure you're receiving contains nested dictionaries for the bucket and object information, but Rekognition expects simple strings.
It proceeded to spit out a new modified version of the Lambda function to fix these errors, so I dropped the new code into the Lambda function and deployed it, deleted the image object I had previously uploaded and uploaded it again.
IT WORKED!!!
As you can see from the above image, Rekognition Labels have been returned... Woooo we are part way there.
Let's Iterate again...
So as you saw earlier Rekognition returns a lot of labels with varying degrees of confidence. With the ultimate end goal to add these labels to the S3 Object as metadata we probably don't want all of the labels, so I asked Q developer again, could the Lambda function be modified to only return labels that had a confidence rating above 95, so off it goes and within 30 secs it has created modified function code.
So I then updated the Lambda function and deployed it, and ran through the process of removing the image object and uploading again, this time it ran perfectly with no issues (Q Developer is getting better as we go) and as you can see from the below it is returning labels as requested.
Now we are cooking...
Let's Iterate one more time...
So now we have this sorted we are going to change the code one more time, so back to Q Developer, so this time, I asked Q Developer to modify the Lambda function to take the returned labels from Rekognition and update the S3 Objects metadata with the labels that have been found. So like a good assistant Q Developer spits out updated function code with the new requirements in. So I update the Lambda function once more and also modify the Lambda execution role to allow Put functions, once this is complete I then went through the process of removing the image object and uploading again and again everything ran perfectly with no issues and the object metadata was updated as expected.
So there you have it
So we got there in the end with the heavy lifting being done by Q Developer, but I hope this shows you two things, 1) You don't have to be great at everything to be able to have a go at using some of the other more advanced services especially with Q Developer getting better and better overtime (when I first used Q Developer is wasn't great..) and 2) Breaking down solutions in to manageable bites and getting things working and linking them together as you go is a great way to solve issues and break down the barriers that might stop you from giving it a go.
I hope this inspires you to go and have a play with AWS and services you may not touch normally.
I have dropped the working Lambda Python codes into a Github repo for you to grab and try if you wish, please just keep in mind you may need to update names in the code if you have called functions different names etc, but use them, try them, use Q Developer to help you if they dont work...
Until next time enjoy playing with AWS!
Top comments (0)