logo
Generate and store images in S3 using Titan Image Generator G1

Generate and store images in S3 using Titan Image Generator G1

A quickstart guide on deploying a SAM app that stores Bedrock images in S3 and creates logs in CloudWatch.

A
Albert
Amazon Employee
Published Feb 8, 2024
Last Modified Feb 9, 2024
"Dogfooding" is core to how we ship at AWS.
Our teams worked hard to get Titan Image Generator G1 out the door for re:Invent last year, feeding large datasets into our model and test-running countless prompts to produce realistic images. Our internal teams are already finding use cases for G1, and we're just getting started.
To see for myself how easy it was to get started, I made a Python SAM app that invokes a Lambda to generate an image in Bedrock, stores the image in an S3 bucket, and finally logs the invocation in CloudWatch.

I logged into my AWS account and requested access in the Bedrock console for Titan Image Generator G1. Access was given almost instantaneously.
bedrock_console
AWS Bedrock Console

Time to generate my Python SAM app and get started:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
zhalbert@6c7e67d927df work % sam init

You can preselect a particular runtime or package type when using the `sam init` experience.
Call `sam init --help` to learn more.

Which template source would you like to use?
1 - AWS Quick Start Templates
2 - Custom Template Location
Choice: 1

Choose an AWS Quick Start application template
1 - Hello World Example
2 - Data processing
3 - Hello World Example with Powertools for AWS Lambda
4 - Multi-step workflow
5 - Scheduled task
6 - Standalone function
7 - Serverless API
8 - Infrastructure event management
9 - Lambda Response Streaming
10 - Serverless Connector Hello World Example
11 - Multi-step workflow with Connectors
12 - GraphQLApi Hello World Example
13 - Full Stack
14 - Lambda EFS example
15 - Hello World Example With Powertools for AWS Lambda
16 - DynamoDB Example
17 - Machine Learning
Template: 1

Use the most popular runtime and package type? (Python and zip) [y/N]: y
The Hello World Example in SAM makes it easy to spin up a template.yaml for me to use as a basis to connect all the AWS services. I also enabled structured logging in JSON for my Lambda functions for CloudWatch.
1
2
3
4
Would you like to set Structured Logging in JSON format on your Lambda functions? [y/N]: y
Structured Logging in JSON format might incur an additional cost. View https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs.html#monitoring-cloudwatchlogs-pricing for more details

Project name [sam-app]: s3imagegenerator

I create a directory called s3imagegenerator in my SAM app for the new Lambda, then added my app.py file.
My Lambda needs to generate an image based on given text and seed parameters, and then uploads the image into S3.
When triggered, Lambda should first extract the 'text' and 'seed' values from the event object. Afterwards, it then calls a function that constructs a request to Bedrock with these parameters. I'll get a base64-encoded image in the response, but then I need to decode it into binary, so it'll upload into S3 under a dynamically generated object key. CloudWatch will log each step I just outlined.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import json
import logging
import boto3
from botocore.exceptions import ClientError
import base64

# This creates a logger instance
logger = logging.getLogger()
logger.setLevel(logging.INFO)

# This initializes the clients Bedrock Runtime and S3
bedrock_runtime_client = boto3.client('bedrock-runtime', region_name='us-east-1')
s3 = boto3.client('s3')

bucket_name = 's3bucketname' # Add the s3 bucket you want to upload photos to

def lambda_handler(event, context):
# We need to extract 'text' and 'seed' from the event, provide defaults if not present
prompt = event.get('text', 'default prompt')
seed = event.get('seed', 0) # Default seed value if not provided

try:
base64_image_data = invoke_titan_image(prompt, seed)

# The image data is a base64-encoded string, so we need to decode it to get the actual image data
image_data = base64.b64decode(base64_image_data)
object_key = f"generated_images/image_{prompt.replace(' ', '_')}_{seed}.jpg"

# Now we upload the image data to S3
s3.put_object(
Bucket=bucket_name,
Key=object_key,
Body=image_data,
ContentType='image/jpeg' # This is adjustable!
)
logger.info(f"Uploaded image to s3://{bucket_name}/{object_key}")

