Fooocus API: Wie und warum nicht (Stand: Fooocus 2.4.3)

In diesem Beitrag soll es darum gehen, wie man die Fooocus API nutzen kann, auch wenn ich davon abrate. Jedoch vorweg erst Mal, was ist Fooocus eigentlich? Fooocus ist eine „KI“-Software zur Bilderzeugung, welche aus einem Text- und/oder Bild-Prompt Bilder erzeugen kann. Selber gibt der Entwickler an, dass es ein „neu-denken“ von Stable Diffusion und Midjourney ist. Dabei nutzt es selber Stable Diffusion bzw. setzt darauf auf. Die Benutzung erinnert jedoch eher an die Einfachheit von Midjourney, da viele Dinge im Hintergrund passieren, die dafür sorgen, dass man schnell und unkompliziert hervorragende Ergebnisse erhält. So werden an jeden Prompt weitere Schlüsselwörter angehangen und durch die sogenannten „Styles“ erweitert, welche wiederum Erweiterungen für den Prompt sind durch weitere Schlüsselwörter. Auch sind die ganzen Abläufe und Schritte dahin vordefiniert, so dass man sich darum nicht kümmern muss. Man kann jedoch an einigen Stellschrauben noch drehen, was aber nichts im Vergleich zu der Komplexität von z.B. Automatic1111 oder ComfyUI ist.

Eine junge Frau mit einem Regenbogen aus Haarfarben hält ein Schild mit der Aufschrift "Move on!", während sie vor der lebendigen Kulisse einer Stadt bei Nacht steht, die von Neonreklamen erhellt wird. Ihre ernste Miene und die entschlossene Haltung deuten darauf hin, dass es ihr ein wichtiges Anliegen ist, die Menschen dazu zu bewegen, nicht die Fooocus API zu nutzen. Ihre Botschaft ist klar: Es gibt bessere Wege, mit Technologie umzugehen, und manchmal muss man einfach weiterziehen.

Warum man die Fooocus API nicht nutzen sollte

So liegt der Gedanke auch Nahe, dass man ebenfalls die Fooocus API nutzen möchte. Besonders da ein Link „Use via API“ einen regelrecht dazu einlädt. Aber lass dich nicht davon täuschen und verwerfe diese Idee am besten direkt wieder! Die „Dokumentation“, welche sich hinter dem Link versteckt, ist mehr als dürftig. Und einige Herausforderungen auf dem Weg Bilder per API zu erzeugen lassen einen schnell die Lust verlieren. Für all jene, die dennoch ihr Glück versuchen wollen, ist dieser Blog-Beitrag. Allen Anderen rate ich, guckt Euch ComfyUI an, da gibt es sogar Skript-Beispiele, wie man die API nutzen kann.

Die Installation

Die Installation von Fooocus selber ist erstaunlich einfach: Man lädt es sich runter, entpackt es und startet es. Das war es. Zumindest beim ersten Ausführen lädt er noch einige Dinge runter, was je nach Internet-Anbindung einige Zeit dauern kann, da es mehrere Gigabytes sind. Irgendwann wird dann eine Web UI im Browser geöffnet und man kann loslegen. Die Download-Links findet man im offiziellen Foocus-GitHub-Repo.

Code-Beispiel

Nun aber ans Eingemachte. Als Beispiel soll ein kurzes Skript dienen, welches per API alle bekannten Styles (derzeit 276) abfragt und für jedes mit dem Prompt „Husky puppy“ ein Bild erzeugt und das Bild in einem Unterverzeichnis speichert:

# Fooocus 2.4.3
from gradio_client import Client
from pathlib import Path
import random
import shutil
from slugify import slugify


# Constants for the placeholder PNG image data
MIN_PNG = ('data:image/png;base64,'
           'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=')


def get_all_styles(client):
    """Fetches all available styles from the client and returns them sorted."""
    result = client.predict(fn_index=18)
    return sorted(style[0] for style in result['choices'])


