From fdd3f73a71ea40492fa3cdef0b4c9107a8077bf2 Mon Sep 17 00:00:00 2001 From: Shubz World <176368083+mithamunda@users.noreply.github.com> Date: Thu, 2 Jan 2025 00:07:37 +0530 Subject: [PATCH 1/5] Update together_vision_node.py fixed resize issue before processing image. --- together_vision_node.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/together_vision_node.py b/together_vision_node.py index c374b99..0e665ab 100644 --- a/together_vision_node.py +++ b/together_vision_node.py @@ -119,6 +119,17 @@ def encode_image(self, image_tensor: torch.Tensor) -> str: if pil_image.size[0] * pil_image.size[1] == 0: raise ValueError("Invalid image dimensions") + # Only resize if the image is larger than 1024 in either dimension + max_size = 1024 + original_width, original_height = pil_image.size + + if original_width > max_size or original_height > max_size: + # Calculate scaling factor to maintain exact aspect ratio + scale = max_size / max(original_width, original_height) + new_width = int(original_width * scale) + new_height = int(original_height * scale) + pil_image = pil_image.resize((new_width, new_height), Image.Resampling.LANCZOS) + # Convert to base64 buffered = io.BytesIO() pil_image.save(buffered, format="PNG") From 9965b7895e8efba0321eb041781a978c7f8edd71 Mon Sep 17 00:00:00 2001 From: Shubz World <176368083+mithamunda@users.noreply.github.com> Date: Thu, 2 Jan 2025 00:35:12 +0530 Subject: [PATCH 2/5] Add files via upload --- __init__.py | 6 ++ together_image_node.py | 209 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 together_image_node.py diff --git a/__init__.py b/__init__.py index a8604c0..41bec2a 100644 --- a/__init__.py +++ b/__init__.py @@ -1,5 +1,11 @@ from .together_vision_node import TogetherVisionNode +from .together_image_node import TogetherImageNode NODE_CLASS_MAPPINGS = { "TogetherVisionNode": TogetherVisionNode, # Together Vision node + "Together Image 🎨": TogetherImageNode, # Together Image Generation node +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "Together Image 🎨": "Together Image Generator" } diff --git a/together_image_node.py b/together_image_node.py new file mode 100644 index 0000000..7e494b0 --- /dev/null +++ b/together_image_node.py @@ -0,0 +1,209 @@ +import os +import base64 +import io +from PIL import Image +import numpy as np +import torch +from together import Together +import logging +from typing import Optional, Tuple +from dotenv import load_dotenv + +# Set up logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# Load environment variables +load_dotenv() + +class TogetherImageNode: + """ + A custom node for ComfyUI that uses Together AI's FLUX model for image generation. + """ + + def __init__(self): + self.client = None + self.last_request_time = 0 + self.min_request_interval = 1.0 # Minimum seconds between requests + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "prompt": ("STRING", { + "default": "", + "multiline": True + }), + "api_key": ("STRING", { + "default": "", + "multiline": False + }), + "width": ("INT", { + "default": 1024, + "min": 512, + "max": 2048, + "step": 256 + }), + "height": ("INT", { + "default": 1024, + "min": 512, + "max": 2048, + "step": 256 + }), + "seed": ("INT", { + "default": 0, + "min": 0, + "max": 0xffffffffffffffff + }), + "num_images": ("INT", { + "default": 1, + "min": 1, + "max": 4, + "step": 1 + }) + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "generate_image" + CATEGORY = "image" + OUTPUT_NODE = True + + def get_api_key(self, provided_key: str) -> str: + """Get API key with validation.""" + api_key = provided_key or os.getenv('TOGETHER_API_KEY') + if not api_key: + raise ValueError("API key not provided. Please provide an API key or set TOGETHER_API_KEY environment variable.") + return api_key + + def b64_to_image(self, b64_string: str) -> torch.Tensor: + """Convert base64 string to torch tensor image.""" + try: + # Decode base64 to image + image_data = base64.b64decode(b64_string) + + # Log base64 string length for debugging + logger.info(f"Decoded base64 string length: {len(image_data)} bytes") + + image = Image.open(io.BytesIO(image_data)) + + # Log image details + logger.info(f"Image mode: {image.mode}, size: {image.size}") + + # Convert to RGB if needed + if image.mode != 'RGB': + logger.info(f"Converting image from {image.mode} to RGB") + image = image.convert('RGB') + + # Convert to numpy array + image_np = np.array(image) + + # Log numpy array details + logger.info(f"Numpy array shape: {image_np.shape}, dtype: {image_np.dtype}") + + # Ensure the image is in the correct format for ComfyUI + # ComfyUI expects [height, width, channels] format with values 0-255 + if image_np.ndim != 3 or image_np.shape[2] != 3: + raise ValueError(f"Unexpected image shape: {image_np.shape}") + + # Ensure uint8 type and correct range + image_np = np.clip(image_np, 0, 255).astype(np.uint8) + + # Convert to torch tensor in ComfyUI's expected format + # [batch, channels, height, width] + image_tensor = torch.from_numpy(image_np).permute(2, 0, 1).float() / 255.0 + image_tensor = image_tensor.unsqueeze(0) # Add batch dimension + + # Log tensor details + logger.info(f"Final tensor shape: {image_tensor.shape}, dtype: {image_tensor.dtype}") + + return image_tensor + + except Exception as e: + logger.error(f"Error converting base64 to image: {str(e)}") + raise ValueError(f"Failed to convert base64 to image: {str(e)}") + + def generate_image(self, prompt: str, api_key: str, width: int, height: int, + seed: int, num_images: int) -> Tuple[torch.Tensor]: + """ + Generate images using Together AI's FLUX model. + """ + try: + # Validate inputs + if not prompt or prompt.strip() == "": + raise ValueError("Prompt cannot be empty") + + # Validate width and height + if width < 512 or width > 2048 or height < 512 or height > 2048: + raise ValueError(f"Invalid image dimensions. Width and height must be between 512 and 2048. Got {width}x{height}") + + # Validate number of images + if num_images < 1 or num_images > 4: + raise ValueError(f"Number of images must be between 1 and 4. Got {num_images}") + + # Initialize API client + api_key = self.get_api_key(api_key) + if self.client is None: + self.client = Together(api_key=api_key) + + # Make API call + response = self.client.images.generate( + prompt=prompt, + model="black-forest-labs/FLUX.1-schnell-Free", + width=width, + height=height, + steps=4, # Fixed to 4 steps as per model requirements + n=num_images, + seed=seed, + response_format="b64_json" + ) + + # Process response + if not response.data: + raise ValueError("No images generated in response") + + # Convert all images to tensors + image_tensors = [] + for img_data in response.data: + if not hasattr(img_data, 'b64_json'): + logger.warning("Skipping image without base64 data") + continue + try: + # Decode base64 + image_data = base64.b64decode(img_data.b64_json) + image = Image.open(io.BytesIO(image_data)) + + # Convert to numpy array + image_np = np.array(image) + + # Ensure uint8 and correct shape + image_np = np.clip(image_np, 0, 255).astype(np.uint8) + + # Convert to torch tensor in ComfyUI format [batch, height, width, channels] + image_tensor = torch.from_numpy(image_np).permute(2, 0, 1).float() / 255.0 + image_tensor = image_tensor.permute(1, 2, 0) # Change to [height, width, channels] + + image_tensors.append(image_tensor) + except Exception as img_error: + logger.error(f"Failed to process an image: {str(img_error)}") + + if not image_tensors: + raise ValueError("Failed to process any images from response") + + # Stack images + final_tensor = torch.stack(image_tensors) + + return (final_tensor,) + + except Exception as e: + logger.error(f"Image generation failed: {str(e)}") + raise ValueError(f"Failed to generate image: {str(e)}") + +# Node registration +NODE_CLASS_MAPPINGS = { + "Together Image 🎨": TogetherImageNode +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "Together Image 🎨": "Together Image Generator" +} From b48b0b42d2a9a1a166f891b1c00df759ddc4276a Mon Sep 17 00:00:00 2001 From: Shubz World <176368083+mithamunda@users.noreply.github.com> Date: Thu, 2 Jan 2025 00:38:41 +0530 Subject: [PATCH 3/5] Add files via upload --- images/Latest.png | Bin 0 -> 803985 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/Latest.png diff --git a/images/Latest.png b/images/Latest.png new file mode 100644 index 0000000000000000000000000000000000000000..7bbd3ffa66e90d325a61364e3b86118fe7486ff8 GIT binary patch literal 803985 zcmeFZc|6qX`#;{|R1%6(_907-eH&y;DI|nM*%}NcV=#lUSJsdvL}hD1S+ehIWf@DB z%Dx^;WY50yyI(_{>U7@k^ZqP<{2t%WdDLl)*Xv%c`?{~|c|EV|zI`vNDv%$fJGg7t zE^;MBd5vAW4$AG?wHJM0ANXe2Jvw{WE=n%j^E$S8E3Abn+6uqRnH7z|*&1T3Opt~s z6V}sWJlkKOQ5Xy27jPa{=$AOw)4I;A&=Z0@thSEUNY>M=SFFsCw&qCeRTK_og;51h zvGTB5TS1TVii+}yiHJzRMMNbe-~tlDVxl~};u3tqaB(p~VPP?G2?4l}s6G!X4&?-z z(iN5v
|Yl6fQhhho!
zdRl;o6@{_3v)vvg24M*e4`qokgGRJ7S|t@Zh4bJ?XlAUZg+SNl2xuB&uAprzJKLXM
z2Xi*YqO5Jf)c$Nl^NNO&s=`hyP;bJ}9MD#_hA4~)(gBPFYRwvJWsStzq9Ca`vqFM_
zL1TiMT47 we(DV g{jEc0vbq?!zWaMVNs(T|7S3Eoh#@4G>~m+`S%!;Yd7k?iF2n2<%*l
z(#H8_e!m+oq?zSoqRHpo*UJ`KLN7hGAIc2AlHzvycReLG#R;Z6`p@MyU;p-&AV^@)
z+>oU~yiMNJ{kVtxi;AR%PM6SWz)%(R4DfRq?*mk!;pgiXEOJ#G7bERIF^1i8l`dM2
zJl@FkqQ|uC1nKN8xBgq0EDueGw9|m=HLpIMz~@X|^K4SF!AW+kNNQlIUHw77^yB$<
zdH411GB?cCh^NI1`llc1M~}I3`I0z(e(F p{IjoHa$8Cajs%te@=64*Nj13Lw|u(w*wi1#LU^B&P0-L%QJmXYUQ{W%?!~5f
zm>$CSBMfh@%Z_z1dyNIPQmm0ql&($ajwUE^c#~YKwP`m9>Ys_}S8rC4)b|oCMJbqV
z@wq#<-LkUqFJrcAR>y$C1sq6CO2|>nL)smk#6svjY91cO*z3Z|Bj(i=QBPh
zVKZxEDnz<-wKP~^LeS=jk#W)a7rok(!#81zs$}!x6)S$nxSizOE4n2>ZN++idpA`b
zbh%2z7(u)O+~ksa=V;#pP)Gx=3$u%9wFm|GhxWx6cQr35ykNE)f6+?W9b7(D;Bnud
zlSXo453NBaVL9!67F5PCXmsV`tP(1e$}{CXzQ;~mlZlptG}Da)p2ba;4kD-gqAzdB
zlA}9#A{2vhIbl1OkI8`2#&wsqBa*pZJziw90&HP6>~hayJp#)sru(Ppt53q@;k-v3
zdF |lZ{Gx2YV!H5=qW;cjuKP1RTIDD;
zikuf;g)C=F#RwqJ7wewX=EFKAcnTb`)+I^_A;hN6-&6X%j4hxVn&N
zAmb0#2S~t*C@O%?&_BwJ54a6r40ha$k6hB!MqdI$a*KCjN;aj-`(epi=Gu$3^2^25yZyF97Y`-+C0TU6;hay=FmO0VGu=)|bvIALe_RR}uX
z9;|91I=Sd;3nQ!dnI7Ji7z#d@)c1Jmm<$=*U#0z!Q#5d-OnGRjfOAp0FX%w7rsc}5
z{Zr+Ely$1X@7^O?8EHFduB8y}c$1x7FwT!Wz?TutEtU9Tx>KiYzd(8&10UhlG&RbG!h2K*JSdp$)rUUP4un3{?$
z<1_UXgKsqJ48wX4^OntIPTN%l)S%O1R_r7uzu)_JLex%VBXl1o_tJ!~QsVh5%nc+c
z7PvoJdA7@?XXv!g@%OoPSX-TTdx&_pc@jRDJ}#-!vS#`!vXtMAv)WoLWmOue*v6cj
z{aR!9W>8;Wk8Pa_F7O?OHNPC&n^P;VsYMnE0W|4K?D?$OL=^G
z(W3%iOqYe+*qUQv@|+xi?pZ_tDIPMs@kealZVg9P{lgerd)Zqh>i$7$nC168TG{)7
zH_#UyT0Ol1iu)90WSb3ntB4-~HnZ+eTSd=Q(FlauIar)r2DZsc!4Uui#wU+scUw2~
z0Oou%dlb)=z|vl^4$vHz+FLAbIOFiwr-QsO{&Xn)mreJvUz{Xgx3iZGQa