return {
'statusCode': 200,
'body': json.dumps({'message': f"Image uploaded successfully to {bucket_name}/{object_key}."})
}

except Exception as e:
logger.error("Error: %s", str(e))
return {
'statusCode': 500,
'body': json.dumps({'error': 'Failed to invoke model or upload image', 'detail': str(e)})
}

def invoke_titan_image(prompt, seed):
try:
request = json.dumps({
"taskType": "TEXT_IMAGE",
"textToImageParams": {"text": prompt},
"imageGenerationConfig": {
"numberOfImages": 1,
"quality": "standard",
"cfgScale": 8.0,
"height": 640, # Permissible sizes: https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-titan-image.html#:~:text=The%20following%20sizes%20are%20permissible.
"width": 1408,
"seed": seed,
},
})

response = bedrock_runtime_client.invoke_model(
modelId="amazon.titan-image-generator-v1", body=request
)

response_body = json.loads(response["body"].read())
base64_image_data = response_body["images"][0]

return base64_image_data

except ClientError as e:
logger.error(f"Couldn't invoke Titan Image generator: {e}")
raise

Time to set up my template.yaml and start putting together the AWS services I'll be using (Lambda, S3, CloudWatch, Bedrock).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
A SAM application to invoke a Lambda that calls the Bedrock Titan Image Generator then stores the output in S3 and logs in CloudWatch, and a Hello World function

Globals:
Function:
Timeout: 60
MemorySize: 128

Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: python3.9
Architectures:
- x86_64
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello
Method: get

S3ImageGeneratorFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3imagegenerator/
Handler: app.lambda_handler
Runtime: python3.8 # Ensure this matches the Python version you're using in s3imagegenerator
Policies:
- S3CrudPolicy:
BucketName: "s3imagegenerator" # Replace with your actual S3 bucket name
- Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- "bedrock-runtime:InvokeModel"
Resource: "*"
- Effect: Allow
Action:
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
Resource: "arn:aws:logs:*:*:*"
Events:
TitanImageApi:
Type: Api
Properties:
Path: /generate-image
Method: post

Outputs:
HelloWorldApi:
Description: "API Gateway endpoint URL for Prod stage for Hello World function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
HelloWorldFunction:
Description: "Hello World Lambda Function ARN"
Value: !GetAtt HelloWorldFunction.Arn
HelloWorldFunctionIamRole:
Description: "Implicit IAM Role created for Hello World function"
Value: !GetAtt HelloWorldFunctionRole.Arn

S3ImageGeneratorApi:
Description: "API Gateway endpoint URL for Prod stage for S3 Image Generator function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/generate-image/"
S3ImageGeneratorFunction:
Description: "S3 Image Generator Function ARN"
Value: !GetAtt S3ImageGeneratorFunction.Arn
S3ImageGeneratorFunctionIamRole:
Description: "Implicit IAM Role created for S3 Image Generator function"
Value: !GetAtt S3ImageGeneratorFunction.Arn

I run sam build and sam deploy.
Everything is looking good, so let's use the AWS CLI and invoke the Lambda locally with a prompt:
1
2
3
4
5
zhalbert@6c7e67d927df s3imagegenerator % aws lambda invoke \
--function-name s3imagegenerator-<function name> \
--payload '{"text":"dog with cowboy hat","seed":2000}' \
--cli-binary-format raw-in-base64-out \
response.json
I get an error. I check the generated response.json, but it's unclear. Finally, I open CloudWatch and see this: "log_level": "ERROR", "errorMessage": "Unknown service: 'bedrock-runtime'
Ah, looks like the default boto3 SDK in Lambda is not up to date for Bedrock. I add a requirements.txt file in my s3imagegenerator directory with the latest version: boto3==1.34.27
I run the Lambda again and boom: my S3 bucket now stores an image of a dog with a cowboy hat, which you can see in this post's header.
And so everyone is happy, here's a cat that G1 made:
orange_cat
Overall, this was a pretty easy experience. You can view this project on GitHub.
 

Any opinions in this post are those of the individual author and may not reflect the opinions of AWS.