def predict(client, prompt='', negative_prompt='', style=None, performance='Speed', aspect_ratio='1024×1024',
            num_images=1, output_format='png', seed=0, image_sharpness=2, guidance_scale=3,
            base_model='juggernautXL_v8Rundiffusion.safetensors', refiner='None', refiner_switch_at=0.8,
            lora1='sd_xl_offset_example-lora_1.0.safetensors', lora1_weight=0.25, lora2='None', lora2_weight=1,
            lora3='None', lora3_weight=1, lora4='None', lora4_weight=1, lora5='None', lora5_weight=1):
    """Generates images based on the given parameters and returns the result."""
    # set parameters
    client.predict(
        False,               # bool in 'Generate Image Grid for Each Batch' Checkbox component
        prompt,              # str in 'parameter_11' Textbox component
        negative_prompt,     # str in 'Negative Prompt' Textbox component
        style or [],         # List[str] in 'Selected Styles' Checkboxgroup component
        performance,         # str in 'Performance' Radio component
        aspect_ratio,        # str in 'Aspect Ratios' Radio component
        num_images,          # int | float (numeric value between 1 and 32) in 'Image Number' Slider component
        output_format,       # str in 'Output Format' Radio component
        str(seed),           # str in 'Seed' Textbox component
        True,                # bool in 'Read wildcards in order' Checkbox component
        image_sharpness,     # int | float (numeric value between 0.0 and 30.0) in 'Image Sharpness' Slider component
        guidance_scale,      # int | float (numeric value between 1.0 and 30.0) in 'Guidance Scale' Slider component
        base_model,          # str (Option from: ['animaPencilXL_v100.safetensors',
                             # 'juggernautXL_v8Rundiffusion.safetensors', 'realisticStockPhoto_v20.safetensors',
                             # 'sd_xl_base_1.0_0.9vae.safetensors', 'sd_xl_refiner_1.0_0.9vae.safetensors'])
                             # in 'Base Model (SDXL only)' Dropdown component
        refiner,             # str (Option from: ['None', 'animaPencilXL_v100.safetensors',
                             # 'juggernautXL_v8Rundiffusion.safetensors', 'realisticStockPhoto_v20.safetensors',
                             # 'sd_xl_base_1.0_0.9vae.safetensors', 'sd_xl_refiner_1.0_0.9vae.safetensors'])
                             # in 'Refiner (SDXL or SD 1.5)' Dropdown component
        refiner_switch_at,   # int | float (numeric value between 0.1 and 1.0) in 'Refiner Switch At' Slider component
        lora1 != 'None',     # bool in 'Enable' Checkbox component
        lora1,               # str (Option from: ['None', 'sd_xl_offset_example-lora_1.0.safetensors',
                             # 'SDXL_FILM_PHOTOGRAPHY_STYLE_BetaV0.4.safetensors', 'sdxl_lcm_lora.safetensors'])
                             # in 'LoRA 1' Dropdown component
        lora1_weight,        # int | float (numeric value between -2 and 2) in 'Weight' Slider component
        lora2 != 'None',     # bool in 'Enable' Checkbox component
        lora2,               # str (Option from: ['None', 'sd_xl_offset_example-lora_1.0.safetensors',
                             # 'SDXL_FILM_PHOTOGRAPHY_STYLE_BetaV0.4.safetensors', 'sdxl_lcm_lora.safetensors'])
                             # in 'LoRA 2' Dropdown component
        lora2_weight,        # int | float (numeric value between -2 and 2) in 'Weight' Slider component
        lora3 != 'None',     # bool in 'Enable' Checkbox component
        lora3,               # str (Option from: ['None', 'sd_xl_offset_example-lora_1.0.safetensors',
                             # 'SDXL_FILM_PHOTOGRAPHY_STYLE_BetaV0.4.safetensors', 'sdxl_lcm_lora.safetensors'])
                             # in 'LoRA 3' Dropdown component
        lora3_weight,        # int | float (numeric value between -2 and 2) in 'Weight' Slider component
        lora4 != 'None',     # bool in 'Enable' Checkbox component
        lora4,               # str (Option from: ['None', 'sd_xl_offset_example-lora_1.0.safetensors',
                             # 'SDXL_FILM_PHOTOGRAPHY_STYLE_BetaV0.4.safetensors', 'sdxl_lcm_lora.safetensors'])
                             # in 'LoRA 4' Dropdown component
        lora4_weight,        # int | float (numeric value between -2 and 2) in 'Weight' Slider component
        lora5 != 'None',     # bool in 'Enable' Checkbox component
        lora5,               # str (Option from: ['None', 'sd_xl_offset_example-lora_1.0.safetensors',
                             # 'SDXL_FILM_PHOTOGRAPHY_STYLE_BetaV0.4.safetensors', 'sdxl_lcm_lora.safetensors'])
                             # in 'LoRA 5' Dropdown component
        lora5_weight,        # int | float (numeric value between -2 and 2) in 'Weight' Slider component
        False,               # bool in 'Input Image' Checkbox component
        '',                  # str in 'parameter_91' Textbox component
        'Disabled',          # str in 'Upscale or Variation:' Radio component
        MIN_PNG,             # str (filepath or URL to image) in 'Drag above image to here' Image component
        [],                  # List[str] in 'Outpaint Direction' Checkboxgroup component
        MIN_PNG,             # str (filepath or URL to image)
                             # in 'Drag inpaint or outpaint image to here' Image component
        '',                  # str in 'Inpaint Additional Prompt' Textbox component
        MIN_PNG,             # str (filepath or URL to image) in 'Mask Upload' Image component
        True,                # bool in 'Disable Preview' Checkbox component
        True,                # bool in 'Disable Intermediate Results' Checkbox component
        False,               # bool in 'Disable seed increment' Checkbox component
        False,               # bool in 'Black Out NSFW' Checkbox component
        1.5,                 # int | float (numeric value between 0.1 and 3.0)
                             # in 'Positive ADM Guidance Scaler' Slider component
        0.8,                 # int | float (numeric value between 0.1 and 3.0)
                             # in 'Negative ADM Guidance Scaler' Slider component
        0.3,                 # int | float (numeric value between 0.0 and 1.0)
                             # in 'ADM Guidance End At Step' Slider component
        7,                   # int | float (numeric value between 1.0 and 30.0)
                             # in 'CFG Mimicking from TSNR' Slider component
        1,                   # int | CLIP Skip Slider (numeric value between 1 and 12)
        'dpmpp_2m_sde_gpu',  # str (Option from: ['euler', 'euler_ancestral', 'heun', 'heunpp2', 'dpm_2',
                             # 'dpm_2_ancestral', 'lms', 'dpm_fast', 'dpm_adaptive', 'dpmpp_2s_ancestral', 'dpmpp_sde',
                             # 'dpmpp_sde_gpu', 'dpmpp_2m', 'dpmpp_2m_sde', 'dpmpp_2m_sde_gpu', 'dpmpp_3m_sde',
                             # 'dpmpp_3m_sde_gpu', 'ddpm', 'lcm', 'ddim', 'uni_pc', 'uni_pc_bh2'])
                             # in 'Sampler' Dropdown component
        'karras',            # str (Option from: ['normal', 'karras', 'exponential', 'sgm_uniform', 'simple',
                             # 'ddim_uniform', 'lcm', 'turbo']) in 'Scheduler' Dropdown component
        'Default (model)',   # str | (Option from: ['Default (model)']) in 'VAE' Dropdown component
        -1,                  # int | float (numeric value between -1 and 200)
                             # in 'Forced Overwrite of Sampling Step' Slider component
        -1,                  # int | float (numeric value between -1 and 200)
                             # in 'Forced Overwrite of Refiner Switch Step' Slider component
        -1,                  # int | float (numeric value between -1 and 200)
                             # in 'Forced Overwrite of Generating Width' Slider component
        -1,                  # int | float (numeric value between -1 and 200)
                             # in 'Forced Overwrite of Generating Height' Slider component
        -1,                  # int | float (numeric value between -1 and 200)
                             # in 'Forced Overwrite of Denoising Strength of "Vary"' Slider component
        -1,                  # int | float (numeric value between -1 and 200)
                             # in 'Forced Overwrite of Denoising Strength of "Upscale"' Slider component
        False,               # bool in 'Mixing Image Prompt and Vary/Upscale' Checkbox component
        False,               # bool in 'Mixing Image Prompt and Inpaint' Checkbox component
        False,               # bool in 'Debug Preprocessors' Checkbox component
        False,               # bool in 'Skip Preprocessors' Checkbox component
        64,                  # int | float (numeric value between 1 and 255) in 'Canny Low Threshold' Slider component
        128,                 # int | float (numeric value between 1 and 255) in 'Canny High Threshold' Slider component
        'joint',             # str (Option from: ['joint', 'separate', 'vae'])
                             # in 'Refiner swap method' Dropdown component
        0.25,                # int | float (numeric value between 0.0 and 1.0)
                             # in 'Softness of ControlNet' Slider component
        False,               # bool in 'Enabled' Checkbox component
        1.01,                # int | float (numeric value between 0 and 2) in 'B1' Slider component
        1.02,                # int | float (numeric value between 0 and 2) in 'B2' Slider component
        0.99,                # int | float (numeric value between 0 and 4) in 'S1' Slider component
        0.95,                # int | float (numeric value between 0 and 4) in 'S2' Slider component
        False,               # bool in 'Debug Inpaint Preprocessing' Checkbox component
        False,               # bool in 'Disable initial latent in inpaint' Checkbox component
        'None',              # str (Option from: ['None', 'v1', 'v2.5', 'v2.6']) in 'Inpaint Engine' Dropdown component
        1,                   # int | float (numeric value between 0.0 and 1.0)
                             # in 'Inpaint Denoising Strength' Slider component
        0.618,               # int | float (numeric value between 0.0 and 1.0)
                             # in 'Inpaint Respective Field' Slider component
        False,               # bool in 'Enable Mask Upload' Checkbox component
        False,               # bool in 'Invert Mask' Checkbox component
        0,                   # int | float (numeric value between -64 and 64) in 'Mask Erode or Dilate' Slider component
        True,                # bool in 'Save Metadata to Images' Checkbox component
        'fooocus',           # str in 'Metadata Scheme' Radio component
        MIN_PNG,             # str (filepath or URL to image) in 'Image' Image component
        0,                   # int | float (numeric value between 0.0 and 1.0) in 'Stop At' Slider component
        0,                   # int | float (numeric value between 0.0 and 2.0) in 'Weight' Slider component
        'ImagePrompt',       # str in 'Type' Radio component
        MIN_PNG,             # str (filepath or URL to image) in 'Image' Image component
        0,                   # int | float (numeric value between 0.0 and 1.0) in 'Stop At' Slider component
        0,                   # int | float (numeric value between 0.0 and 2.0) in 'Weight' Slider component
        'ImagePrompt',       # str in 'Type' Radio component
        MIN_PNG,             # str (filepath or URL to image) in 'Image' Image component
        0,                   # int | float (numeric value between 0.0 and 1.0) in 'Stop At' Slider component
        0,                   # int | float (numeric value between 0.0 and 2.0) in 'Weight' Slider component
        'ImagePrompt',       # str in 'Type' Radio component
        MIN_PNG,             # str (filepath or URL to image) in 'Image' Image component
        0,                   # int | float (numeric value between 0.0 and 1.0) in 'Stop At' Slider component
        0,                   # int | float (numeric value between 0.0 and 2.0) in 'Weight' Slider component
        'ImagePrompt',       # str in 'Type' Radio component
        fn_index=46)

    # generate image(s)
    result = client.predict(fn_index=47)

    return result


