Initial commit: upload_images and classifiers nodes

This commit is contained in:
David Strand
2024-12-12 00:11:39 -06:00
parent 2939a34e8b
commit 5df47d60d5
5 changed files with 197 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Kudelabs.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+12
View File
@@ -0,0 +1,12 @@
from .upload_images import UploadImages
from .classifiers import *
NODE_CLASS_MAPPINGS = {
"UploadImagesToInstantStudio": UploadImages,
'HuggingFace Classify': HFClassify,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"UploadImagesToInstantStudio": "Upload Images to Instant Studio"
}
+67
View File
@@ -0,0 +1,67 @@
import os
import folder_paths
import torch
from torchvision import transforms
import numpy as np
from PIL import Image
def _tensor_check_image(image):
if image.ndim != 4:
raise ValueError(f"Expected NHWC tensor, but found {image.ndim} dimensions")
if image.shape[-1] not in (1, 3, 4):
raise ValueError(f"Expected 1, 3 or 4 channels for image, but found {image.shape[-1]} channels")
return
def tensor2pil(image):
_tensor_check_image(image)
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(0), 0, 255).astype(np.uint8))
def numpy2pil(image):
return Image.fromarray(np.clip(255. * image.squeeze(0), 0, 255).astype(np.uint8))
def to_pil(image):
if isinstance(image, Image.Image):
return image
if isinstance(image, torch.Tensor):
return tensor2pil(image)
if isinstance(image, np.ndarray):
return numpy2pil(image)
raise ValueError(f"Cannot convert {type(image)} to PIL.Image")
class HFClassify:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"hf_classifier_model": ("TRANSFORMERS_CLASSIFIER",),
"image_to_classify": ("IMAGE",),
"target_label": (["male", "female"],), # Toggle for selecting 'male' or 'female'
}
}
RETURN_TYPES = ("FLOAT", "INT") # Return the raw score as a float and the rounded score as an int
RETURN_NAMES = ("raw_score", "rounded_score")
FUNCTION = "classify"
def classify(self, hf_classifier_model, image_to_classify, target_label):
# Convert image to PIL format
image = to_pil(image_to_classify)
# Get classification result
result = hf_classifier_model(image)
# Extract the score for the target label
target_result = next((entry for entry in result if entry["label"] == target_label), None)
if target_result:
score = target_result["score"]
else:
# If the target label isn't in the results, default score to 0
score = 0.0
# Round the score to 0 or 1
rounded_score = int(round(score))
# Return the raw score as a float and the rounded score as an integer
return (score, rounded_score)
+3
View File
@@ -0,0 +1,3 @@
numpy
Pillow
Requests
+94
View File
@@ -0,0 +1,94 @@
import os
import json
from comfy.cli_args import args
from PIL import Image
from PIL.PngImagePlugin import PngInfo
import numpy as np
import requests
class UploadImages:
def __init__(self):
self.type = "output"
self.compress_level = 4
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"images": ("IMAGE", {"tooltip": "The images to upload."}),
"note": ("STRING", {"tooltip": "A note to associate with the image."}),
"email": ("STRING", {"tooltip": "The email to use for authentication."}),
"url": ("STRING", {"default": "https://toolkit.instantstudio.ai/api/v1/creations", "tooltip": "The URL to post the image to."}),
"api_key": ("STRING", {"default": "TODO", "tooltip": "The API Key to use for authentication."})
},
"hidden": {
"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"
},
}
RETURN_TYPES = ()
FUNCTION = "upload_images"
OUTPUT_NODE = True
CATEGORY = "Instant Studio"
DESCRIPTION = "Uploads the input images to Instant Studio."
def upload_images(self, images, note=None, email=None, url=None, api_key=None, prompt=None, extra_pnginfo=None):
uuids = None
succeeded_count = 0
failed_count = 0
temp_file_paths = list()
try:
for image in images:
i = 255. * image.cpu().numpy()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
metadata = None
if not args.disable_metadata:
metadata = PngInfo()
if prompt is not None:
for key in prompt:
if "inputs" in prompt[key] and "api_key" in prompt[key]["inputs"]:
prompt[key]["inputs"]["api_key"] = "TODO"
metadata.add_text("prompt", json.dumps(prompt))
if extra_pnginfo is not None:
if "workflow" in extra_pnginfo and "nodes" in extra_pnginfo["workflow"]:
for node in extra_pnginfo['workflow']['nodes']:
if node['type'] == 'UploadImagesToInstantStudio':
node['widgets_values'] = []
for x in extra_pnginfo:
metadata.add_text(x, json.dumps(extra_pnginfo[x]))
temp_file = None
import tempfile
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as temp_file:
temp_file_path = temp_file.name
img.save(temp_file_path, pnginfo=metadata, compress_level=self.compress_level)
temp_file_paths.append(temp_file_path)
files = [("files[]", open(temp_file_path, "rb")) for temp_file_path in temp_file_paths]
headers = {
"Authorization": f"Bearer {api_key}",
"X-User-Email": email
}
params = {
"note": note,
}
response = requests.post(url, headers=headers, params=params, files=files)
if response.status_code != 200:
failed_count += 1
raise Exception(f"Failed to post image: {response.status_code} - {response.text}")
else:
succeeded_count += 1
response_data = response.json()
uuids = response_data.get("uuids")
finally:
for temp_file_path in temp_file_paths:
if temp_file_path and os.path.exists(temp_file_path):
os.remove(temp_file_path)
print(f"Successfully uploaded {succeeded_count} images to Instant Studio: {uuids}")
return { "ui": { "uuids": uuids }, "result": (uuids,) }