import os import gradio as gr import numpy as np import spaces import torch import random from PIL import Image from typing import Iterable from gradio.themes import Soft from gradio.themes.utils import colors, fonts, sizes import uuid from datetime import datetime from huggingface_hub import HfApi import hashlib # --- SETTINGS --- INPUT_DATASET_ID = "ingi/image-edit-inputs" OUTPUT_DATASET_ID = "ingi/image-edit-outputs" # --------------- colors.steel_blue = colors.Color( name="steel_blue", c50="#EBF3F8", c100="#D3E5F0", c200="#A8CCE1", c300="#7DB3D2", c400="#529AC3", c500="#4682B4", c600="#3E72A0", c700="#36638C", c800="#2E5378", c900="#264364", c950="#1E3450", ) class SteelBlueTheme(Soft): def __init__( self, *, primary_hue: colors.Color | str = colors.gray, secondary_hue: colors.Color | str = colors.steel_blue, neutral_hue: colors.Color | str = colors.slate, text_size: sizes.Size | str = sizes.text_lg, font: fonts.Font | str | Iterable[fonts.Font | str] = ( fonts.GoogleFont("Outfit"), "Arial", "sans-serif", ), font_mono: fonts.Font | str | Iterable[fonts.Font | str] = ( fonts.GoogleFont("IBM Plex Mono"), "ui-monospace", "monospace", ), ): super().__init__( primary_hue=primary_hue, secondary_hue=secondary_hue, neutral_hue=neutral_hue, text_size=text_size, font=font, font_mono=font_mono, ) super().set( background_fill_primary="*primary_50", background_fill_primary_dark="*primary_900", body_background_fill="linear-gradient(135deg, *primary_200, *primary_100)", body_background_fill_dark="linear-gradient(135deg, *primary_900, *primary_800)", button_primary_text_color="white", button_primary_text_color_hover="white", button_primary_background_fill="linear-gradient(90deg, *secondary_500, *secondary_600)", button_primary_background_fill_hover="linear-gradient(90deg, *secondary_600, *secondary_700)", button_primary_background_fill_dark="linear-gradient(90deg, *secondary_600, *secondary_800)", button_primary_background_fill_hover_dark="linear-gradient(90deg, *secondary_500, *secondary_500)", button_secondary_text_color="black", button_secondary_text_color_hover="white", button_secondary_background_fill="linear-gradient(90deg, *primary_300, *primary_300)", button_secondary_background_fill_hover="linear-gradient(90deg, *primary_400, *primary_400)", button_secondary_background_fill_dark="linear-gradient(90deg, *primary_500, *primary_600)", button_secondary_background_fill_hover_dark="linear-gradient(90deg, *primary_500, *primary_500)", slider_color="*secondary_500", slider_color_dark="*secondary_600", block_title_text_weight="600", block_border_width="3px", block_shadow="*shadow_drop_lg", button_primary_shadow="*shadow_drop_lg", button_large_padding="11px", color_accent_soft="*primary_100", block_label_background_fill="*primary_200", ) steel_blue_theme = SteelBlueTheme() device = torch.device("cuda" if torch.cuda.is_available() else "cpu") from diffusers import FlowMatchEulerDiscreteScheduler from qwenimage.pipeline_qwenimage_edit_plus import QwenImageEditPlusPipeline from qwenimage.transformer_qwenimage import QwenImageTransformer2DModel from qwenimage.qwen_fa3_processor import QwenDoubleStreamAttnProcessorFA3 dtype = torch.bfloat16 device = "cuda" if torch.cuda.is_available() else "cpu" pipe = QwenImageEditPlusPipeline.from_pretrained( "Qwen/Qwen-Image-Edit-2509", transformer=QwenImageTransformer2DModel.from_pretrained( "linoyts/Qwen-Image-Edit-Rapid-AIO", subfolder='transformer', torch_dtype=dtype, device_map='cuda' ), torch_dtype=dtype ).to(device) # Load the single inflation LoRA pipe.load_lora_weights("Ingi/SYSTMS-INFL8-LoRA-Qwen-Image-Edit-2511", weight_name="SYSTMS_INFL8_LoRA_Qwen_Image_Edit_2511.safetensors", adapter_name="inflation") pipe.set_adapters(["inflation"], adapter_weights=[1.0]) pipe.transformer.set_attn_processor(QwenDoubleStreamAttnProcessorFA3()) MAX_SEED = np.iinfo(np.int32).max def update_dimensions_on_upload(image): if image is None: return 1024, 1024 original_width, original_height = image.size if original_width > original_height: new_width = 1280 aspect_ratio = original_height / original_width new_height = int(new_width * aspect_ratio) else: new_height = 1280 aspect_ratio = original_width / original_height new_width = int(new_height * aspect_ratio) new_width = (new_width // 8) * 8 new_height = (new_height // 8) * 8 return new_width, new_height # --- HASH CALCULATION --- def get_image_hash(image): img_byte_arr = image.tobytes() return hashlib.md5(img_byte_arr).hexdigest() # --- HUB UPLOAD --- def upload_image_to_hub(image, dataset_id, folder_prefix="images"): try: hf_token = os.environ.get("HF_TOKEN") if not hf_token: print(f"Error: HF_TOKEN not found.") return api = HfApi(token=hf_token) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") unique_id = str(uuid.uuid4())[:8] filename = f"{folder_prefix}_{timestamp}_{unique_id}.png" temp_path = f"/tmp/{filename}" image.save(temp_path) api.upload_file( path_or_fileobj=temp_path, path_in_repo=f"{folder_prefix}/{filename}", repo_id=dataset_id, repo_type="dataset" ) os.remove(temp_path) print(f"Uploaded: {filename} -> {dataset_id}") except Exception as e: print(f"Upload error ({dataset_id}): {e}") @spaces.GPU(duration=90) def infer( input_image, subject_input, seed, randomize_seed, guidance_scale, steps, uploaded_history_list, progress=gr.Progress(track_tqdm=True) ): if input_image is None: raise gr.Error("Please upload an image to edit.") # 1. CHECK INPUT IMAGE AND SAVE IF NEEDED original_image = input_image.convert("RGB") # Get image hash current_image_hash = get_image_hash(original_image) # If this hash is NOT in history list, upload and add to list if current_image_hash not in uploaded_history_list: print("New image detected, uploading...") upload_image_to_hub(original_image, INPUT_DATASET_ID, folder_prefix="inputs") uploaded_history_list.append(current_image_hash) else: print("This image was already uploaded in this session, skipping.") # ---------------------------------------------------- # Construct the full prompt with user input prompt = f"inflate the {subject_input}" if randomize_seed: seed = random.randint(0, MAX_SEED) generator = torch.Generator(device=device).manual_seed(seed) negative_prompt = "worst quality, low quality, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, jpeg artifacts, signature, watermark, username, blurry" width, height = update_dimensions_on_upload(original_image) result = pipe( image=original_image, prompt=prompt, negative_prompt=negative_prompt, height=height, width=width, num_inference_steps=steps, generator=generator, true_cfg_scale=guidance_scale, ).images[0] # 2. ALWAYS SAVE OUTPUT IMAGE upload_image_to_hub(result, OUTPUT_DATASET_ID, folder_prefix="generated") return result, seed, uploaded_history_list @spaces.GPU(duration=90) def infer_example(input_image, subject_input): input_pil = input_image.convert("RGB") guidance_scale = 3.5 steps = 16 result, seed, _ = infer(input_pil, subject_input, 0, True, guidance_scale, steps, []) return result, seed css=""" #col-container { margin: 0 auto; max-width: 960px; } #main-title h1 {font-size: 2.1em !important;} """ with gr.Blocks(css=css, theme=steel_blue_theme) as demo: # STATE: Holds hashes of images uploaded during user's session (using list for JSON serialization) uploaded_history = gr.State(value=[]) with gr.Column(elem_id="col-container"): gr.Markdown("# **SYSTMS // INFL8 - Qwen Image Edit 2511 Lora**", elem_id="main-title") gr.Markdown("Inflate anything..") with gr.Row(equal_height=True): with gr.Column(): input_image = gr.Image(label="Upload Image", type="pil", height=290) subject_input = gr.Text( label="What to Inflate", show_label=True, placeholder="what you want to inflate, e.g. man" ) run_button = gr.Button("Inflate", variant="primary") with gr.Column(): output_image = gr.Image(label="Output Image", interactive=False, format="png", height=350) with gr.Accordion("Advanced Settings", open=False, visible=False): seed = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=0) randomize_seed = gr.Checkbox(label="Randomize Seed", value=True) guidance_scale = gr.Slider(label="Guidance Scale", minimum=1.0, maximum=10.0, step=0.1, value=3.5) steps = gr.Slider(label="Inference Steps", minimum=1, maximum=50, step=1, value=16) run_button.click( fn=infer, inputs=[input_image, subject_input, seed, randomize_seed, guidance_scale, steps, uploaded_history], outputs=[output_image, seed, uploaded_history] ) if __name__ == "__main__": demo.queue(max_size=30).launch(mcp_server=True, ssr_mode=False, show_error=True)