def main():
    """Main function to generate and save images for all available styles."""
    prompt = 'Husky puppy'
    negative_prompt = ''
    performance = 'Speed'  # Speed / Quality
    aspect_ratio = '896×1152'  # 7:9
    num_images = 1
    seed = random.randint(0, 2 ** 32 - 1)
    image_sharpness = 2
    guidance_scale = 4
    base_model = 'realisticStockPhoto_v20.safetensors'
    lora1 = 'SDXL_FILM_PHOTOGRAPHY_STYLE_BetaV0.4.safetensors'
    lora1_weight = 0.25

    cwd = Path.cwd()
    base_path = cwd / 'images' / 'all_styles'
    base_path.mkdir(exist_ok=True)

    client = Client('http://localhost:7865', serialize=False)

    for style in get_all_styles(client):
        file_path = base_path / f'{slugify(style)}_{seed}.png'
        print(f'Generating image for style: {style}')
        result = predict(client, prompt=prompt, negative_prompt=negative_prompt, style=[style], performance=performance,
                         aspect_ratio=aspect_ratio, num_images=num_images, seed=seed, image_sharpness=image_sharpness,
                         guidance_scale=guidance_scale, base_model=base_model, lora1=lora1, lora1_weight=lora1_weight)
        src_file_path = result[3]['value'][0]['name']
        shutil.copy(src_file_path, file_path)


