awslabs.nova-canvas-mcp-server 0.1.10652__tar.gz → 0.1.62303__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- awslabs_nova_canvas_mcp_server-0.1.62303/.python-version +1 -0
- awslabs_nova_canvas_mcp_server-0.1.62303/Dockerfile +29 -0
- awslabs_nova_canvas_mcp_server-0.1.62303/PKG-INFO +91 -0
- awslabs_nova_canvas_mcp_server-0.1.62303/README.md +80 -0
- {awslabs_nova_canvas_mcp_server-0.1.10652 → awslabs_nova_canvas_mcp_server-0.1.62303}/awslabs/nova_canvas_mcp_server/__init__.py +1 -1
- {awslabs_nova_canvas_mcp_server-0.1.10652 → awslabs_nova_canvas_mcp_server-0.1.62303}/awslabs/nova_canvas_mcp_server/consts.py +3 -3
- {awslabs_nova_canvas_mcp_server-0.1.10652 → awslabs_nova_canvas_mcp_server-0.1.62303}/awslabs/nova_canvas_mcp_server/models.py +23 -27
- {awslabs_nova_canvas_mcp_server-0.1.10652 → awslabs_nova_canvas_mcp_server-0.1.62303}/awslabs/nova_canvas_mcp_server/novacanvas.py +50 -70
- {awslabs_nova_canvas_mcp_server-0.1.10652 → awslabs_nova_canvas_mcp_server-0.1.62303}/awslabs/nova_canvas_mcp_server/server.py +56 -70
- {awslabs_nova_canvas_mcp_server-0.1.10652 → awslabs_nova_canvas_mcp_server-0.1.62303}/pyproject.toml +5 -2
- awslabs_nova_canvas_mcp_server-0.1.62303/smithery.yaml +58 -0
- {awslabs_nova_canvas_mcp_server-0.1.10652 → awslabs_nova_canvas_mcp_server-0.1.62303}/uv.lock +175 -3
- awslabs_nova_canvas_mcp_server-0.1.10652/.python-version +0 -1
- awslabs_nova_canvas_mcp_server-0.1.10652/PKG-INFO +0 -74
- awslabs_nova_canvas_mcp_server-0.1.10652/README.md +0 -63
- {awslabs_nova_canvas_mcp_server-0.1.10652 → awslabs_nova_canvas_mcp_server-0.1.62303}/.gitignore +0 -0
- {awslabs_nova_canvas_mcp_server-0.1.10652 → awslabs_nova_canvas_mcp_server-0.1.62303}/.pre-commit-config.yaml +0 -0
- {awslabs_nova_canvas_mcp_server-0.1.10652 → awslabs_nova_canvas_mcp_server-0.1.62303}/CHANGELOG.md +0 -0
- {awslabs_nova_canvas_mcp_server-0.1.10652 → awslabs_nova_canvas_mcp_server-0.1.62303}/awslabs/__init__.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.10
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
|
|
2
|
+
FROM python:3.10-alpine
|
|
3
|
+
|
|
4
|
+
# Install build dependencies
|
|
5
|
+
RUN apk add --no-cache gcc musl-dev linux-headers
|
|
6
|
+
|
|
7
|
+
WORKDIR /app
|
|
8
|
+
|
|
9
|
+
# Copy the entire repository (monorepo context)
|
|
10
|
+
COPY . .
|
|
11
|
+
|
|
12
|
+
# Set working directory to the MCP server subdirectory
|
|
13
|
+
WORKDIR /app/src/nova-canvas-mcp-server
|
|
14
|
+
|
|
15
|
+
# Create dummy AWS credentials to satisfy boto3 when using the default profile
|
|
16
|
+
RUN mkdir -p /root/.aws && \
|
|
17
|
+
echo "[default]" > /root/.aws/credentials && \
|
|
18
|
+
echo "aws_access_key_id = dummy" >> /root/.aws/credentials && \
|
|
19
|
+
echo "aws_secret_access_key = dummy" >> /root/.aws/credentials
|
|
20
|
+
|
|
21
|
+
# Upgrade pip and install the package
|
|
22
|
+
RUN pip install --no-cache-dir --upgrade pip && \
|
|
23
|
+
pip install --no-cache-dir .
|
|
24
|
+
|
|
25
|
+
# Expose port if using SSE transport (optional)
|
|
26
|
+
EXPOSE 8888
|
|
27
|
+
|
|
28
|
+
# Start the MCP server (entrypoint provided by project.scripts in pyproject.toml)
|
|
29
|
+
CMD ["awslabs.nova-canvas-mcp-server"]
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: awslabs.nova-canvas-mcp-server
|
|
3
|
+
Version: 0.1.62303
|
|
4
|
+
Summary: An AWS Labs Model Context Protocol (MCP) server for Amazon Nova Canvas
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: boto3>=1.37.24
|
|
7
|
+
Requires-Dist: loguru>=0.7.3
|
|
8
|
+
Requires-Dist: mcp[cli]>=1.6.0
|
|
9
|
+
Requires-Dist: pydantic>=2.11.1
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# Amazon Nova Canvas MCP Server
|
|
13
|
+
|
|
14
|
+
[](https://smithery.ai/server/@awslabs/nova-canvas-mcp-server)
|
|
15
|
+
|
|
16
|
+
MCP server for generating images using Amazon Nova Canvas
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
### Text-based image generation
|
|
21
|
+
|
|
22
|
+
- Create images from text prompts with `generate_image`
|
|
23
|
+
- Customizable dimensions (320-4096px), quality options, and negative prompting
|
|
24
|
+
- Supports multiple image generation (1-5) in single request
|
|
25
|
+
- Adjustable parameters like cfg_scale (1.1-10.0) and seeded generation
|
|
26
|
+
|
|
27
|
+
### Color-guided image generation
|
|
28
|
+
|
|
29
|
+
- Generate images with specific color palettes using `generate_image_with_colors`
|
|
30
|
+
- Define up to 10 hex color values to influence the image style and mood
|
|
31
|
+
- Same customization options as text-based generation
|
|
32
|
+
|
|
33
|
+
### Workspace integration
|
|
34
|
+
|
|
35
|
+
- Images saved to user-specified workspace directories with automatic folder creation
|
|
36
|
+
|
|
37
|
+
### AWS authentication
|
|
38
|
+
|
|
39
|
+
- Uses AWS profiles for secure access to Amazon Nova Canvas services
|
|
40
|
+
|
|
41
|
+
## Prerequisites
|
|
42
|
+
|
|
43
|
+
1. Install `uv` from [Astral](https://docs.astral.sh/uv/getting-started/installation/) or the [GitHub README](https://github.com/astral-sh/uv#installation)
|
|
44
|
+
2. Install Python using `uv python install 3.10`
|
|
45
|
+
3. Set up AWS credentials with access to Amazon Bedrock and Nova Canvas
|
|
46
|
+
- You need an AWS account with Amazon Bedrock and Amazon Nova Canvas enabled
|
|
47
|
+
- Configure AWS credentials with `aws configure` or environment variables
|
|
48
|
+
- Ensure your IAM role/user has permissions to use Amazon Bedrock and Nova Canvas
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
Here are some ways you can work with MCP across AWS, and we'll be adding support to more products including Amazon Q Developer CLI soon: (e.g. for Amazon Q Developer CLI MCP, `~/.aws/amazonq/mcp.json`):
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"mcpServers": {
|
|
57
|
+
"awslabs.nova-canvas-mcp-server": {
|
|
58
|
+
"command": "uvx",
|
|
59
|
+
"args": ["awslabs.nova-canvas-mcp-server@latest"],
|
|
60
|
+
"env": {
|
|
61
|
+
"AWS_PROFILE": "your-aws-profile",
|
|
62
|
+
"AWS_REGION": "us-east-1",
|
|
63
|
+
"FASTMCP_LOG_LEVEL": "ERROR"
|
|
64
|
+
},
|
|
65
|
+
"disabled": false,
|
|
66
|
+
"autoApprove": []
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Installing via Smithery
|
|
73
|
+
|
|
74
|
+
To install Amazon Nova Canvas MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@awslabs/nova-canvas-mcp-server):
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npx -y @smithery/cli install @awslabs/nova-canvas-mcp-server --client claude
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### AWS Authentication
|
|
81
|
+
|
|
82
|
+
The MCP server uses the AWS profile specified in the `AWS_PROFILE` environment variable. If not provided, it defaults to the "default" profile in your AWS configuration file.
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
"env": {
|
|
86
|
+
"AWS_PROFILE": "your-aws-profile",
|
|
87
|
+
"AWS_REGION": "us-east-1"
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Make sure the AWS profile has permissions to access Amazon Bedrock and Amazon Nova Canvas. The MCP server creates a boto3 session using the specified profile to authenticate with AWS services. Your AWS IAM credentials remain on your local machine and are strictly used for using the Amazon Bedrock model APIs.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Amazon Nova Canvas MCP Server
|
|
2
|
+
|
|
3
|
+
[](https://smithery.ai/server/@awslabs/nova-canvas-mcp-server)
|
|
4
|
+
|
|
5
|
+
MCP server for generating images using Amazon Nova Canvas
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
### Text-based image generation
|
|
10
|
+
|
|
11
|
+
- Create images from text prompts with `generate_image`
|
|
12
|
+
- Customizable dimensions (320-4096px), quality options, and negative prompting
|
|
13
|
+
- Supports multiple image generation (1-5) in single request
|
|
14
|
+
- Adjustable parameters like cfg_scale (1.1-10.0) and seeded generation
|
|
15
|
+
|
|
16
|
+
### Color-guided image generation
|
|
17
|
+
|
|
18
|
+
- Generate images with specific color palettes using `generate_image_with_colors`
|
|
19
|
+
- Define up to 10 hex color values to influence the image style and mood
|
|
20
|
+
- Same customization options as text-based generation
|
|
21
|
+
|
|
22
|
+
### Workspace integration
|
|
23
|
+
|
|
24
|
+
- Images saved to user-specified workspace directories with automatic folder creation
|
|
25
|
+
|
|
26
|
+
### AWS authentication
|
|
27
|
+
|
|
28
|
+
- Uses AWS profiles for secure access to Amazon Nova Canvas services
|
|
29
|
+
|
|
30
|
+
## Prerequisites
|
|
31
|
+
|
|
32
|
+
1. Install `uv` from [Astral](https://docs.astral.sh/uv/getting-started/installation/) or the [GitHub README](https://github.com/astral-sh/uv#installation)
|
|
33
|
+
2. Install Python using `uv python install 3.10`
|
|
34
|
+
3. Set up AWS credentials with access to Amazon Bedrock and Nova Canvas
|
|
35
|
+
- You need an AWS account with Amazon Bedrock and Amazon Nova Canvas enabled
|
|
36
|
+
- Configure AWS credentials with `aws configure` or environment variables
|
|
37
|
+
- Ensure your IAM role/user has permissions to use Amazon Bedrock and Nova Canvas
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
Here are some ways you can work with MCP across AWS, and we'll be adding support to more products including Amazon Q Developer CLI soon: (e.g. for Amazon Q Developer CLI MCP, `~/.aws/amazonq/mcp.json`):
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"awslabs.nova-canvas-mcp-server": {
|
|
47
|
+
"command": "uvx",
|
|
48
|
+
"args": ["awslabs.nova-canvas-mcp-server@latest"],
|
|
49
|
+
"env": {
|
|
50
|
+
"AWS_PROFILE": "your-aws-profile",
|
|
51
|
+
"AWS_REGION": "us-east-1",
|
|
52
|
+
"FASTMCP_LOG_LEVEL": "ERROR"
|
|
53
|
+
},
|
|
54
|
+
"disabled": false,
|
|
55
|
+
"autoApprove": []
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Installing via Smithery
|
|
62
|
+
|
|
63
|
+
To install Amazon Nova Canvas MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@awslabs/nova-canvas-mcp-server):
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npx -y @smithery/cli install @awslabs/nova-canvas-mcp-server --client claude
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### AWS Authentication
|
|
70
|
+
|
|
71
|
+
The MCP server uses the AWS profile specified in the `AWS_PROFILE` environment variable. If not provided, it defaults to the "default" profile in your AWS configuration file.
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
"env": {
|
|
75
|
+
"AWS_PROFILE": "your-aws-profile",
|
|
76
|
+
"AWS_REGION": "us-east-1"
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Make sure the AWS profile has permissions to access Amazon Bedrock and Amazon Nova Canvas. The MCP server creates a boto3 session using the specified profile to authenticate with AWS services. Your AWS IAM credentials remain on your local machine and are strictly used for using the Amazon Bedrock model APIs.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Constants
|
|
2
|
-
NOVA_CANVAS_MODEL_ID =
|
|
2
|
+
NOVA_CANVAS_MODEL_ID = 'amazon.nova-canvas-v1:0'
|
|
3
3
|
DEFAULT_WIDTH = 1024
|
|
4
4
|
DEFAULT_HEIGHT = 1024
|
|
5
|
-
DEFAULT_QUALITY =
|
|
5
|
+
DEFAULT_QUALITY = 'standard'
|
|
6
6
|
DEFAULT_CFG_SCALE = 6.5
|
|
7
7
|
DEFAULT_NUMBER_OF_IMAGES = 1
|
|
8
|
-
DEFAULT_OUTPUT_DIR =
|
|
8
|
+
DEFAULT_OUTPUT_DIR = 'output' # Default directory inside workspace_dir
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
# Nova Canvas Prompt Best Practices
|
|
@@ -15,8 +15,8 @@ class Quality(str, Enum):
|
|
|
15
15
|
PREMIUM: Premium quality image generation with enhanced details.
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
STANDARD =
|
|
19
|
-
PREMIUM =
|
|
18
|
+
STANDARD = 'standard'
|
|
19
|
+
PREMIUM = 'premium'
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class TaskType(str, Enum):
|
|
@@ -27,8 +27,8 @@ class TaskType(str, Enum):
|
|
|
27
27
|
COLOR_GUIDED_GENERATION: Generate an image guided by both text and color palette.
|
|
28
28
|
"""
|
|
29
29
|
|
|
30
|
-
TEXT_IMAGE =
|
|
31
|
-
COLOR_GUIDED_GENERATION =
|
|
30
|
+
TEXT_IMAGE = 'TEXT_IMAGE'
|
|
31
|
+
COLOR_GUIDED_GENERATION = 'COLOR_GUIDED_GENERATION'
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
class ImageGenerationConfig(BaseModel):
|
|
@@ -50,12 +50,10 @@ class ImageGenerationConfig(BaseModel):
|
|
|
50
50
|
height: int = Field(default=1024, ge=320, le=4096)
|
|
51
51
|
quality: Quality = Quality.STANDARD
|
|
52
52
|
cfgScale: float = Field(default=6.5, ge=1.1, le=10.0)
|
|
53
|
-
seed: int = Field(
|
|
54
|
-
default_factory=lambda: random.randint(0, 858993459), ge=0, le=858993459
|
|
55
|
-
)
|
|
53
|
+
seed: int = Field(default_factory=lambda: random.randint(0, 858993459), ge=0, le=858993459)
|
|
56
54
|
numberOfImages: int = Field(default=1, ge=1, le=5)
|
|
57
55
|
|
|
58
|
-
@field_validator(
|
|
56
|
+
@field_validator('width', 'height')
|
|
59
57
|
@classmethod
|
|
60
58
|
def must_be_divisible_by_16(cls, v: int) -> int:
|
|
61
59
|
"""Validate that width and height are divisible by 16.
|
|
@@ -70,10 +68,10 @@ class ImageGenerationConfig(BaseModel):
|
|
|
70
68
|
ValueError: If the value is not divisible by 16.
|
|
71
69
|
"""
|
|
72
70
|
if v % 16 != 0:
|
|
73
|
-
raise ValueError(
|
|
71
|
+
raise ValueError('Value must be divisible by 16')
|
|
74
72
|
return v
|
|
75
73
|
|
|
76
|
-
@model_validator(mode=
|
|
74
|
+
@model_validator(mode='after')
|
|
77
75
|
def validate_aspect_ratio_and_total_pixels(self):
|
|
78
76
|
"""Validate aspect ratio and total pixel count.
|
|
79
77
|
|
|
@@ -93,12 +91,12 @@ class ImageGenerationConfig(BaseModel):
|
|
|
93
91
|
# Check aspect ratio between 1:4 and 4:1
|
|
94
92
|
aspect_ratio = width / height
|
|
95
93
|
if aspect_ratio < 0.25 or aspect_ratio > 4.0:
|
|
96
|
-
raise ValueError(
|
|
94
|
+
raise ValueError('Aspect ratio must be between 1:4 and 4:1')
|
|
97
95
|
|
|
98
96
|
# Check total pixel count
|
|
99
97
|
total_pixels = width * height
|
|
100
98
|
if total_pixels >= 4194304:
|
|
101
|
-
raise ValueError(
|
|
99
|
+
raise ValueError('Total pixel count must be less than 4,194,304')
|
|
102
100
|
|
|
103
101
|
return self
|
|
104
102
|
|
|
@@ -132,7 +130,7 @@ class ColorGuidedGenerationParams(BaseModel):
|
|
|
132
130
|
text: str = Field(..., min_length=1, max_length=1024)
|
|
133
131
|
negativeText: Optional[str] = Field(default=None, min_length=1, max_length=1024)
|
|
134
132
|
|
|
135
|
-
@field_validator(
|
|
133
|
+
@field_validator('colors')
|
|
136
134
|
@classmethod
|
|
137
135
|
def validate_hex_colors(cls, v: List[str]) -> List[str]:
|
|
138
136
|
"""Validate that colors are in the correct hexadecimal format.
|
|
@@ -146,7 +144,7 @@ class ColorGuidedGenerationParams(BaseModel):
|
|
|
146
144
|
Raises:
|
|
147
145
|
ValueError: If any color is not a valid hexadecimal color in the format '#RRGGBB'.
|
|
148
146
|
"""
|
|
149
|
-
hex_pattern = re.compile(r
|
|
147
|
+
hex_pattern = re.compile(r'^#[0-9A-Fa-f]{6}$')
|
|
150
148
|
for color in v:
|
|
151
149
|
if not hex_pattern.match(color):
|
|
152
150
|
raise ValueError(
|
|
@@ -182,13 +180,13 @@ class TextImageRequest(BaseModel):
|
|
|
182
180
|
"""
|
|
183
181
|
text_to_image_params = self.textToImageParams.model_dump()
|
|
184
182
|
# Remove negativeText if it's None
|
|
185
|
-
if text_to_image_params.get(
|
|
186
|
-
text_to_image_params.pop(
|
|
183
|
+
if text_to_image_params.get('negativeText') is None:
|
|
184
|
+
text_to_image_params.pop('negativeText', None)
|
|
187
185
|
|
|
188
186
|
return {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
187
|
+
'taskType': self.taskType,
|
|
188
|
+
'textToImageParams': text_to_image_params,
|
|
189
|
+
'imageGenerationConfig': self.imageGenerationConfig.model_dump()
|
|
192
190
|
if self.imageGenerationConfig
|
|
193
191
|
else None,
|
|
194
192
|
}
|
|
@@ -206,9 +204,7 @@ class ColorGuidedRequest(BaseModel):
|
|
|
206
204
|
imageGenerationConfig: Configuration for image generation.
|
|
207
205
|
"""
|
|
208
206
|
|
|
209
|
-
taskType: Literal[
|
|
210
|
-
TaskType.COLOR_GUIDED_GENERATION
|
|
211
|
-
] = TaskType.COLOR_GUIDED_GENERATION
|
|
207
|
+
taskType: Literal[TaskType.COLOR_GUIDED_GENERATION] = TaskType.COLOR_GUIDED_GENERATION
|
|
212
208
|
colorGuidedGenerationParams: ColorGuidedGenerationParams
|
|
213
209
|
imageGenerationConfig: Optional[ImageGenerationConfig] = Field(
|
|
214
210
|
default_factory=ImageGenerationConfig
|
|
@@ -223,13 +219,13 @@ class ColorGuidedRequest(BaseModel):
|
|
|
223
219
|
"""
|
|
224
220
|
color_guided_params = self.colorGuidedGenerationParams.model_dump()
|
|
225
221
|
# Remove negativeText if it's None
|
|
226
|
-
if color_guided_params.get(
|
|
227
|
-
color_guided_params.pop(
|
|
222
|
+
if color_guided_params.get('negativeText') is None:
|
|
223
|
+
color_guided_params.pop('negativeText', None)
|
|
228
224
|
|
|
229
225
|
return {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
226
|
+
'taskType': self.taskType,
|
|
227
|
+
'colorGuidedGenerationParams': color_guided_params,
|
|
228
|
+
'imageGenerationConfig': self.imageGenerationConfig.model_dump()
|
|
233
229
|
if self.imageGenerationConfig
|
|
234
230
|
else None,
|
|
235
231
|
}
|
|
@@ -41,7 +41,7 @@ def save_generated_images(
|
|
|
41
41
|
base64_images: List[str],
|
|
42
42
|
filename: Optional[str] = None,
|
|
43
43
|
number_of_images: int = DEFAULT_NUMBER_OF_IMAGES,
|
|
44
|
-
prefix: str =
|
|
44
|
+
prefix: str = 'nova_canvas',
|
|
45
45
|
workspace_dir: Optional[str] = None,
|
|
46
46
|
) -> Dict[str, List]:
|
|
47
47
|
"""Save base64-encoded images to files.
|
|
@@ -56,7 +56,7 @@ def save_generated_images(
|
|
|
56
56
|
Returns:
|
|
57
57
|
Dictionary with lists of paths to the saved image files and PIL Image objects.
|
|
58
58
|
"""
|
|
59
|
-
logger.debug(f
|
|
59
|
+
logger.debug(f'Saving {len(base64_images)} images')
|
|
60
60
|
# Determine the output directory
|
|
61
61
|
if workspace_dir:
|
|
62
62
|
output_dir = os.path.join(workspace_dir, DEFAULT_OUTPUT_DIR)
|
|
@@ -73,27 +73,25 @@ def save_generated_images(
|
|
|
73
73
|
# Generate filename if not provided
|
|
74
74
|
if filename:
|
|
75
75
|
image_filename = (
|
|
76
|
-
f
|
|
76
|
+
f'{filename}_{i + 1}.png' if number_of_images > 1 else f'{filename}.png'
|
|
77
77
|
)
|
|
78
78
|
else:
|
|
79
79
|
# Generate a random filename
|
|
80
|
-
random_id =
|
|
81
|
-
|
|
82
|
-
)
|
|
83
|
-
image_filename = f"{prefix}_{random_id}_{i + 1}.png"
|
|
80
|
+
random_id = ''.join(random.choices('abcdefghijklmnopqrstuvwxyz0123456789', k=8))
|
|
81
|
+
image_filename = f'{prefix}_{random_id}_{i + 1}.png'
|
|
84
82
|
|
|
85
83
|
# Decode the base64 image data
|
|
86
84
|
image_data = base64.b64decode(base64_image_data)
|
|
87
85
|
|
|
88
86
|
# Save the image
|
|
89
87
|
image_path = os.path.join(output_dir, image_filename)
|
|
90
|
-
with open(image_path,
|
|
88
|
+
with open(image_path, 'wb') as file:
|
|
91
89
|
file.write(image_data)
|
|
92
90
|
# Convert to absolute path
|
|
93
91
|
abs_image_path = os.path.abspath(image_path)
|
|
94
92
|
saved_paths.append(abs_image_path)
|
|
95
93
|
|
|
96
|
-
return {
|
|
94
|
+
return {'paths': saved_paths}
|
|
97
95
|
|
|
98
96
|
|
|
99
97
|
async def invoke_nova_canvas(
|
|
@@ -112,24 +110,22 @@ async def invoke_nova_canvas(
|
|
|
112
110
|
Raises:
|
|
113
111
|
Exception: If the API call fails.
|
|
114
112
|
"""
|
|
115
|
-
logger.debug(
|
|
113
|
+
logger.debug('Invoking Nova Canvas API')
|
|
116
114
|
|
|
117
115
|
# Convert the request payload to JSON
|
|
118
116
|
request = json.dumps(request_model_dict)
|
|
119
117
|
|
|
120
118
|
try:
|
|
121
119
|
# Invoke the model
|
|
122
|
-
logger.info(f
|
|
123
|
-
response = bedrock_runtime_client.invoke_model(
|
|
124
|
-
modelId=NOVA_CANVAS_MODEL_ID, body=request
|
|
125
|
-
)
|
|
120
|
+
logger.info(f'Sending request to Nova Canvas model: {NOVA_CANVAS_MODEL_ID}')
|
|
121
|
+
response = bedrock_runtime_client.invoke_model(modelId=NOVA_CANVAS_MODEL_ID, body=request)
|
|
126
122
|
|
|
127
123
|
# Decode the response body
|
|
128
|
-
result = json.loads(response[
|
|
129
|
-
logger.info(
|
|
124
|
+
result = json.loads(response['body'].read())
|
|
125
|
+
logger.info('Nova Canvas API call successful')
|
|
130
126
|
return result
|
|
131
127
|
except Exception as e:
|
|
132
|
-
logger.error(f
|
|
128
|
+
logger.error(f'Nova Canvas API call failed: {str(e)}')
|
|
133
129
|
raise
|
|
134
130
|
|
|
135
131
|
|
|
@@ -169,22 +165,18 @@ async def generate_image_with_text(
|
|
|
169
165
|
ImageGenerationResponse: An object containing the paths to the generated images,
|
|
170
166
|
PIL Image objects, and status information.
|
|
171
167
|
"""
|
|
172
|
-
logger.debug(
|
|
173
|
-
f"Generating text-to-image with prompt: '{prompt[:30]}...' ({width}x{height})"
|
|
174
|
-
)
|
|
168
|
+
logger.debug(f"Generating text-to-image with prompt: '{prompt[:30]}...' ({width}x{height})")
|
|
175
169
|
|
|
176
170
|
try:
|
|
177
171
|
# Validate input parameters using Pydantic
|
|
178
172
|
try:
|
|
179
|
-
logger.debug(
|
|
173
|
+
logger.debug('Validating parameters and creating request model')
|
|
180
174
|
|
|
181
175
|
# Create image generation config
|
|
182
176
|
config = ImageGenerationConfig(
|
|
183
177
|
width=width,
|
|
184
178
|
height=height,
|
|
185
|
-
quality=Quality.STANDARD
|
|
186
|
-
if quality == DEFAULT_QUALITY
|
|
187
|
-
else Quality.PREMIUM,
|
|
179
|
+
quality=Quality.STANDARD if quality == DEFAULT_QUALITY else Quality.PREMIUM,
|
|
188
180
|
cfgScale=cfg_scale,
|
|
189
181
|
seed=seed if seed is not None else random.randint(0, 858993459),
|
|
190
182
|
numberOfImages=number_of_images,
|
|
@@ -193,9 +185,7 @@ async def generate_image_with_text(
|
|
|
193
185
|
# Create text-to-image params
|
|
194
186
|
# The Nova Canvas API doesn't accept null for negativeText
|
|
195
187
|
if negative_prompt is not None:
|
|
196
|
-
text_params = TextToImageParams(
|
|
197
|
-
text=prompt, negativeText=negative_prompt
|
|
198
|
-
)
|
|
188
|
+
text_params = TextToImageParams(text=prompt, negativeText=negative_prompt)
|
|
199
189
|
else:
|
|
200
190
|
text_params = TextToImageParams(text=prompt)
|
|
201
191
|
|
|
@@ -206,13 +196,13 @@ async def generate_image_with_text(
|
|
|
206
196
|
|
|
207
197
|
# Convert model to dictionary
|
|
208
198
|
request_model_dict = request_model.to_api_dict()
|
|
209
|
-
logger.info(
|
|
199
|
+
logger.info('Request validation successful')
|
|
210
200
|
|
|
211
201
|
except Exception as e:
|
|
212
|
-
logger.error(f
|
|
202
|
+
logger.error(f'Parameter validation failed: {str(e)}')
|
|
213
203
|
return ImageGenerationResponse(
|
|
214
|
-
status=
|
|
215
|
-
message=f
|
|
204
|
+
status='error',
|
|
205
|
+
message=f'Validation error: {str(e)}',
|
|
216
206
|
paths=[],
|
|
217
207
|
prompt=prompt,
|
|
218
208
|
negative_prompt=negative_prompt,
|
|
@@ -220,36 +210,34 @@ async def generate_image_with_text(
|
|
|
220
210
|
|
|
221
211
|
try:
|
|
222
212
|
# Invoke the Nova Canvas API
|
|
223
|
-
logger.debug(
|
|
224
|
-
model_response = await invoke_nova_canvas(
|
|
225
|
-
request_model_dict, bedrock_runtime_client
|
|
226
|
-
)
|
|
213
|
+
logger.debug('Sending request to Nova Canvas API')
|
|
214
|
+
model_response = await invoke_nova_canvas(request_model_dict, bedrock_runtime_client)
|
|
227
215
|
|
|
228
216
|
# Extract the image data
|
|
229
|
-
base64_images = model_response[
|
|
230
|
-
logger.info(f
|
|
217
|
+
base64_images = model_response['images']
|
|
218
|
+
logger.info(f'Received {len(base64_images)} images from Nova Canvas API')
|
|
231
219
|
|
|
232
220
|
# Save the generated images
|
|
233
221
|
result = save_generated_images(
|
|
234
222
|
base64_images,
|
|
235
223
|
filename,
|
|
236
224
|
number_of_images,
|
|
237
|
-
prefix=
|
|
225
|
+
prefix='nova_canvas',
|
|
238
226
|
workspace_dir=workspace_dir,
|
|
239
227
|
)
|
|
240
228
|
|
|
241
229
|
logger.info(f'Successfully generated {len(result["paths"])} image(s)')
|
|
242
230
|
return ImageGenerationResponse(
|
|
243
|
-
status=
|
|
231
|
+
status='success',
|
|
244
232
|
message=f'Generated {len(result["paths"])} image(s)',
|
|
245
|
-
paths=result[
|
|
233
|
+
paths=result['paths'],
|
|
246
234
|
prompt=prompt,
|
|
247
235
|
negative_prompt=negative_prompt,
|
|
248
236
|
)
|
|
249
237
|
except Exception as e:
|
|
250
|
-
logger.error(f
|
|
238
|
+
logger.error(f'Image generation failed: {str(e)}')
|
|
251
239
|
return ImageGenerationResponse(
|
|
252
|
-
status=
|
|
240
|
+
status='error',
|
|
253
241
|
message=str(e),
|
|
254
242
|
paths=[],
|
|
255
243
|
prompt=prompt,
|
|
@@ -257,9 +245,9 @@ async def generate_image_with_text(
|
|
|
257
245
|
)
|
|
258
246
|
|
|
259
247
|
except Exception as e:
|
|
260
|
-
logger.error(f
|
|
248
|
+
logger.error(f'Unexpected error in generate_image_with_text: {str(e)}')
|
|
261
249
|
return ImageGenerationResponse(
|
|
262
|
-
status=
|
|
250
|
+
status='error',
|
|
263
251
|
message=str(e),
|
|
264
252
|
paths=[],
|
|
265
253
|
prompt=prompt,
|
|
@@ -312,17 +300,13 @@ async def generate_image_with_colors(
|
|
|
312
300
|
try:
|
|
313
301
|
# Validate input parameters using Pydantic
|
|
314
302
|
try:
|
|
315
|
-
logger.debug(
|
|
316
|
-
"Validating parameters and creating color-guided request model"
|
|
317
|
-
)
|
|
303
|
+
logger.debug('Validating parameters and creating color-guided request model')
|
|
318
304
|
|
|
319
305
|
# Create image generation config
|
|
320
306
|
config = ImageGenerationConfig(
|
|
321
307
|
width=width,
|
|
322
308
|
height=height,
|
|
323
|
-
quality=Quality.STANDARD
|
|
324
|
-
if quality == DEFAULT_QUALITY
|
|
325
|
-
else Quality.PREMIUM,
|
|
309
|
+
quality=Quality.STANDARD if quality == DEFAULT_QUALITY else Quality.PREMIUM,
|
|
326
310
|
cfgScale=cfg_scale,
|
|
327
311
|
seed=seed if seed is not None else random.randint(0, 858993459),
|
|
328
312
|
numberOfImages=number_of_images,
|
|
@@ -349,13 +333,13 @@ async def generate_image_with_colors(
|
|
|
349
333
|
|
|
350
334
|
# Convert model to dictionary
|
|
351
335
|
request_model_dict = request_model.to_api_dict()
|
|
352
|
-
logger.info(
|
|
336
|
+
logger.info('Color-guided request validation successful')
|
|
353
337
|
|
|
354
338
|
except Exception as e:
|
|
355
|
-
logger.error(f
|
|
339
|
+
logger.error(f'Color-guided parameter validation failed: {str(e)}')
|
|
356
340
|
return ImageGenerationResponse(
|
|
357
|
-
status=
|
|
358
|
-
message=f
|
|
341
|
+
status='error',
|
|
342
|
+
message=f'Validation error: {str(e)}',
|
|
359
343
|
paths=[],
|
|
360
344
|
prompt=prompt,
|
|
361
345
|
negative_prompt=negative_prompt,
|
|
@@ -364,39 +348,35 @@ async def generate_image_with_colors(
|
|
|
364
348
|
|
|
365
349
|
try:
|
|
366
350
|
# Invoke the Nova Canvas API
|
|
367
|
-
logger.debug(
|
|
368
|
-
model_response = await invoke_nova_canvas(
|
|
369
|
-
request_model_dict, bedrock_runtime_client
|
|
370
|
-
)
|
|
351
|
+
logger.debug('Sending color-guided request to Nova Canvas API')
|
|
352
|
+
model_response = await invoke_nova_canvas(request_model_dict, bedrock_runtime_client)
|
|
371
353
|
|
|
372
354
|
# Extract the image data
|
|
373
|
-
base64_images = model_response[
|
|
374
|
-
logger.info(f
|
|
355
|
+
base64_images = model_response['images']
|
|
356
|
+
logger.info(f'Received {len(base64_images)} images from Nova Canvas API')
|
|
375
357
|
|
|
376
358
|
# Save the generated images
|
|
377
359
|
result = save_generated_images(
|
|
378
360
|
base64_images,
|
|
379
361
|
filename,
|
|
380
362
|
number_of_images,
|
|
381
|
-
prefix=
|
|
363
|
+
prefix='nova_canvas_color',
|
|
382
364
|
workspace_dir=workspace_dir,
|
|
383
365
|
)
|
|
384
366
|
|
|
385
|
-
logger.info(
|
|
386
|
-
f'Successfully generated {len(result["paths"])} color-guided image(s)'
|
|
387
|
-
)
|
|
367
|
+
logger.info(f'Successfully generated {len(result["paths"])} color-guided image(s)')
|
|
388
368
|
return ImageGenerationResponse(
|
|
389
|
-
status=
|
|
369
|
+
status='success',
|
|
390
370
|
message=f'Generated {len(result["paths"])} image(s)',
|
|
391
|
-
paths=result[
|
|
371
|
+
paths=result['paths'],
|
|
392
372
|
prompt=prompt,
|
|
393
373
|
negative_prompt=negative_prompt,
|
|
394
374
|
colors=colors,
|
|
395
375
|
)
|
|
396
376
|
except Exception as e:
|
|
397
|
-
logger.error(f
|
|
377
|
+
logger.error(f'Color-guided image generation failed: {str(e)}')
|
|
398
378
|
return ImageGenerationResponse(
|
|
399
|
-
status=
|
|
379
|
+
status='error',
|
|
400
380
|
message=str(e),
|
|
401
381
|
paths=[],
|
|
402
382
|
prompt=prompt,
|
|
@@ -405,9 +385,9 @@ async def generate_image_with_colors(
|
|
|
405
385
|
)
|
|
406
386
|
|
|
407
387
|
except Exception as e:
|
|
408
|
-
logger.error(f
|
|
388
|
+
logger.error(f'Unexpected error in generate_image_with_colors: {str(e)}')
|
|
409
389
|
return ImageGenerationResponse(
|
|
410
|
-
status=
|
|
390
|
+
status='error',
|
|
411
391
|
message=str(e),
|
|
412
392
|
paths=[],
|
|
413
393
|
prompt=prompt,
|