Merge pull request #6 from qingyuan18/main

added nova canvas&reel and textract node
This commit is contained in:
qingyuan18
2025-01-22 22:22:09 +08:00
committed by GitHub
21 changed files with 3599 additions and 187 deletions
+3
View File
@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/comfyui-llm-node-for-amazon-bedrock.iml" filepath="$PROJECT_DIR$/.idea/comfyui-llm-node-for-amazon-bedrock.iml" />
</modules>
</component>
</project>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
+8
View File
@@ -143,6 +143,14 @@ Here are models ready for use, more models are coming soon.
- [X] Claude Instant (1.x)
- Amazon:
- Nova Canvas
- [X] text to image
- [X] variation
- [X] background replacement(with prompt)
- Nova Reel
- [X] text to video
- [X] image to video
- Titan Image Generator G1 (1.x)
- [X] text to image
Binary file not shown.

After

Width:  |  Height:  |  Size: 664 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 392 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

+126 -1
View File
@@ -20,6 +20,129 @@ CLAUDE3_MAX_SIZE = 1568
bedrock_runtime_client = get_client(service_name="bedrock-runtime")
class BedrockNovaMultimodal:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"prompt": ("STRING", {"multiline": True}),
"model_id": (
[
"amazon.nova-pro-v1:0",
"amazon.nova-lite-v1:0",
],
),
"maxTokens": (
"INT",
{
"default": 2048,
"min": 0, # Minimum value
"max": 4096, # Maximum value
"step": 1, # Slider's step
"display": "number", # Cosmetic only: display as "number" or "slider"
},
),
"temperature": (
"FLOAT",
{
"default": 0.5,
"min": 0.0,
"max": 1.0,
"step": 0.1,
"round": 0.001, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding.
"display": "slider",
},
),
"topP": (
"FLOAT",
{
"default": 1.0,
"min": 0.0,
"max": 1.0,
"step": 0.1,
"round": 0.001, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding.
"display": "slider",
},
)
},
"optional": {
"image": ("IMAGE",{"default": None}),
}
}
RETURN_TYPES = ("STRING",)
FUNCTION = "forward"
CATEGORY = "aws"
@retry(tries=MAX_RETRY)
def forward(
self,
**kwargs
):
prompt = kwargs.get('prompt')
model_id = kwargs.get('model_id')
maxTokens = kwargs.get('maxTokens')
temperature = kwargs.get('temperature')
topP = kwargs.get('topP')
image = kwargs.get('image')
content = []
content.append({
"text": prompt,
})
if image is not None:
image = image[0] * 255.0
image = Image.fromarray(image.clamp(0, 255).numpy().round().astype(np.uint8))
width, height = image.size
max_size = max(width, height)
if max_size > CLAUDE3_MAX_SIZE:
width = round(width * CLAUDE3_MAX_SIZE / max_size)
height = round(height * CLAUDE3_MAX_SIZE / max_size)
image = image.resize((width, height))
buffer = BytesIO()
image.save(buffer, format="png", quality=80)
image_data = buffer.getvalue()
image_base64 = base64.b64encode(image_data).decode("utf-8")
content.append( {
"image": {
"format": "png",
"source": {"bytes": image_base64},
}
}
)
inf_params = {"max_new_tokens": maxTokens, "top_p": topP, "temperature": temperature}
body = json.dumps(
{
"schemaVersion": "messages-v1",
"messages": [
{
"role": "user",
"content": content,
}
],
"inferenceConfig": inf_params,
},
ensure_ascii=False,
)
response = bedrock_runtime_client.invoke_model(
body=body,
modelId=model_id,
)
message = json.loads(response.get("body").read())['output']['message']['content'][0]['text']
print("here1===",message)
return (message,)
class BedrockClaudeMultimodal:
@classmethod
def INPUT_TYPES(s):
@@ -252,7 +375,7 @@ class BedrockClaude:
# The different model providers have individual request and response formats.
# For the format, ranges, and default values for Anthropic Claude, refer to:
# https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages.html
print("prompt input:",prompt)
body = json.dumps(
{
"anthropic_version": "bedrock-2023-05-31",
@@ -280,6 +403,7 @@ class BedrockClaude:
modelId=model_id,
)
message = json.loads(response.get("body").read())["content"][0]["text"]
print("output message:",message)
return (message,)
@@ -450,4 +574,5 @@ NODE_CLASS_MAPPINGS = {
"Bedrock - Claude": BedrockClaude,
"Bedrock - Claude Multimodal": BedrockClaudeMultimodal,
"Bedrock - SDXL": BedrockSDXL,
"Bedrock - Nova": BedrockNovaMultimodal
}
+454
View File
@@ -0,0 +1,454 @@
import json
import os
import re
import base64
from io import BytesIO
from random import randint
import logging
from datetime import datetime
import requests
from retry import retry
from PIL import Image
import numpy as np
import torch
from .session import get_client
import boto3
MAX_RETRY = 3
def generate_images(
inference_params,
model_id="amazon.nova-canvas-v1:0",
region_name="us-east-1",
endpoint_url=None,
output_directory="~/ComfyUI/output",
):
os.makedirs(output_directory, exist_ok=True)
image_count = 1
if "imageGenerationConfig" in inference_params:
if "numberOfImages" in inference_params["imageGenerationConfig"]:
image_count = inference_params["imageGenerationConfig"]["numberOfImages"]
print(f"Generating {image_count} image(s) with {model_id}")
# Display the seed value if one is being used.
if "imageGenerationConfig" in inference_params:
if "seed" in inference_params["imageGenerationConfig"]:
print(
f"Using seed: {inference_params['imageGenerationConfig']['seed']}"
)
bedrock_client_optional_args = (
{} if endpoint_url is None else {"endpoint_url": endpoint_url}
)
bedrock = boto3.client(
service_name="bedrock-runtime",
#region_name=region_name,
**bedrock_client_optional_args,
)
body_json = json.dumps(inference_params, indent=2)
#print("here1===",body_json)
# Write request body to JSON file.
request_file_path = os.path.join(output_directory, "request.json")
with open(request_file_path, "w") as f:
f.write(body_json)
try:
response = bedrock.invoke_model(
body=body_json,
modelId=model_id,
accept="application/json",
contentType="application/json",
)
response_metadata = response.get("ResponseMetadata")
# WRite response metadata to JSON file.
response_metadata_file_path = os.path.join(
output_directory, "response_metadata.json"
)
with open(response_metadata_file_path, "w") as f:
json.dump(response_metadata, f, indent=2)
response_body = json.loads(response.get("body").read())
# Write response body to JSON file.
response_file_path = os.path.join(output_directory, "response_body.json")
with open(response_file_path, "w") as f:
json.dump(response_body, f, indent=2)
# Log the request ID.
print(f"Request ID: {response['ResponseMetadata']['RequestId']}")
# Check for non-exception errors.
if "error" in response_body:
if response_body["error"] == "":
print(
"""Response included 'error' of "" (empty string). This indicates a bug with the Bedrock API."""
)
else:
printf("Error: {response_body['error']}")
return response_body
except Exception as e:
# If e has "response", write it to disk as JSON.
if hasattr(e, "response"):
error_response = e.response
error_response_file_path = os.path.join(
output_directory, "error_response.json"
)
with open(error_response_file_path, "w") as f:
json.dump(error_response, f, indent=2)
print(e)
raise e
bedrock_runtime_client = get_client(service_name="bedrock-runtime")
output_directory = "/home/ubuntu/ComfyUI/output/"
class BedrockNovaTextImage:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"prompt": ("STRING", {"multiline": True}),
"num_images": (
"INT",
{
"default": 1,
"min": 1,
"max": 5,
"step": 1,
"round": 1, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding.
"display": "number",
},
),
"quality": (
[
"standard",
"premium",
],
),
"resolution": (
[
"1024 x 1024",
"768 x 768",
"512 x 512",
"1152 x 768",
"576 x 384",
"768 x 1280",
"384 x 640",
"1280 x 768"
],
),
"cfg_scale": (
"FLOAT",
{
"default": 7.0,
"min": 0.0,
"max": 35.0,
"step": 0.1,
"round": 0.001, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding.
"display": "slider",
},
),
"seed": (
"INT",
{
"default": 0,
"min": 0,
"max": 2147483646,
"step": 1,
"round": 1, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding.
"display": "number",
},
),
},
"optional": {
"negative_prompt":("STRING", {"multiline": True}),
}
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "forward"
CATEGORY = "aws"
@retry(tries=MAX_RETRY)
def forward(self, prompt, negative_prompt,num_images, quality, resolution, cfg_scale, seed):
height, width = map(int, re.findall(r"\d+", resolution))
inference_params ={
"taskType": "TEXT_IMAGE",
"textToImageParams": {"text": prompt,
"negativeText":negative_prompt},
"imageGenerationConfig": {
"numberOfImages": num_images,
"quality": quality,
"cfgScale": cfg_scale,
"height": height,
"width": width,
"seed": seed,
},
}
# Define an output directory with a unique name.
generation_id = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
output_directory = f"output/{generation_id}"
# Generate the image(s).
response_body = generate_images(
inference_params=inference_params,
model_id="amazon.nova-canvas-v1:0",
output_directory=output_directory
)
# response_body = json.loads(response["body"].read())
# base64_image_data = response_body["images"][0]
images = [
np.array(Image.open(BytesIO(base64.b64decode(base64_image))))
for base64_image in response_body.get("images")
]
images = [
torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
for image in images
]
if len(images) == 1:
images = images[0]
else:
images = torch.cat(images, 0)
return (images,)
class BedrockNovaIpAdatper:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE",),
"prompt": ("STRING", {"multiline": True}),
"negative_prompt": ("STRING", {"multiline": True}),
"similarity_strength": (
"FLOAT",
{
"default": 0.9,
"min": 0.2,
"max": 1.0,
"step": 0.1,
"round": 0.01, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding.
"display": "slider",
},
),
"num_images": (
"INT",
{
"default": 1,
"min": 1,
"max": 5,
"step": 1,
"round": 1, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding.
"display": "number",
},
),
"cfg_scale": (
"FLOAT",
{
"default": 8.0,
"min": 1.1,
"max": 10.0,
"step": 0.1,
"round": 0.001, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding.
"display": "slider",
},
),
"resolution": (
[
"1024 x 1024",
"1024 x 1024",
"768 x 768",
"512 x 512",
"1152 x 768",
"576 x 384",
"768 x 1280"
],
),
"seed": (
"INT",
{
"default": 0,
"min": 0,
"max": 2147483646,
"step": 1,
"round": 1, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding.
"display": "number",
},
),
}
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "forward"
CATEGORY = "aws"
@retry(tries=MAX_RETRY)
def forward(self, image, prompt, negative_prompt, similarity_strength, num_images, cfg_scale, resolution,seed):
image = image[0] * 255.0
image = Image.fromarray(image.clamp(0, 255).numpy().round().astype(np.uint8))
height, width = map(int, re.findall(r"\d+", resolution))
buffer = BytesIO()
image.save(buffer, format="PNG")
image_data = buffer.getvalue()
image_base64 = base64.b64encode(image_data).decode("utf-8")
inference_params = {
"taskType": "IMAGE_VARIATION",
"imageVariationParams": {
"images": [image_base64],
"text": prompt,
"negativeText": negative_prompt,
"similarityStrength": similarity_strength,
},
"imageGenerationConfig": {
"numberOfImages": num_images,
"quality": "premium",
"height": height,
"width": width,
"cfgScale": cfg_scale,
"seed": seed
}
}
response_body = generate_images(
inference_params=inference_params,
model_id="amazon.nova-canvas-v1:0",
output_directory=output_directory
)
images = [
np.array(Image.open(BytesIO(base64.b64decode(base64_image))))
for base64_image in response_body.get("images")
]
images = [
torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
for image in images
]
if len(images) == 1:
images = images[0]
else:
images = torch.cat(images, 0)
return (images,)
class BedrockNovaBackgroundPEReplace:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE",),
"prompt": ("STRING", {"multiline": True}),
"mask_prompt": ("STRING", {"multiline": True}),
"num_images": (
"INT",
{
"default": 1,
"min": 1,
"max": 5,
"step": 1,
"round": 1, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding.
"display": "number",
},
),
"cfg_scale": (
"FLOAT",
{
"default": 8.0,
"min": 1.1,
"max": 10.0,
"step": 0.1,
"round": 0.001, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding.
"display": "slider",
},
),
"seed": (
"INT",
{
"default": 0,
"min": 0,
"max": 2147483646,
"step": 1,
"round": 1, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding.
"display": "number",
},
),
}
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "forward"
CATEGORY = "aws"
@retry(tries=MAX_RETRY)
def forward(self, image, prompt, mask_prompt, num_images, cfg_scale,seed):
image = image[0] * 255.0
image = Image.fromarray(image.clamp(0, 255).numpy().round().astype(np.uint8))
buffer = BytesIO()
image.save(buffer, format="PNG")
image_data = buffer.getvalue()
image_base64 = base64.b64encode(image_data).decode("utf-8")
inference_params = {
"taskType": "OUTPAINTING",
"outPaintingParams": {
"image": image_base64,
"text": prompt,
"maskPrompt": mask_prompt,
"outPaintingMode": "PRECISE",
},
"imageGenerationConfig": {
"numberOfImages": num_images,
"quality": "premium",
"cfgScale": cfg_scale,
"seed": seed
}
}
response_body = generate_images(
inference_params=inference_params,
model_id="amazon.nova-canvas-v1:0",
output_directory=output_directory
)
images = [
np.array(Image.open(BytesIO(base64.b64decode(base64_image))))
for base64_image in response_body.get("images")
]
images = [
torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
for image in images
]
if len(images) == 1:
images = images[0]
else:
images = torch.cat(images, 0)
return (images,)
NODE_CLASS_MAPPINGS = {
"Bedrock - Nova Text to Image": BedrockNovaTextImage,
"Bedrock - Nova IpAdapter": BedrockNovaIpAdatper,
"Bedrock - Nova Background Prompt Replace": BedrockNovaBackgroundPEReplace,
}
+227
View File
@@ -0,0 +1,227 @@
import json
import os
import re
import base64
from io import BytesIO
from random import randint
import logging
from datetime import datetime
import requests
from retry import retry
from PIL import Image
import numpy as np
import torch
from .session import get_client
import time
import boto3
MAX_RETRY = 3
def get_default_region():
"""获取默认的 AWS 区域"""
session = boto3.Session()
return session.region_name
def get_account_id():
"""获取当前 AWS 账户 ID"""
sts_client = boto3.client('sts')
return sts_client.get_caller_identity().get('Account')
def is_video_downloaded_for_invocation_job(invocation_job, output_folder="output"):
"""
This function checks if the video file for the given invocation job has been downloaded.
"""
invocation_arn = invocation_job["invocationArn"]
invocation_id = invocation_arn.split("/")[-1]
folder_name = get_folder_name_for_job(invocation_job)
output_folder = os.path.abspath(f"{output_folder}/{folder_name}")
file_name = f"{invocation_id}.mp4"
local_file_path = os.path.join(output_folder, file_name)
return os.path.exists(local_file_path)
def get_folder_name_for_job(invocation_job):
invocation_arn = invocation_job["invocationArn"]
invocation_id = invocation_arn.split("/")[-1]
submit_time = invocation_job["submitTime"]
timestamp = submit_time.astimezone().strftime("%Y-%m-%d_%H-%M-%S")
folder_name = f"{timestamp}_{invocation_id}"
return folder_name
def download_video_for_invocation_arn(invocation_arn, bucket_name, destination_folder):
"""
This function downloads the video file for the given invocation ARN.
"""
invocation_id = invocation_arn.split("/")[-1]
# Create the local file path
file_name = f"{invocation_id}.mp4"
import os
output_folder = os.path.abspath(destination_folder)
local_file_path = os.path.join(output_folder, file_name)
# Ensure the output folder exists
os.makedirs(output_folder, exist_ok=True)
# Create an S3 client
s3 = boto3.client("s3")
# List objects in the specified folder
response = s3.list_objects_v2(Bucket=bucket_name, Prefix=invocation_id)
# Find the first MP4 file and download it.
for obj in response.get("Contents", []):
object_key = obj["Key"]
if object_key.endswith(".mp4"):
print(f"""Downloading "{object_key}"...""")
s3.download_file(bucket_name, object_key, local_file_path)
print(f"Downloaded to {local_file_path}")
return local_file_path
# If we reach this point, no MP4 file was found.
print(f"Problem: No MP4 file was found in S3 at {bucket_name}/{invocation_id}")
def get_job_id_from_arn(invocation_arn):
return invocation_arn.split("/")[-1]
def save_completed_job(job, output_folder="output"):
job_id = get_job_id_from_arn(job["invocationArn"])
output_folder_abs = os.path.abspath(
f"{output_folder}/{get_folder_name_for_job(job)}"
)
# Ensure the output folder exists
os.makedirs(output_folder_abs, exist_ok=True)
status_file = os.path.join(output_folder_abs, "completed.json")
if is_video_downloaded_for_invocation_job(job, output_folder=output_folder):
print(f"Skipping completed job {job_id}, video already downloaded.")
return
s3_bucket_name = (
job["outputDataConfig"]["s3OutputDataConfig"]["s3Uri"]
.split("//")[1]
.split("/")[0]
)
localPath = download_video_for_invocation_arn(
job["invocationArn"], s3_bucket_name, output_folder_abs
)
return localPath
bedrock_runtime_client = get_client(service_name="bedrock-runtime")
region = get_default_region()
account_id = get_account_id()
s3_destination_bucket = f"sagemaker-{region}-{account_id}"
class BedrockNovaVideo:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"prompt": ("STRING", {"multiline": True}),
"dimension": (
[
"1280x720"
],
),
"seed": (
"INT",
{
"default": 0,
"min": 0,
"max": 2147483646,
"step": 1,
"round": 1, # The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding.
"display": "number",
},
),
},
"optional": {
"image": ("IMAGE",),
},
}
RETURN_TYPES = ("STRING",)
FUNCTION = "forward"
CATEGORY = "aws"
@retry(tries=MAX_RETRY)
def forward(self, **kwargs):
prompt = kwargs.get('prompt')
dimension = kwargs.get('dimension')
seed = kwargs.get('seed')
image = kwargs.get('image')
input_image_base64=None
if image is not None:
image = image[0] * 255.0
image = Image.fromarray(image.clamp(0, 255).numpy().round().astype(np.uint8))
buffer = BytesIO()
image.save(buffer, format="PNG")
image_data = buffer.getvalue()
input_image_base64 = base64.b64encode(image_data).decode("utf-8")
textToVideoParams={ "text": prompt}
if input_image_base64 is not None:
textToVideoParams["images"] = [
{
"format": "png", # May be "png" or "jpeg"
"source": {
"bytes": input_image_base64
}
}
]
model_input = {
"taskType": "TEXT_VIDEO",
"textToVideoParams": textToVideoParams,
"videoGenerationConfig": {
"durationSeconds": 6, # 6 is the only supported value currently.
"fps": 24, # 24 is the only supported value currently.
"dimension": dimension, # "1280x720" is the only supported value currently.
"seed": seed # A random seed guarantees we'll get a different result each time this code runs.
},
}
invocation = bedrock_runtime_client.start_async_invoke(
modelId="amazon.nova-reel-v1:0",
modelInput=model_input,
outputDataConfig={"s3OutputDataConfig": {"s3Uri": f"s3://{s3_destination_bucket}"}},
)
invocation_arn = invocation["invocationArn"]
print("\nResponse:")
print(json.dumps(invocation, indent=2, default=str))
save_local_path = ""
# Save the invocation details for later reference. Helpful for debugging and reporting feedback.
while True:
job_update = bedrock_runtime_client.get_async_invoke(invocationArn=invocation_arn)
status = job_update["status"]
start_time = time.time()
if status == "Completed":
save_local_path = save_completed_job(job_update, output_folder="/home/ubuntu/ComfyUI/output/")
break
else:
elapsed_time = time.time() - start_time
if elapsed_time > 600:
print("Job timed out after 60 seconds.")
break
else:
time.sleep(5)
return (save_local_path,)
NODE_CLASS_MAPPINGS = {
"Bedrock - Nova Video": BedrockNovaVideo
}
+62
View File
@@ -0,0 +1,62 @@
import base64
import io
import os
from datetime import datetime
from PIL import Image
def save_base64_image(base64_image, output_directory, base_name="image", suffix="_1"):
"""
Saves a base64 encoded image to a specified output directory with a timestamp and a suffix.
Args:
base64_image (str): The base64 encoded image string.
output_directory (str): The directory where the image will be saved.
suffix (str, optional): A suffix to be added to the filename. Defaults to "_1".
Returns:
PIL.Image.Image: The Pillow Image object representing the saved image.
"""
image_bytes = base64.b64decode(base64_image)
image = Image.open(io.BytesIO(image_bytes))
save_image(image, output_directory, base_name, suffix)
return image
def save_image(image, output_directory, base_name="image", suffix="_1"):
"""
Saves a Pillow Image object to a specified output directory with a timestamp and a suffix.
Args:
image (PIL.Image.Image): The Pillow Image object to be saved.
output_directory (str): The directory where the image will be saved.
suffix (str, optional): A suffix to be added to the filename. Defaults to "_1".
Returns:
None
"""
if not os.path.exists(output_directory):
os.makedirs(output_directory)
file_name = f"{base_name}{suffix}.png"
file_path = os.path.join(output_directory, file_name)
image.save(file_path)
def save_base64_images(base64_images, output_directory, base_name="image"):
"""
Saves a list of base64 encoded images to a specified output directory.
Args:
base64_images (list): A list of base64 encoded image strings.
output_directory (str): The directory where the images will be saved.
Returns:
An array of Pillow Image objects representing the saved images.
"""
images = []
for i, base64_image in enumerate(base64_images):
image = save_base64_image(
base64_image, output_directory, base_name=base_name, suffix=f"_{i+1}"
)
images.append(image)
return images
+1
View File
@@ -18,6 +18,7 @@ class PromptTemplate:
def forward(self, prompt, prompt_template):
output = prompt_template.replace("{prompt}", prompt).replace("[prompt]", prompt)
#print("output prompt:",output)
return (output,)
class PromptTemplateTwoInput:
+423
View File
@@ -0,0 +1,423 @@
import json
import os
import re
import base64
from io import BytesIO
import io
import requests
from retry import retry
import boto3
from PIL import Image
import numpy as np
import torch
import cv2
import folder_paths
from .session import get_client
import torch
from torchvision import transforms
from paddleocr import PaddleOCR, draw_ocr
from paddleocr import PPStructure,draw_structure_result
import tempfile
current_directory = os.path.dirname(os.path.abspath(__file__))
temp_img_path = os.path.join(current_directory, "temp_dir", "AnyText_manual_mask_pos_img.png")
textract = get_client(service_name="textract")
MAX_RETRY = 3
class ImageOCRByTextract:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE",)
}
}
RETURN_TYPES = ("STRING","INT","INT","INT","INT","IMAGE")
RETURN_NAMES = ("Text","Left","Top","Width","Height","Mask Image")
FUNCTION = "forward"
CATEGORY = "aws"
OUTPUT_NODE = True
def ocr_by_textract(self,image_input):
numpy_image = (image_input[0] * 255.0).clamp(0, 255).numpy()
image = image_input[0] * 255.0
image = Image.fromarray(image.clamp(0, 255).numpy().round().astype(np.uint8))
# Get the original dimensions of the image
img_width, img_height = image.size
## Call Textract DetectDocumentText function
byte_stream = io.BytesIO()
image.save(byte_stream, format='PNG')
byte_image = byte_stream.getvalue()
response = textract.detect_document_text(Document={'Bytes': byte_image})
#initial the scan result
result = []
# Create a black mask image of the same size as the original image
mask = np.zeros_like(numpy_image)
all_text=""
# Extract text and bounding box information
for item in response['Blocks']:
if item['BlockType'] == 'LINE':
text = item['Text']
box = item['Geometry']['BoundingBox']
left = int(box['Left'] * img_width)
top = int(box['Top'] * img_height)
width = int(box['Width'] * img_width)
height = int(box['Height']* img_height)
##Add information to the result list
result.append({
'Text': text,
'Left': left,
'Top': top,
'Width': width,
'Height': height
})
all_text=all_text+text
#Draw mask for each text information box
#Specify the coordinates of the top-left and bottom-right corners of the rectangle
temp_mask = np.zeros_like(numpy_image)
x1, y1 = int(left), int(top)
x2, y2 = int(left + width), int(top + height)
# Draw white rectangular boxes on the mask image
cv2.rectangle(temp_mask, (x1, y1), (x2, y2), (255, 255, 255), -1)
mask = cv2.bitwise_or(mask, temp_mask)
# Perform bitwise operation between the mask image and the original image to generate the masked image
masked_img = cv2.bitwise_and(numpy_image, mask)
masked_img = torch.from_numpy(np.array(masked_img).astype(np.float32) / 255.0).unsqueeze(0)
print("result",result)
return all_text,result[0]['Left'],result[0]['Top'],result[0]['Width'],result[0]['Height'],masked_img
@retry(tries=MAX_RETRY)
def forward(self, image):
return self.ocr_by_textract(image)
### for anyText Nodes
class ImageOCRByTextractV2:
@classmethod
def INPUT_TYPES(s):
input_dir = folder_paths.get_input_directory()
files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))]
return {"required":
{
"image": (sorted(files), {"image_upload": True}),
},
}
RETURN_TYPES = ("STRING","STRING","STRING","IMAGE")
RETURN_NAMES = ("Texts","Original Image Path","Mask Image Path","AnyText Mask Image")
FUNCTION = "forward"
CATEGORY = "aws"
OUTPUT_NODE = True
def ocr_by_textract(self,image_input):
ori_image_path = folder_paths.get_annotated_filepath(image_input)
pos_img_path = os.path.join(temp_img_path)
## Convert orignal image to tensor
pil_image = Image.open(ori_image_path)
transform = transforms.Compose([
transforms.ToTensor(), # Convert PIL Image to tensor and normalize pixel values to [0, 1]
])
image_input = transform(pil_image)
## The image input is already in the standard ComfyUI image tensor format
numpy_image = (image_input[0] * 255.0).clamp(0, 255).numpy()
image = image_input[0] * 255.0
image = Image.fromarray(image.clamp(0, 255).numpy().round().astype(np.uint8))
# Get the original dimensions of the image
img_width, img_height = image.size
# Call the Textract DetectDocumentText function
byte_stream = io.BytesIO()
image.save(byte_stream, format='PNG')
byte_image = byte_stream.getvalue()
response = textract.detect_document_text(Document={'Bytes': byte_image})
# Initialize the results list
result = []
# Create a black mask image with the same size as the original image
#mask = np.zeros_like(numpy_image)
mask = np.ones_like(numpy_image) * 255
all_text=""
# Extract text and bounding box information
for item in response['Blocks']:
if item['BlockType'] == 'LINE':
text = item['Text']
box = item['Geometry']['BoundingBox']
left = int(box['Left'] * img_width)
top = int(box['Top'] * img_height)
width = int(box['Width'] * img_width)
height = int(box['Height']* img_height)
# Add the information to the result list
result.append(text)
# Draw mask for each text information box
# Specify the coordinates of the top-left and bottom-right corners of the rectangle
temp_mask = np.zeros_like(numpy_image)
x1, y1 = int(left), int(top)
x2, y2 = int(left + width), int(top + height)
# Draw black rectangular boxes on the mask image
#cv2.rectangle(temp_mask, (x1, y1), (x2, y2), (0, 0, 0), -1)
#mask = cv2.bitwise_or(mask, temp_mask)
cv2.rectangle(mask, (left, top), (left + width, top + height), (0, 0, 0), -1)
# Perform bitwise operation between the mask image and the original image to generate the masked image
masked_img = np.where(mask == 0, 0, numpy_image)
#masked_img = cv2.bitwise_and(numpy_image, mask)
masked_img = torch.from_numpy(np.array(masked_img).astype(np.float32) / 255.0).unsqueeze(0)
#masked_img.save(temp_img_path)
# Convert PyTorch tensor to PIL image
to_pil = transforms.ToPILImage()
pil_image = to_pil(masked_img.squeeze(0))
# save PIL image
pil_image.save(temp_img_path)
all_text="|".join(result)
print("result",result)
return all_text ,ori_image_path,temp_img_path, masked_img
@retry(tries=MAX_RETRY)
def forward(self, image):
return self.ocr_by_textract(image)
## for layer style nodes
class ImageOCRByTextractV4:
@classmethod
def INPUT_TYPES(s):
input_dir = folder_paths.get_input_directory()
files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))]
return {"required":{
"image": ("IMAGE",),
}
}
RETURN_TYPES = ("STRING","STRING","STRING","STRING","STRING","STRING","STRING","IMAGE","IMAGE")
RETURN_NAMES = ("Texts","x_offsets","y_offsets","widths","heights","img_width","img_height","Mask Image","Original Image")
FUNCTION = "forward"
CATEGORY = "aws"
OUTPUT_NODE = True
def convert_to_xywh(self,coordinates):
# Extract all x and y coordinates
x_coords = [coord[0] for coord in coordinates]
y_coords = [coord[1] for coord in coordinates]
# Calculate x offset and y offset
x_offset = min(x_coords)
y_offset = min(y_coords)
# Calculate width and height
width = max(x_coords) - x_offset
height = max(y_coords) - y_offset
print(x_offset,y_offset,width,height)
return int(x_offset), int(y_offset), int(width), int(height)
def ocr_by_paddleocr(self,image_input):
image = image_input[0] * 255.0
image = Image.fromarray(image.clamp(0, 255).numpy().round().astype(np.uint8))
numpy_image = (image_input[0] * 255.0).clamp(0, 255).numpy()
img_width, img_height = image.size
# Create temporary file
with tempfile.NamedTemporaryFile(delete=False, suffix='.png', dir='/tmp/') as temp_file:
temp_filename = temp_file.name
# Save numpy_image as a temporary file
cv2.imwrite(temp_filename, cv2.cvtColor(numpy_image, cv2.COLOR_RGB2BGR))
ocr = PaddleOCR(
det_model_dir='/home/ubuntu/ComfyUI/models/checkpoints/PaddleOCR/det',
rec_model_dir='/home/ubuntu/ComfyUI/models/checkpoints/PaddleOCR/rec',
det_limit_side_len=2048,
use_angle_cls=True,
#use_gpu=False,
)
ocr_results = ocr.ocr(temp_filename, cls=True)[0]
# Create a mask image with the same size as the original image
result = []
masked_img = numpy_image.copy()
all_text=""
x_offsets=[]
y_offsets=[]
widths=[]
heights=[]
# Extract text and bounding box information
for line in ocr_results:
if not isinstance(line, list):
continue
boxes = line[0]
x_offset,y_offset,width,height = self.convert_to_xywh(boxes)
x_offsets.append(str(x_offset))
y_offsets.append(str(y_offset))
widths.append(str(width))
heights.append(str(height))
text = line[1][0]
print("text")
print(text)
result.append(text)
# Draw mask for each text information box
# Specify the coordinates of the top-left and bottom-right corners of the rectangle
x1, y1 = int(x_offset), int(y_offset)
x2, y2 = int(x_offset + width), int(y_offset + height)
# Draw a black rectangle on the mask image
cv2.rectangle(masked_img, (x1, y1), (x2, y2), (0, 0, 0), -1)
masked_img = torch.from_numpy(np.array(masked_img).astype(np.float32) / 255.0).unsqueeze(0)
all_text="|".join(result)
x_offsets="|".join(x_offsets)
y_offsets="|".join(y_offsets)
widths="|".join(widths)
heights="|".join(heights)
print("result",result)
# Add original image output
original_img = image_input
# delete temp files
os.unlink(temp_filename)
return all_text ,x_offsets,y_offsets,widths,heights,img_width,img_height, masked_img,original_img
@retry(tries=MAX_RETRY)
def forward(self, image):
return self.ocr_by_paddleocr(image)
## for layer style nodes
class ImageOCRByTextractV3:
@classmethod
def INPUT_TYPES(s):
input_dir = folder_paths.get_input_directory()
files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))]
return {"required":{
"image": ("IMAGE",),
}
}
RETURN_TYPES = ("STRING","STRING","STRING","STRING","STRING","STRING","STRING","IMAGE","IMAGE")
RETURN_NAMES = ("Texts","x_offsets","y_offsets","widths","heights","img_width","img_height","Mask Image","Original Image")
FUNCTION = "forward"
CATEGORY = "aws"
OUTPUT_NODE = True
def ocr_by_textract(self,image_input):
image = image_input[0] * 255.0
image = Image.fromarray(image.clamp(0, 255).numpy().round().astype(np.uint8))
numpy_image = (image_input[0] * 255.0).clamp(0, 255).numpy()
img_width, img_height = image.size
byte_stream = io.BytesIO()
image.save(byte_stream, format='PNG')
byte_image = byte_stream.getvalue()
response = textract.detect_document_text(Document={'Bytes': byte_image})
result = []
masked_img = numpy_image.copy()
all_text=""
x_offsets=[]
y_offsets=[]
widths=[]
heights=[]
for item in response['Blocks']:
if item['BlockType'] == 'LINE':
text = item['Text']
box = item['Geometry']['BoundingBox']
left = int(box['Left'] * img_width)
x_offsets.append(str(left))
top = int(box['Top'] * img_height)
y_offsets.append(str(top))
width = int(box['Width'] * img_width)
widths.append(str(width))
height = int(box['Height']* img_height)
heights.append(str(height))
result.append(text)
# Draw mask for each text information box
# Specify the coordinates of the top-left and bottom-right corners of the rectangle
x1, y1 = int(left), int(top)
x2, y2 = int(left + width), int(top + height)
# Draw black rectangular boxes on the mask image
cv2.rectangle(masked_img, (x1, y1), (x2, y2), (0, 0, 0), -1)
#masked_img = torch.from_numpy(np.array(masked_img).astype(np.float32) / 255.0).unsqueeze(0)
masked_img = torch.from_numpy(np.array(masked_img).astype(np.float32) / 255.0).unsqueeze(0)
###summary the output
all_text="|".join(result)
x_offsets="|".join(x_offsets)
y_offsets="|".join(y_offsets)
widths="|".join(widths)
heights="|".join(heights)
print("result",result)
# Add original image output
original_img = image_input
return all_text ,x_offsets,y_offsets,widths,heights,img_width,img_height, masked_img,original_img
@retry(tries=MAX_RETRY)
def forward(self, image):
return self.ocr_by_textract(image)
NODE_CLASS_MAPPINGS = {
"Image OCR By Textract": ImageOCRByTextract,
"Image OCR By Textract V2":ImageOCRByTextractV2,
"Image OCR By Textract V3":ImageOCRByTextractV3,
"Image OCR by PaddleOCR": ImageOCRByTextractV4
}
-186
View File
@@ -1,186 +0,0 @@
{
"last_node_id": 5,
"last_link_id": 3,
"nodes": [
{
"id": 1,
"type": "Image From URL",
"pos": [
68,
283
],
"size": {
"0": 315,
"1": 58
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
1,
2
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "Image From URL"
},
"widgets_values": [
"https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg"
]
},
{
"id": 3,
"type": "Bedrock - Claude Multimodal",
"pos": [
461,
123
],
"size": {
"0": 400,
"1": 200
},
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 2
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [
3
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "Bedrock - Claude Multimodal"
},
"widgets_values": [
"Describe this image.",
"anthropic.claude-3-haiku-20240307-v1:0",
2048,
0.5,
1,
250
]
},
{
"id": 2,
"type": "PreviewImage",
"pos": [
484,
400
],
"size": [
266.63671875,
246
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 1
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 5,
"type": "ShowText|pysssss",
"pos": [
916,
154
],
"size": [
323.16015625,
179.2890625
],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": 3,
"widget": {
"name": "text"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": null,
"shape": 6
}
],
"properties": {
"Node name for S&R": "ShowText|pysssss"
},
"widgets_values": [
"",
"The image shows a close-up view of a black ant on a textured surface. The ant appears to be in a crouched or alert position, with its legs and antennae extended. The lighting and focus create a dramatic, almost cinematic effect, highlighting the intricate details of the ant's body and the rough texture of the surface it is standing on."
]
}
],
"links": [
[
1,
1,
0,
2,
0,
"IMAGE"
],
[
2,
1,
0,
3,
0,
"IMAGE"
],
[
3,
3,
0,
5,
0,
"STRING"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1,
"offset": [
-0.125,
0.2421875
]
}
},
"version": 0.4
}
+788
View File
@@ -0,0 +1,788 @@
{
"last_node_id": 35,
"last_link_id": 71,
"nodes": [
{
"id": 15,
"type": "PreviewImage",
"pos": [
1680,
650
],
"size": {
"0": 295.9647216796875,
"1": 320.621337890625
},
"flags": {},
"order": 11,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 23
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 29,
"type": "PreviewImage",
"pos": [
1680,
1040
],
"size": {
"0": 295.9647216796875,
"1": 320.621337890625
},
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 62
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 8,
"type": "PreviewImage",
"pos": [
1680,
230
],
"size": {
"0": 299.51416015625,
"1": 327.3353271484375
},
"flags": {},
"order": 10,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 8
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 31,
"type": "PreviewImage",
"pos": [
2410,
240
],
"size": {
"0": 299.51416015625,
"1": 327.3353271484375
},
"flags": {},
"order": 13,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 65
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 33,
"type": "PreviewImage",
"pos": [
2410,
630
],
"size": {
"0": 299.51416015625,
"1": 327.3353271484375
},
"flags": {},
"order": 14,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 68
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 35,
"type": "PreviewImage",
"pos": [
2420,
1020
],
"size": {
"0": 299.51416015625,
"1": 327.3353271484375
},
"flags": {},
"order": 15,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 71
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 25,
"type": "Reroute",
"pos": [
1132,
727
],
"size": [
75,
26
],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "",
"type": "*",
"link": 54
}
],
"outputs": [
{
"name": "",
"type": "MASK",
"links": [
58,
59,
61,
64,
67,
70
],
"slot_index": 0
}
],
"properties": {
"showOutputText": false,
"horizontal": false
}
},
{
"id": 27,
"type": "Reroute",
"pos": [
1129,
439
],
"size": [
75,
26
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "",
"type": "*",
"link": 55
}
],
"outputs": [
{
"name": "",
"type": "IMAGE",
"links": [
56,
57,
60,
63,
66,
69
],
"slot_index": 0
}
],
"properties": {
"showOutputText": false,
"horizontal": false
}
},
{
"id": 24,
"type": "LayerMask: RemBgUltra",
"pos": [
785,
772
],
"size": {
"0": 315,
"1": 150
},
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 36
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": null,
"shape": 3
},
{
"name": "mask",
"type": "MASK",
"links": [
54
],
"shape": 3,
"slot_index": 1
}
],
"properties": {
"Node name for S&R": "LayerMask: RemBgUltra"
},
"widgets_values": [
8,
0.01,
0.99,
false
]
},
{
"id": 2,
"type": "LoadImage",
"pos": [
431,
439
],
"size": {
"0": 323.62158203125,
"1": 420.20391845703125
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
36,
55
],
"shape": 3,
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"512x512with_background (24).png",
"image"
]
},
{
"id": 7,
"type": "LayerUtility: LaMa",
"pos": [
1300,
320
],
"size": {
"0": 315,
"1": 174
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 56
},
{
"name": "mask",
"type": "MASK",
"link": 58
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": [
8
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "LayerUtility: LaMa"
},
"widgets_values": [
"lama",
"cuda",
false,
25,
6
]
},
{
"id": 14,
"type": "LayerUtility: LaMa",
"pos": [
1300,
710
],
"size": {
"0": 315,
"1": 174
},
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 57
},
{
"name": "mask",
"type": "MASK",
"link": 59
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": [
23
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "LayerUtility: LaMa"
},
"widgets_values": [
"ldm",
"cuda",
false,
25,
6
]
},
{
"id": 28,
"type": "LayerUtility: LaMa",
"pos": [
1290,
1030
],
"size": {
"0": 315,
"1": 174
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 60
},
{
"name": "mask",
"type": "MASK",
"link": 61
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": [
62
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "LayerUtility: LaMa"
},
"widgets_values": [
"zits",
"cuda",
false,
25,
6
]
},
{
"id": 30,
"type": "LayerUtility: LaMa",
"pos": [
2036,
314
],
"size": {
"0": 315,
"1": 174
},
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 63
},
{
"name": "mask",
"type": "MASK",
"link": 64
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": [
65
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "LayerUtility: LaMa"
},
"widgets_values": [
"mat",
"cuda",
false,
25,
6
]
},
{
"id": 32,
"type": "LayerUtility: LaMa",
"pos": [
2037,
724
],
"size": {
"0": 315,
"1": 174
},
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 66
},
{
"name": "mask",
"type": "MASK",
"link": 67
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": [
68
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "LayerUtility: LaMa"
},
"widgets_values": [
"fcf",
"cuda",
false,
25,
6
]
},
{
"id": 34,
"type": "LayerUtility: LaMa",
"pos": [
2043,
1042
],
"size": {
"0": 315,
"1": 174
},
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 69
},
{
"name": "mask",
"type": "MASK",
"link": 70
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": [
71
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "LayerUtility: LaMa"
},
"widgets_values": [
"spread",
"cuda",
false,
25,
6
]
}
],
"links": [
[
8,
7,
0,
8,
0,
"IMAGE"
],
[
23,
14,
0,
15,
0,
"IMAGE"
],
[
36,
2,
0,
24,
0,
"IMAGE"
],
[
54,
24,
1,
25,
0,
"*"
],
[
55,
2,
0,
27,
0,
"*"
],
[
56,
27,
0,
7,
0,
"IMAGE"
],
[
57,
27,
0,
14,
0,
"IMAGE"
],
[
58,
25,
0,
7,
1,
"MASK"
],
[
59,
25,
0,
14,
1,
"MASK"
],
[
60,
27,
0,
28,
0,
"IMAGE"
],
[
61,
25,
0,
28,
1,
"MASK"
],
[
62,
28,
0,
29,
0,
"IMAGE"
],
[
63,
27,
0,
30,
0,
"IMAGE"
],
[
64,
25,
0,
30,
1,
"MASK"
],
[
65,
30,
0,
31,
0,
"IMAGE"
],
[
66,
27,
0,
32,
0,
"IMAGE"
],
[
67,
25,
0,
32,
1,
"MASK"
],
[
68,
32,
0,
33,
0,
"IMAGE"
],
[
69,
27,
0,
34,
0,
"IMAGE"
],
[
70,
25,
0,
34,
1,
"MASK"
],
[
71,
34,
0,
35,
0,
"IMAGE"
]
],
"groups": [],
"config": {},
"extra": {},
"version": 0.4
}
+701
View File
@@ -0,0 +1,701 @@
{
"last_node_id": 52,
"last_link_id": 83,
"nodes": [
{
"id": 25,
"type": "PreviewImage",
"pos": [
1789,
644
],
"size": {
"0": 210,
"1": 246
},
"flags": {},
"order": 13,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 47
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 23,
"type": "Image OCR By Textract",
"pos": [
-654,
420
],
"size": {
"0": 210,
"1": 126
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 36
}
],
"outputs": [
{
"name": "Text",
"type": "STRING",
"links": [
38
],
"shape": 3,
"slot_index": 0
},
{
"name": "Left",
"type": "INT",
"links": [],
"shape": 3,
"slot_index": 1
},
{
"name": "Top",
"type": "INT",
"links": [],
"shape": 3,
"slot_index": 2
},
{
"name": "Width",
"type": "INT",
"links": [],
"shape": 3,
"slot_index": 3
},
{
"name": "Height",
"type": "INT",
"links": [],
"shape": 3,
"slot_index": 4
},
{
"name": "Mask Image",
"type": "IMAGE",
"links": [
49
],
"shape": 3,
"slot_index": 5
}
],
"properties": {
"Node name for S&R": "Image OCR By Textract"
}
},
{
"id": 15,
"type": "Bedrock - Claude",
"pos": [
-322,
948
],
"size": {
"0": 400,
"1": 200
},
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "prompt",
"type": "STRING",
"link": 30,
"widget": {
"name": "prompt"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [
59
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "Bedrock - Claude"
},
"widgets_values": [
"",
"anthropic.claude-3-haiku-20240307-v1:0",
2048,
1,
1,
71
]
},
{
"id": 40,
"type": "String",
"pos": [
347,
1165
],
"size": {
"0": 315,
"1": 58
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [
58
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "String"
},
"widgets_values": [
"Yes"
]
},
{
"id": 48,
"type": "MaskPreview+",
"pos": [
756,
302
],
"size": {
"0": 210,
"1": 246
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "mask",
"type": "MASK",
"link": 69
}
],
"properties": {
"Node name for S&R": "MaskPreview+"
}
},
{
"id": 39,
"type": "Compare",
"pos": [
757,
1079
],
"size": {
"0": 315,
"1": 78
},
"flags": {},
"order": 10,
"mode": 0,
"inputs": [
{
"name": "a",
"type": "*",
"link": 59
},
{
"name": "b",
"type": "*",
"link": 58
}
],
"outputs": [
{
"name": "B",
"type": "BOOLEAN",
"links": [
78
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "Compare"
},
"widgets_values": [
"a == b"
]
},
{
"id": 32,
"type": "ImageToMask",
"pos": [
-296,
518
],
"size": {
"0": 315,
"1": 58
},
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 49
}
],
"outputs": [
{
"name": "MASK",
"type": "MASK",
"links": [
50,
79
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "ImageToMask"
},
"widgets_values": [
"green"
]
},
{
"id": 51,
"type": "If ANY execute A else B",
"pos": [
947,
648
],
"size": {
"0": 210,
"1": 66
},
"flags": {},
"order": 11,
"mode": 0,
"inputs": [
{
"name": "ANY",
"type": "*",
"link": 78
},
{
"name": "IF_TRUE",
"type": "*",
"link": 79
},
{
"name": "IF_FALSE",
"type": "*",
"link": 80
}
],
"outputs": [
{
"name": "?",
"type": "*",
"links": [
81
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "If ANY execute A else B"
}
},
{
"id": 24,
"type": "LayerUtility: LaMa",
"pos": [
1384,
959
],
"size": {
"0": 315,
"1": 174
},
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 83
},
{
"name": "mask",
"type": "MASK",
"link": 81,
"slot_index": 1
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": [
47
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "LayerUtility: LaMa"
},
"widgets_values": [
"lama",
"cuda",
false,
25,
6
]
},
{
"id": 47,
"type": "SolidMask",
"pos": [
-312,
227
],
"size": {
"0": 315,
"1": 106
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "MASK",
"type": "MASK",
"links": [
69,
80
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "SolidMask"
},
"widgets_values": [
0,
1000,
662
]
},
{
"id": 33,
"type": "MaskPreview+",
"pos": [
511,
394
],
"size": {
"0": 210,
"1": 246
},
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "mask",
"type": "MASK",
"link": 50
}
],
"properties": {
"Node name for S&R": "MaskPreview+"
}
},
{
"id": 17,
"type": "Prompt Template",
"pos": [
-400,
648
],
"size": {
"0": 521.4636840820312,
"1": 208.76742553710938
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "prompt",
"type": "STRING",
"link": 38,
"widget": {
"name": "prompt"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [
30
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "Prompt Template"
},
"widgets_values": [
"",
"判断如下文本中是否包含暴力,恐怖,色情,政治的内容\n[prompt]\n如果包含,返回\"Yes\", 否则返回\"No\"\n直接返回结果,不要包含其他任务内容"
]
},
{
"id": 4,
"type": "LoadImage",
"pos": [
-1091,
346
],
"size": {
"0": 315,
"1": 314
},
"flags": {},
"order": 2,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
36,
82
],
"shape": 3,
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": [],
"shape": 3,
"slot_index": 1
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"textract_demo005 (1).png",
"image"
]
},
{
"id": 52,
"type": "Reroute",
"pos": [
726.544712798572,
819.4428242873535
],
"size": [
75,
26
],
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "",
"type": "*",
"link": 82
}
],
"outputs": [
{
"name": "",
"type": "IMAGE",
"links": [
83
],
"slot_index": 0
}
],
"properties": {
"showOutputText": false,
"horizontal": false
}
}
],
"links": [
[
30,
17,
0,
15,
0,
"STRING"
],
[
36,
4,
0,
23,
0,
"IMAGE"
],
[
38,
23,
0,
17,
0,
"STRING"
],
[
47,
24,
0,
25,
0,
"IMAGE"
],
[
49,
23,
5,
32,
0,
"IMAGE"
],
[
50,
32,
0,
33,
0,
"MASK"
],
[
58,
40,
0,
39,
1,
"*"
],
[
59,
15,
0,
39,
0,
"*"
],
[
69,
47,
0,
48,
0,
"MASK"
],
[
78,
39,
0,
51,
0,
"*"
],
[
79,
32,
0,
51,
1,
"*"
],
[
80,
47,
0,
51,
2,
"*"
],
[
81,
51,
0,
24,
1,
"MASK"
],
[
82,
4,
0,
52,
0,
"*"
],
[
83,
52,
0,
24,
0,
"IMAGE"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 0.7513148009015782,
"offset": {
"0": -256.5145409235732,
"1": -365.5770235061039
}
}
},
"version": 0.4
}
+777
View File
@@ -0,0 +1,777 @@
{
"last_node_id": 41,
"last_link_id": 58,
"nodes": [
{
"id": 11,
"type": "LayerStyle: DropShadow",
"pos": [
1364,
274
],
"size": {
"0": 315,
"1": 266
},
"flags": {},
"order": 11,
"mode": 0,
"inputs": [
{
"name": "background_image",
"type": "IMAGE",
"link": 46
},
{
"name": "layer_image",
"type": "IMAGE",
"link": 28
},
{
"name": "layer_mask",
"type": "MASK",
"link": null
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": [
20
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "LayerStyle: DropShadow"
},
"widgets_values": [
true,
"normal",
75,
8,
8,
2,
8,
"#000000"
]
},
{
"id": 24,
"type": "LayerUtility: LaMa",
"pos": [
833,
102
],
"size": {
"0": 315,
"1": 174
},
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 44
},
{
"name": "mask",
"type": "MASK",
"link": 56
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": [
45,
46,
47
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "LayerUtility: LaMa"
},
"widgets_values": [
"lama",
"cuda",
false,
25,
6
]
},
{
"id": 32,
"type": "ImageToMask",
"pos": [
-25.47701164649243,
402.810252108054
],
"size": {
"0": 315,
"1": 58
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 49
}
],
"outputs": [
{
"name": "MASK",
"type": "MASK",
"links": [
50,
56
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "ImageToMask"
},
"widgets_values": [
"green"
]
},
{
"id": 33,
"type": "MaskPreview+",
"pos": [
601,
267
],
"size": {
"0": 210,
"1": 246
},
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "mask",
"type": "MASK",
"link": 50
}
],
"properties": {
"Node name for S&R": "MaskPreview+"
}
},
{
"id": 25,
"type": "PreviewImage",
"pos": [
1452,
-35
],
"size": {
"0": 210,
"1": 246
},
"flags": {},
"order": 10,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 47
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 2,
"type": "PreviewImage",
"pos": [
1904,
273
],
"size": {
"0": 714.4351806640625,
"1": 444.59918212890625
},
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 20
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 23,
"type": "Image OCR By Textract",
"pos": [
-654,
420
],
"size": {
"0": 210,
"1": 126
},
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 36
}
],
"outputs": [
{
"name": "Text",
"type": "STRING",
"links": [
38
],
"shape": 3,
"slot_index": 0
},
{
"name": "Left",
"type": "INT",
"links": [
39
],
"shape": 3,
"slot_index": 1
},
{
"name": "Top",
"type": "INT",
"links": [
40
],
"shape": 3,
"slot_index": 2
},
{
"name": "Width",
"type": "INT",
"links": [
41
],
"shape": 3,
"slot_index": 3
},
{
"name": "Height",
"type": "INT",
"links": [
42
],
"shape": 3,
"slot_index": 4
},
{
"name": "Mask Image",
"type": "IMAGE",
"links": [
49
],
"shape": 3,
"slot_index": 5
}
],
"properties": {
"Node name for S&R": "Image OCR By Textract"
}
},
{
"id": 15,
"type": "Bedrock - Claude",
"pos": [
305,
811
],
"size": {
"0": 400,
"1": 200
},
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "prompt",
"type": "STRING",
"link": 30,
"widget": {
"name": "prompt"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [
29
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "Bedrock - Claude"
},
"widgets_values": [
"",
"anthropic.claude-3-haiku-20240307-v1:0",
2048,
1,
1,
71
]
},
{
"id": 9,
"type": "LayerUtility: SimpleTextImage",
"pos": [
1316,
619
],
"size": {
"0": 392.5400695800781,
"1": 406.2833557128906
},
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "size_as",
"type": "*",
"link": 45
},
{
"name": "text",
"type": "STRING",
"link": 29,
"widget": {
"name": "text"
}
},
{
"name": "x_offset",
"type": "INT",
"link": 39,
"widget": {
"name": "x_offset"
}
},
{
"name": "y_offset",
"type": "INT",
"link": 40,
"widget": {
"name": "y_offset"
}
},
{
"name": "width",
"type": "INT",
"link": 41,
"widget": {
"name": "width"
}
},
{
"name": "height",
"type": "INT",
"link": 42,
"widget": {
"name": "height"
}
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": [
28
],
"shape": 3,
"slot_index": 0
},
{
"name": "mask",
"type": "MASK",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LayerUtility: SimpleTextImage"
},
"widgets_values": [
"Are you going to Scarborough Fair?\nParsley, sage, rosemary, and thyme.\nRemember me to one who lives there,\nShe once was the true love of mine.",
"Alibaba-PuHuiTi-Heavy.ttf",
"left",
80,
8,
50,
"#FFFFFF",
0,
"#FF8000",
0,
300,
512,
512
]
},
{
"id": 40,
"type": "String",
"pos": [
-99.1735806485697,
1134.5376124549446
],
"size": {
"0": 315,
"1": 58
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [
58
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "String"
},
"widgets_values": [
"浪人"
]
},
{
"id": 39,
"type": "Compare",
"pos": [
306,
1091
],
"size": {
"0": 315,
"1": 78
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "a",
"type": "*",
"link": 57
},
{
"name": "b",
"type": "*",
"link": 58
}
],
"outputs": [
{
"name": "B",
"type": "BOOLEAN",
"links": null,
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "Compare"
},
"widgets_values": [
"a == b"
]
},
{
"id": 4,
"type": "LoadImage",
"pos": [
-1091,
346
],
"size": {
"0": 315,
"1": 314
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
36,
44
],
"shape": 3,
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": [],
"shape": 3,
"slot_index": 1
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"textract_demo005.png",
"image"
]
},
{
"id": 17,
"type": "Prompt Template",
"pos": [
-398,
661
],
"size": {
"0": 521.4636840820312,
"1": 208.76742553710938
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "prompt",
"type": "STRING",
"link": 38,
"widget": {
"name": "prompt"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [
30,
57
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "Prompt Template"
},
"widgets_values": [
"",
"翻译如下英文为日语\n[prompt]\n直接返回翻译后文本"
]
}
],
"links": [
[
20,
11,
0,
2,
0,
"IMAGE"
],
[
28,
9,
0,
11,
1,
"IMAGE"
],
[
29,
15,
0,
9,
1,
"STRING"
],
[
30,
17,
0,
15,
0,
"STRING"
],
[
36,
4,
0,
23,
0,
"IMAGE"
],
[
38,
23,
0,
17,
0,
"STRING"
],
[
39,
23,
1,
9,
2,
"INT"
],
[
40,
23,
2,
9,
3,
"INT"
],
[
41,
23,
3,
9,
4,
"INT"
],
[
42,
23,
4,
9,
5,
"INT"
],
[
44,
4,
0,
24,
0,
"IMAGE"
],
[
45,
24,
0,
9,
0,
"*"
],
[
46,
24,
0,
11,
0,
"IMAGE"
],
[
47,
24,
0,
25,
0,
"IMAGE"
],
[
49,
23,
5,
32,
0,
"IMAGE"
],
[
50,
32,
0,
33,
0,
"MASK"
],
[
56,
32,
0,
24,
1,
"MASK"
],
[
57,
17,
0,
39,
0,
"*"
],
[
58,
40,
0,
39,
1,
"*"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1.1000000000000005,
"offset": {
"0": 986.1125011031153,
"1": -372.55607836403607
}
}
},
"version": 0.4
}