if __name__ == '__main__':
    main()

Dieses Skript auszuführen stellt einen jedoch bereits vor die ersten Herausforderungen: Die aktuelle Version des gradio-client funktioniert nicht mit Fooocus. Derzeit (Fooocus 2.4.§) muss man die Version 0.5.3 installieren, da es sonst zu Fehlern in der Kommunikation kommt. Ebenfalls zu Fehlern kommt es nach dem Erzeugen des ersten Bildes, wenn man den Client nicht mit serialize=False aufruft. Dies führt jedoch dazu, dass man an jeder Stelle, an welcher man ein Bild übergeben muss, nun eine serialisierte Version eines Bildes (in dem Fall ein transparentes PNG) übergeben muss, wodurch man (vermutlich) einige der Features nicht mehr anständig per API nutzen kann. Zumindest habe ich es so keinen FaceSwap mehr hinbekommen und hatte schnell die Motivation verloren da weiter zu experimentieren.

Hinweis: Mit Version 2.2.0 von Fooocus hat sich die Verwendung der API zum Erstellen von Bildern verändert, so muss man nun erst Mal alle Parameter setzen und dann mit der Folge-Funktion das Generieren starten. Dies passierte vorher über ein und die selbe Funktion. Diese gibt nun jedoch lediglich ein leeres Tuple zurück.

Fazit

Sieht man nun davon ab, dass man ihm per API nicht oder nur eingeschränkt Bilder übergeben kann, kann man jedoch den Rest recht gut beeinflussen und so automatisiert einige Einstellungen und Stellschrauben testen. Für die Vergleichbarkeit sollte man immer den gleichen Seed benutzen, da bei gleicher Eingabe (Prompt, Styles, Parameter, Model usw. usf.) und gleichem Seed das Ergebnis das Gleiche ist.

Durch minimale Anpassungen sollte es dir so möglich sein, verschiedene Stellschrauben einfach und schnell zu vergleichen. Wobei schnell natürlich auch davon abhängt, wie gut Deine Grafikkarte ist. In meinem Fall dauert es knapp 10 Sekunden ein Bild mit dem obigen Skript zu erstellen.

Solltest du noch mehr über die API rausfinden oder findest du, dass ein Aspekt nicht ausreichend oder gar falsch beleuchtet wurde, lass es mich gerne in den Kommentaren wissen.

Schreibe einen Kommentar