| | import os |
| | import requests |
| | from typing import Optional |
| | from PIL import Image |
| | from io import BytesIO |
| | from diffusers.utils import load_image |
| | from diffusers.modular_pipelines import ( |
| | PipelineState, |
| | ModularPipelineBlocks, |
| | InputParam, |
| | OutputParam, |
| | ) |
| |
|
| | IDEOGRAM_GENERATE_URL = "https://api.ideogram.ai/v1/ideogram-v3/generate" |
| |
|
| | class CreateCharacterImageBlock(ModularPipelineBlocks): |
| | |
| | @property |
| | def inputs(self) -> list[InputParam]: |
| | return [ |
| | InputParam( |
| | name="prompt", |
| | type_hint=str, |
| | description="Text prompt describing the desired image." |
| | ), |
| | InputParam( |
| | name="character_image", |
| | type_hint=Image.Image, |
| | description="A single PIL Image to use as the character reference." |
| | ), |
| | ] |
| |
|
| | @property |
| | def intermediate_outputs(self) -> list[OutputParam]: |
| | return [ |
| | OutputParam( |
| | name="image", |
| | type_hint=Image.Image, |
| | description="Generated image." |
| | ), |
| | OutputParam( |
| | name="image_url", |
| | type_hint=str, |
| | description="URL to the generated image." |
| | ) |
| | ] |
| | |
| | @staticmethod |
| | def generate_image_with_character_reference( |
| | prompt: str, |
| | character_image: Image.Image, *, |
| | style_type: str = "AUTO", |
| | seed: Optional[int] = None, |
| | resolution: Optional[str] = "1024x1024", |
| | request_timeout: int = 60, |
| | ) -> str: |
| | """ |
| | Generate an image using Ideogram's character reference feature. |
| | |
| | Args: |
| | api_key: Your Ideogram API key. |
| | prompt: Text prompt describing the desired image. |
| | character_image: A single PIL Image to use as the character reference. |
| | style_type: Ideogram style selection. "AUTO" lets the API decide. |
| | seed: Random seed for reproducibility. |
| | resolution: Image resolution. |
| | request_timeout: Timeout for HTTP requests (seconds). |
| | |
| | Returns: |
| | str: URL to the generated image (default) |
| | |
| | Raises: |
| | RuntimeError on API errors or missing results. |
| | """ |
| | |
| | api_key = os.getenv("IDEOGRAM_API_KEY") |
| |
|
| | if not api_key: |
| | raise RuntimeError( |
| | "IDEOGRAM_API_KEY is not set. Provide api_key param or set env var IDEOGRAM_API_KEY." |
| | ) |
| |
|
| | headers = {"Api-Key": api_key} |
| |
|
| | |
| | if not isinstance(character_image, Image.Image): |
| | raise TypeError("character_image must be a PIL.Image.Image") |
| |
|
| | |
| | if seed is not None: |
| | if not (0 <= int(seed) <= 2147483647): |
| | raise ValueError("seed must be between 0 and 2147483647 inclusive") |
| |
|
| | |
| | with BytesIO() as buf: |
| | character_image.save(buf, format="PNG") |
| | buf.seek(0) |
| | files = [ |
| | ( |
| | "character_reference_images", |
| | ("character.png", buf, "image/png"), |
| | ) |
| | ] |
| |
|
| | data: dict = { |
| | "prompt": prompt, |
| | "style_type": style_type, |
| | } |
| | if seed is not None: |
| | data["seed"] = str(int(seed)) |
| | if resolution is not None: |
| | data["resolution"] = resolution |
| |
|
| | resp = requests.post( |
| | IDEOGRAM_GENERATE_URL, |
| | headers=headers, |
| | data=data, |
| | files=files, |
| | timeout=request_timeout, |
| | ) |
| | if not resp.ok: |
| | raise RuntimeError(f"Ideogram API error {resp.status_code}: {resp.text}") |
| |
|
| | payload = resp.json() |
| | |
| | try: |
| | image_url = payload["data"][0]["url"] |
| | except Exception as e: |
| | raise RuntimeError(f"Unexpected Ideogram response format: {payload}") from e |
| |
|
| | return image_url |
| |
|
| | def __call__(self, components: dict, state: PipelineState): |
| | |
| | block_state = self.get_block_state(state) |
| |
|
| | if isinstance(block_state.character_image, str): |
| | character_image = load_image(block_state.character_image) |
| | elif isinstance(block_state.character_image, Image.Image): |
| | character_image = block_state.character_image |
| | else: |
| | raise ValueError(f"Invalid character image type: {type(block_state.character_image)}") |
| |
|
| | block_state.image_url = self.generate_image_with_character_reference( |
| | prompt=block_state.prompt, |
| | character_image=character_image, |
| | ) |
| | block_state.image = load_image(block_state.image_url) |
| | self.set_block_state(state, block_state) |
| | return components, state |