見出し画像

Diffusersで画像サーバ構築 V1.1

Diffusersが0.26.0にバージョンアップし、注目のIP-Adapetrにマルチアダプタ対応が導入されたので、自作DiffusersPipleineManagerクラスとFastAPIによるAPIサーバもv1.1 へアップデートしました。同じモデル、同じパラメータで生成される画風が0.26.0では変わってしまってしまいましたが、マルチIP-Adapetrが使えるので、環境を使い分けても良さそうです。

環境

Python=3.10
CUDA=12.2
Torch=2.22
transformers
diffusers 0.25.0 and/or 0.26.0
compel
numpy
cv2
PIL
GPU 中程度の性能が必要

DiffusersPipleineManagerクラスv1.1

DiffusersPipleineManagerをV1.1へ機能upしました。
1)マルチIP-Adaper機能
 t2i/i2iに対してIP-Adaper及びマルチIP-Adaperを適用
2)IP-Adaper機能にinpaintを追加
3)Freeu有効時にパラメータを渡す機能
 デフォルト:s1=0.9, s2=0.2, b1=1.2, b2=1.4
 s1、s2、b1、b2をクライアント側から指定できます。
4)SEEDの指定方法変更
 seed無し→ auto_seed有効→seedx自動生成 torch.manual_seed(seedx )
       auto_seed無効→ generator指定なし(pipelineがseed生成)
 seed有り→ generator = torch.manual_seed(seed )
       auto_seedは無視される

マルチIP-Adapter

0.26.0で導入された主要機能です。複数のIP-Adapterを用いて10枚程度の画像とfaceイメージでLoRAのような効果が期待できます。
refarenceイメージ 修正をかけたい元画像
faceイメージ    refarenceイメージの一部に反映させたい画像(顔など)
stypeイメージ   画風(雰囲気)を定義する10枚程度の画像

SDXL版のウエイトはまだ少数です。以下参照ください。

サーバのコード

デフォルトlola_modelの定義

pipelineにbindされるのでクライアント側では指定できません。サーバ側で事前にbindしたいLoRAを指定します。List形式でモデル名とscaleを設定

lola_models=[
            ["./models/animagine-xl-2.0/megu_sports_sdxl_v02.safetensors","style",0.8],
            ["./models/animagine-xl-2.0/anime-detailer-xl.safetensors","detaile",1.0],
            ["./models/animagine-xl-2.0/style-enhancer-xl.safetensors","enhancer",0.5]]

モデルの指定

モデルも事前にサーバで指定します。DiffusersPipleineManagerはSDXL専用なので注意してください。safetenser形式のchkekpointは事前にダンロードする必要が有ります。pipline作成のコードで.from_single_fileをfrom_pretrainedに修正するとHugingFaceのモデルは自動ダウンロド出来ます。HugingFaceに無いモデルは事前にダンロードが必要です。

 #----- model定義
model="./models/animagine-xl-2.0/animagine-xl-2.0.safetensors"
model_t2i="stabilityai/stable-diffusion-xl-base-1.0"

モデルのロード

作成したい生成方法ごとにpipelineへのロードが必要です。t2iを基本に、i2iとinpaintはt2iで生成したpipelineを引き継いで指定できます。VRAM使用量は増えません。以下、t2i/i2i/inpaintをデフォルトロードし、他のpipelineは必要に応じてロードします。16G-VRAMですと1種類しかロードできません。

 #----- pipelineのロード "t2i"・"i2i_pipe"・"inpaint_pipe"はデフォルトでロードでOK、他はVRAMの容量次第
#pipe_t2i=Df.pipeline_init("t2i",model=model, lola_models=lola_models)
#pipe_i2i=Df.pipeline_init("i2i_pipe",pipeline=pipe_t2i)#pipelineの使い回し
#pipe_inpaint=Df.pipeline_init("inpaint_pipe",pipeline=pipe_t2i)#pipelineの使い回し

 #Control Net pipelineの設定
#pipe_canny=Df.pipeline_init("canny",model=model)  #Canny ContorolNET用pipeの新規定義
#pipe_openpose=Df.pipeline_init("openpose",model=model)  #openpose ContorolNET用pipeの新規定義
#pipe_openpose=Df.pipeline_init("openpose",model=model,  lola_models=lola_models) #lola_models指定
 #pipe_depth = Df.pipeline_init("depth",model=model)
#pipe_t2i_adapter=Df.pipeline_init("t2i_adapter_canny",model=model_t2i, lora_en=False)#t2i_adapter用

#IP-Adapter pipeline 設定 
 #mode = t2i_ip/i2i_ip/inpaint_ip
#mul_ip=True  or  single_ip=True
#pipe_t2i_mip=Df.pipeline_init("t2i_ip",model=model, lcm_lora_en=True,  mul_ip=True, ip_scal1=0.9,ip_scal2=0.5)
#pipe_i2i_mip=Df.pipeline_init("i2i_ip",model=model, lcm_lora_en=True,  mul_ip=True, ip_scal1=0.9,ip_scal2=0.5)
#pipe_t2i_sip=Df.pipeline_init("t2i_ip",model=model, lcm_lora_en=True,  single_ip=True,ip_scal1=0.6)
#pipe_i2i_sip=Df.pipeline_init("i2i_ip",model=model, lcm_lora_en=True,  single_ip=True,ip_scal1=0.6)
pipe_inpaint_sip=Df.pipeline_init("inpaint_ip",model=model, lcm_lora_en=True,  single_ip=True,ip_scal1=0.6)

pipelineの指定

サーバ-クライント間ではpipelineをやり取りできません。クライントからは使いたいpipelineを指定して、サーバ側で実施のpipelineを選択します。
pipline_sel(pipeline)ではクライアントから受け取ったpipeline名から利用する生成に利用するpipelineを選んでいます。いずれにも該当がなければ自動的にt2iが選択されます。

#--------pipeline名からpipeを選択して返す
def pipline_sel(pipeline):
    if pipeline=="t2i":
         return pipe_t2i
    elif  pipeline=="pipe_i2i":
         return pipe_i2i
    elif  pipeline=="pipe_inpaint":
         return pipe_inpaint
    elif  pipeline=="canny":
         return pipe_canny
    elif  pipeline=="openpose":
         return pipe_openpose
    elif  pipeline=="depth":
         return pipe_depth 
    elif  pipeline=="t2i_adapter":
         return pipe_t2i_adapter
    elif  pipeline=="t2i_sip":
         return pipe_t2i_sip
    elif  pipeline=="t2i_mip":
         return pipe_t2i_mip
    elif  pipeline=="i2i_sip":
         return pipe_i2i_sip
    elif  pipeline=="i2i_mip":
         return pipe_i2i_mip
    elif  pipeline=="inpaint_sip":
         return pipe_inpaint_sip
    else:
        return pipe_t2i

ControleNETで使用する画像変換

クライアントからは以下の3種類
get_canny
get_openpose
get_depth_map
を呼び出すことが出来ます。

画像の生成

クライアントからパラメータや画像データを受け取りDiffusersPipleineManagerのgenerateクラスへ渡して画像を生成します。
@app.post("/generate/")エンドポイントでは前準備として
1)pipelineの選択
2)プロンプトエンべデッディングの処理(デフォルトは無し)
3)SEEDの生成
4)イメージデータの復元
 i2iのレファレンス
 inpaintのレファレンスとマスク
 IP-AdapterのレファレンスとStyle画像群
5)Freeuの設定 デフォルトで標準値が設定されます
6)crops_coords_top_leftの設定
を実行し、pipelineへ渡して画像を生成します。
t2i_adapterとそれ以外の2種類を使わけています。

生成パラメータ

各パラメータの意味はDiffusersPipleineManagerの記事を参照してください
クライアントから指定できる変数
pipeline: str = Form(...), image: UploadFile = File(None), mask_image: UploadFile = File(None), embe:bool = Form(False), prompt_embe:list = Form(None), prompt_embe0:str = Form(None), prompt_embe1:str = Form(None), prompt:str = Form(None), negative_prompt:str = Form(None), num_inference_steps:int =Form(4), strength:float = Form(0.1), guidance_scale:float = Form(1.0), seed:int= Form(0), auto_seed:bool = Form(True), height :int= Form(None), width:int = Form(None), num_images_per_prompt:int = Form(1), guess_mode:bool = Form(None), freeu:bool = Form(False), freeu_list:list=Form(None), output_type:str = Form("pil"), crops_coords_top_left:list = Form([0,0]), controlnet_conditioning_scale:float = Form(1.0), adapter_conditioning_scale:float=Form(None), adapter_conditioning_factor:int=Form(None), ip_adapter:bool=Form(False), #style_images =Form(None), style_images:UploadFile =File(None), ip_image:UploadFile =File(None),

実際にpipelineへ渡される変数
pipe, image=image, prompt=prompt , mask_image=mask_image, embe=embe, conditioning=conditioning ,#prompt_embeds=conditioning, pooled=pooled ,#pooled_prompt_embeds=pooled, negative_prompt = negative_prompt, num_inference_steps= num_inference_steps, strength=strength, guidance_scale=guidance_scale, generator=generator, height = height, width = width, num_images_per_prompt = num_images_per_prompt, guess_mode=True, freeu=freeu, freeu_list=freeu_list, output_type=output_type, crops_coords_top_left=crops_coords_top_left, controlnet_conditioning_scale=controlnet_conditioning_scale, ip_adapter=ip_adapter, ip_image = ip_image , style_images=style_images_list,

生成画像の返送

DiffusersPipleineManagerで生成された画像はクラインアントへ返送されます。media_type="application/octet-stream"で通信オーバヘッドを軽減しています。

    #−−−−−生成画像を返信
    images_data = pickle.dumps(image_list, 5)  # tx_dataはpklデータ
    return Response(content= images_data, media_type="application/octet-stream")

基本テスト

i2i/t2i/inpaintのテスト

以下でテストを選択します。複数選択も可能です。
t2i=True
i2i=False
inpaint=False

import requests
from PIL import Image
from io import BytesIO
import pickle
import time

t2i=True
i2i=False
inpaint=False

url = 'http://0.0.0.0:8000/generate/'

# API サーバrequest モジュール
def resuest_imag(data,files=None):
    response = requests.post(url, data=data ,files=files) # POSTリクエストを送信
    print(response)
    # レスポンスを表示 
    if response.status_code == 200:
        print("get data OK")
        image_data = response.content
        image =(pickle.loads(image_data))#元の形式にpickle.loadsで復元
    else:
        image=[]
        print("リクエストが失敗しました。ステータスコード:", response.status_code)
    return  image

# t2iのテスト  pipe_t2i のロードが必要
if t2i:
    count=5 #生成回数
    for i in range(count):
        start_time = time.time()
        data= {
            "pipeline":"t2i",
            "prompt":"masterpiece, best quality, 1girl, solo, flower, long hair, outdoors, letterboxed, school uniform, day, sky, looking up, short sleeves, parted lips, shirt, cloud, black hair, sunlight, white shirt, serafuku, upper body, from side, pink flower, blurry, brown hair, blue sky, depth of field",
            "negative_prompt":"monochrome, lowres, bad anatomy, worst quality, low quality",
            "freeu":True,
            "freeu_list":[0.9, 0.2,1.2,1.4],
            "num_inference_steps":4,
            "guidance_scale":1.0,  
            #"auto_seed":False,
            "seed":i,
            "height": 512,
            "width": 512, 
            }
        image = resuest_imag(data)
        print("Generation time = ",(time.time() - start_time)*1000,"mS")
        image[0].show()
        
# t2iのテスト  pipe_t2i のロードが必要
if i2i:
    count=5 #生成回数
    for i in range(count):
        start_time = time.time()
        image_file_path="MIKU.png"
        file_data = open(image_file_path, "rb").read()
        files={"image": ("img.png", BytesIO(file_data), "image/png"), }
        data= {
            "pipeline":"pipe_i2i",
            "prompt":"masterpiece, best quality, 1girl, solo, flower, long hair, outdoors, letterboxed, school uniform, day, sky, looking up, short sleeves, parted lips, shirt, cloud, black hair, sunlight, white shirt, serafuku, upper body, from side, pink flower, blurry, brown hair, blue sky, depth of field",
            "negative_prompt":"monochrome, lowres, bad anatomy, worst quality, low quality",
            "freeu":True,
            "freeu_list":[0.9, 0.2,1.2,1.4],
            "num_inference_steps":8,
            "guidance_scale":1.0,  
            #"auto_seed":False,
            "strength":0.8, #小さすぎるとエラーになります
            "seed":i,
            }
        image = resuest_imag(data,files)
        print("Generation time = ",(time.time() - start_time)*1000,"mS")
        image[0].show()

# inpaintのテスト  pipe_t2i のロードが必要
if inpaint:
    count=5 #生成回数
    for i in range(count):
        start_time = time.time()
        image_file_path="image_lcm_lora_3.png"
        file_data = open(image_file_path, "rb").read()
        mask_image_file_path="mask_body.jpg"
        mask_file_data = open(mask_image_file_path, "rb").read()
        files={"image": ("img.png", BytesIO(file_data), "image/png"),
                     "mask_image":("img.png", BytesIO(mask_file_data), "image/png"),}
        data= {
            "pipeline":"pipe_inpaint",
            "prompt": "masterpiece, best quality, 1girl, solo, long hair, red t-shirt, simple background,white background",
            "freeu":True,
            "freeu_list":[0.9, 0.2,1.2,1.4],
            "num_inference_steps":8,
            "guidance_scale":1.0,
            "height": 512,
            "width": 512, 
            #"auto_seed":False,
            "strength":0.95, #大きくないとプロンプトが反映されにくい
            "seed":i,
            }
        image = resuest_imag(data,files)
        print("Generation time = ",(time.time() - start_time)*1000,"mS")
        image[0].show()

IP- Adapterテスト

シングルのIP- AdapterテストとマルチIP- Adapterテストがi2i/t2i/inpaintに対して実行できます。以下から選択しますが、各IP- Adapterテストを実行するためにはサーバ側で対応するpiupelineを定義して置く必要があります。
t2i_sip=False
t2i_mip=False
i2i_sip=False
i2i_mip=False
inpaint_sip=True

import requests
from PIL import Image
from io import BytesIO
import pickle

t2i_sip=False
t2i_mip=False
i2i_sip=False
i2i_mip=False
inpaint_sip=True

url = 'http://0.0.0.0:8000/generate/'

# API サーバrequest モジュール
def resuest_imag(data,files=None):
    response = requests.post(url, data=data ,files=files) # POSTリクエストを送信
    print(response)
    # レスポンスを表示 
    if response.status_code == 200:
        print("get data OK")
        image_data = response.content
        image =(pickle.loads(image_data))#元の形式にpickle.loadsで復元
    else:
        image=[]
        print("リクエストが失敗しました。ステータスコード:", response.status_code)
    return  image


# t2iのIP-Adapterのテスト pipe_t2i_sip のロードが必要
if t2i_sip:
    count=5 #生成回数
    for i in range(count):
        image_file_path="img_gen0iPkha .jpg"
        face_image=  Image.open(image_file_path)
        face_image .show()
        file_data = open(image_file_path, "rb").read()
        files={"ip_image": ("img.png", BytesIO(file_data), "image/png"),}
        data= {
            "pipeline":"t2i_sip",
            "prompt":"masterpiece, best quality, 1girl, solo, flower, long hair, outdoors, letterboxed",
            "negative_prompt":"monochrome, lowres, bad anatomy, worst quality, low quality",
            "freeu":True ,
            "freeu_list":[0.9, 0.2,1.2,1.4],
            "height":768,
            "width": 768,
            "num_inference_steps":8,
            "guidance_scale":1.0,  
            "embe":False,
            "ip_adapter":True,
            "seed":i,
            }
        image = resuest_imag(data,files)
        image[0].show()

# t2iのマルチIP-Adapterのテスト pipe_t2i_mip のロードが必要
if t2i_mip:
    image_file_path="img_gen5PlAk9.png"
    ip_image=  Image.open(image_file_path)
    ip_image .show()
    file_data = open(image_file_path, "rb").read()
    style_folder = "./style_images"
    image_list =  [Image.open(f"{style_folder}/img{i}.png") for i in range(10)]
    
    files =  {"ip_image": ("img.png", BytesIO(file_data), "image/png"),
                    "style_images":("data.dat",BytesIO(pickle.dumps(image_list )),"data/data"),# image_list をpikleでバイト化し、ファイルデータとして送る
                   }
    count=1 #生成回数
    for i in range(count):
        data={
            "pipeline":"t2i_mip",
            "prompt":"masterpiece, best quality, 1girl, solo,looking at viewer,blush,smile ,bangs,blue eyes",
            "negative_prompt":"monochrome, lowres, bad anatomy, worst quality, low quality",
            "freeu":True ,
             "freeu_list":[0.9, 0.2,1.2,1.4],
             "num_inference_steps":4,    #lcm_lora_en=Falseのときは50
             "guidance_scale":1.0,               #lcm_lora_en=Falseのときは6.0
             "embe":False,
             "ip_adapter":True,
             "ip_image": ip_image,            #face_image              "height": 512,
             "width": 512,
               }
        image = resuest_imag(data,files)
        image[0].show()

# i2iのIP-Adapterのテスト pipe_t2i_sip のロードが必要
if i2i_sip:
    count=5 #生成回数
    for i in range(count):
        image_file_path="img_gen0iPkha .jpg"
        face_image=  Image.open(image_file_path)
        face_image .show()
        ip_file_data = open(image_file_path, "rb").read()
        image_file_path="MIKU.png"
        file_data = open(image_file_path, "rb").read()
        files={"ip_image": ("img.png", BytesIO(ip_file_data), "image/png"),
                      "image": ("img.png", BytesIO(file_data), "image/png"),
                     }
        data= {
            "pipeline":"i2i_sip",
            "prompt":"masterpiece, best quality, 1girl, solo, flower, long hair, outdoors, letterboxed",
            "negative_prompt":"monochrome, lowres, bad anatomy, worst quality, low quality",
            "freeu":True ,
            "freeu_list":[0.9, 0.2,1.2,1.4],
            "height":768,
            "width": 768,
            "num_inference_steps":8,
            "guidance_scale":1.0,  
            "embe":False,
            "ip_adapter":True,
            "seed":i,
            }
        image = resuest_imag(data,files)
        image[0].show()

    # i2iのマルチIP-Adapterのテスト pipe_i2i_mip のロードが必要
if i2i_mip:
    count=5 #生成回数
    for i in range(count):
        image_file_path="img_gen0iPkha .jpg"
        face_image=  Image.open(image_file_path)
        face_image .show()
        ip_file_data = open(image_file_path, "rb").read()
        image_file_path="MIKU.png"
        file_data = open(image_file_path, "rb").read()
        style_folder = "./style_images"
        image_list =  [Image.open(f"{style_folder}/img{i}.png") for i in range(10)]
        files={"ip_image": ("img.png", BytesIO(ip_file_data), "image/png"),
                      "image": ("img.png", BytesIO(file_data), "image/png"),
                      "style_images":("data.dat",BytesIO(pickle.dumps(image_list )),"data/data"),# image_list をpikleでバイト化し、ファイルデータとして送る
                     }
        data= {
            "pipeline":"i2i_mip",
            "prompt":"masterpiece, best quality, 1girl, solo, flower, long hair, outdoors, letterboxed",
            "negative_prompt":"monochrome, lowres, bad anatomy, worst quality, low quality",
            "freeu":True ,
            "freeu_list":[0.9, 0.2,1.2,1.4],
            "height":768,
            "width": 768,
            "num_inference_steps":8,
            "guidance_scale":1.0,  
            "embe":False,
            "ip_adapter":True,
            "seed":i,
            }
        image = resuest_imag(data,files)
        image[0].show()

# inpaintのIP-Adapterのテスト pipe_t2i_sip のロードが必要
if inpaint_sip:
    count=5 #生成回数
    for i in range(count):
        image_file_path="image_lcm_lora_3.png"
        face_image=  Image.open(image_file_path)
         #face_image .show()
        ip_file_data = open(image_file_path, "rb").read()
        image_file_path="MIKU.png"
        mask_file_path="mask_body.jpg"
        mask_img = open(mask_file_path, "rb").read()
        file_data = open(image_file_path, "rb").read()
        files={"ip_image": ("img.png", BytesIO(ip_file_data), "image/png"),
                      "image": ("img.png", BytesIO(file_data), "image/png"),
                      "mask_image": ("mask_image.png", BytesIO(mask_img), "image/png"),
                     }
        data= {
            "pipeline":"inpaint_sip",
            "prompt":"masterpiece, best quality, 1girl, solo, long hair, red t-shirt,simple background,white background",
            "negative_prompt":"monochrome, lowres, bad anatomy, worst quality, low quality",
            "freeu":True ,
            "freeu_list":[0.9, 0.2,1.2,1.4],
            "height":512,
            "width": 512,
            "num_inference_steps":8,
            "guidance_scale":1.0,  
            "embe":False,
            "ip_adapter":True,
            "seed":i,
            }
        image = resuest_imag(data,files)
        image[0].show()

ControlNETテスト

V1.0と同じ仕様です。以下の記事を参考にしてください。

生成時間の目安

GPU:A4000(ampaleアーキテクチャ、VRAM16G)中クラスのGPUです
CPU:i5-12400F
SDLXモデル animagine-xl-2.0.safetensors

t2iの時
Step=4,Guidance?scale=1.0(無効)
512x512 0.55秒/image
1024x1024 1.7秒/image

まとめ

SDXL専用の画像生成サーバを構築しました。SDXLでLCM対応なので非常に綺麗な画像が短い時間で生成出来ます。ControleNETやIP-Adapterのpipelineの定義を個別に行わなければならない点は問題でもありますが、基本的なt1i/i2i/inpaitは満足出来る品質と生成速度が得られました。画像生成サーバを動かしてアプリから生成リクエストを送る使い方は完全な並列処理ができて生成待ち時間も有効に他のタスクに活用できるので、アプリの開発に余裕が出るはずです。Diffusersはバージョンアップも早く、次々と機能が追加されます。実際に0.25.0から0.26.0 へは2ヶ月程度でバージョンアップしています。
画風が変わってしまう件は理由が明確ではないですが、時間があれば原因を調べたいと思います。

付録:サーバ全コード

import torch
import cv2
from PIL import Image
 #from  n_lcm_lora_sdxl_i2i_t2i_class_v1 import DiffusersPipelineManager
from  n_lcm_lora_sdxl_i2i_t2i_class_v1_1 import DiffusersPipelineManager
import pickle

# ===================     FastAPI  =================
from fastapi import FastAPI, File, UploadFile, Form,  Query
from fastapi.responses import HTMLResponse,StreamingResponse,JSONResponse,Response
from pydantic import BaseModel
from io import BytesIO
import io
import json
import base64
import datetime
import string

app = FastAPI()

#------ DiffusersPipelineManagerの定義と初期化
Df=DiffusersPipelineManager()

#------ 独自LoRAの定義
lola_models=[
            ["./models/animagine-xl-2.0/megu_sports_sdxl_v02.safetensors","style",0.8],
            ["./models/animagine-xl-2.0/anime-detailer-xl.safetensors","detaile",1.0],
            ["./models/animagine-xl-2.0/style-enhancer-xl.safetensors","enhancer",0.5]]
 #----- model定義
model="./models/animagine-xl-2.0/animagine-xl-2.0.safetensors"
model_t2i="stabilityai/stable-diffusion-xl-base-1.0"

 #----- pipelineのロード "t2i"・"i2i_pipe"・"inpaint_pipe"はデフォルトでロードでOK、他はVRAMの容量次第
pipe_t2i=Df.pipeline_init("t2i",model=model, lola_models=lola_models)
pipe_i2i=Df.pipeline_init("i2i_pipe",pipeline=pipe_t2i)#pipelineの使い回し
pipe_inpaint=Df.pipeline_init("inpaint_pipe",pipeline=pipe_t2i)#pipelineの使い回し

 #Control Net pipelineの設定
pipe_canny=Df.pipeline_init("canny",model=model)  #Canny ContorolNET用pipeの新規定義
#pipe_openpose=Df.pipeline_init("openpose",model=model)  #openpose ContorolNET用pipeの新規定義
#pipe_openpose=Df.pipeline_init("openpose",model=model,  lola_models=lola_models) #lola_models指定
 #pipe_depth = Df.pipeline_init("depth",model=model)
#pipe_t2i_adapter=Df.pipeline_init("t2i_adapter_canny",model=model_t2i, lora_en=False)#t2i_adapter用

#IP-Adapter pipeline 設定 
 #mode = t2i_ip/i2i_ip/inpaint_ip
#mul_ip=True  or  single_ip=True
#pipe_t2i_mip=Df.pipeline_init("t2i_ip",model=model, lcm_lora_en=True,  mul_ip=True, ip_scal1=0.9,ip_scal2=0.5)
#pipe_i2i_mip=Df.pipeline_init("i2i_ip",model=model, lcm_lora_en=True,  mul_ip=True, ip_scal1=0.9,ip_scal2=0.5)
#pipe_t2i_sip=Df.pipeline_init("t2i_ip",model=model, lcm_lora_en=True,  single_ip=True,ip_scal1=0.6)
#pipe_i2i_sip=Df.pipeline_init("i2i_ip",model=model, lcm_lora_en=True,  single_ip=True,ip_scal1=0.6)

# 利用できるpipeline名 → i2i/canny/pipe_i2i/pipe_inpaint/canny/openpose//depth/t2i_adapter
#--------pipeline名からpipeを選択して返す
def pipline_sel(pipeline):
    if pipeline=="t2i":
         return pipe_t2i
    elif  pipeline=="pipe_i2i":
         return pipe_i2i
    elif  pipeline=="pipe_inpaint":
         return pipe_inpaint
    elif  pipeline=="canny":
         return pipe_canny
    elif  pipeline=="openpose":
         return pipe_openpose
    elif  pipeline=="depth":
         return pipe_depth 
    elif  pipeline=="t2i_adapter":
         return pipe_t2i_adapter
    elif  pipeline=="t2i_sip":
         return pipe_t2i_sip
    elif  pipeline=="t2i_mip":
         return pipe_t2i_mip
    elif  pipeline=="i2i_sip":
         return pipe_i2i_sip
    elif  pipeline=="i2i_mip":
         return pipe_i2i_mip
    else:
        return pipe_t2i

#-------------------- FastAPI エンドポイント--------------------------
   
@app.post("/get_canny/")
def get_canny(image: UploadFile = File(...),  low_threshold : int = Form(100) , high_threshold: int = Form(200)):
    image_data = image.file.read()
    image_data = Image.open(BytesIO( image_data))  # バイナリデータをPIL形式に変換
    image_data = image_data.convert("RGB")
    canny_image = Df.get_canny(original_image=image_data , low_threshold = low_threshold , high_threshold =high_threshold)
    canny_data = pickle.dumps(canny_image, 5)  # tx_dataはpklデータ
    return Response(content=canny_data , media_type="application/octet-stream")

@app.post("/get_openpose/")
def get_openpose(image: UploadFile = File(...)):
    image_data = image.file.read()
    image_data = Image.open(BytesIO( image_data))  # バイナリデータをPIL形式に変換
    image_data = image_data.convert("RGB")
    openpose_image = Df.get_openpose(image_data )    
    openpose_data = pickle.dumps(openpose_image, 5)  # tx_dataはpklデータ
    return Response(content= openpose_data , media_type="application/octet-stream")

@app.post("/get_depth_map/")
def get_depth_map(image: UploadFile = File(...)):
    image_data = image.file.read()
    image_data = Image.open(BytesIO( image_data))  # バイナリデータをPIL形式に変換
    image_data = image_data.convert("RGB")
    depth_image = Df.get_depth_map( image_data)
    depth_data = pickle.dumps(depth_image , 5)  # tx_dataはpklデータ
    return Response(content= depth_data, media_type="application/octet-stream")

@app.post("/generate/")
def generate(
    pipeline: str = Form(...),
    image: UploadFile = File(None),
    mask_image: UploadFile =  File(None),
    embe:bool =  Form(False),
    prompt_embe:list =  Form(None),
    prompt_embe0:str =  Form(None),
    prompt_embe1:str =  Form(None),
    prompt:str = Form(None),
    negative_prompt:str = Form(None),
    num_inference_steps:int =Form(4),
    strength:float = Form(0.1),
    guidance_scale:float = Form(1.0),
    seed:int= Form(0),
    auto_seed:bool = Form(True),
    height :int= Form(None),
    width:int = Form(None),
    num_images_per_prompt:int = Form(1),
    guess_mode:bool = Form(None),
    freeu:bool = Form(False),
    freeu_list:list=Form(None),
    output_type:str = Form("pil"),
    crops_coords_top_left:list = Form([0,0]),
    controlnet_conditioning_scale:float = Form(1.0),
    adapter_conditioning_scale:float=Form(None),
    adapter_conditioning_factor:int=Form(None),
    ip_adapter:bool=Form(False),
    #style_images=Form(None),
    style_images:UploadFile =File(None),   
    ip_image:UploadFile =File(None),   
    ):
    #−−−−−pipeの設定
    pipe = pipline_sel(pipeline)
    #−−−−−conditioning, pooledがGPUにTensor形式でロードされるのでprompt_embeddingをサーバ側で実行
    if embe:
        prompt_embe=[prompt_embe0,prompt_embe1]
        conditioning, pooled = Df.prompt_embedding(pipe , prompt_embe)
    else:    #embeがFalseならconditioning、pooled はNoneを設定
        conditioning = None
        pooled = None
    #−−−−−Seed値を取得 auto_seedがTrueならtorch.initial_seed( )

    if seed == None:    
        if auto_seed: #seedが無くてauto_seedが有効(True)の時
            seedx= torch.initial_seed( )
            generator = torch.manual_seed(seedx )
        else:                   #seedが無くてauto_seedが無効(False)の時
           generator = None
    else: #seedが指定された場合
        generator = torch.manual_seed(seed )
    """
    if auto_seed:
        seedx= torch.initial_seed( )
        generator = torch.manual_seed(seedx )
    else:
        if seed == None:    #auto_seedがFalseでseedがない場合
            generator = None
        else:                          #auto_seedがFalseでseedが指定された場合
            generator = torch.manual_seed(seed )
    """
    # −−−−− 受け取ったイメージをPIL(RGB)へ変換
    if image:                     #inpaint input イメージ
        image_data =image.file.read()
        image = Image.open(BytesIO(image_data))  # バイナリデータをPIL形式に変換
        image = image.convert("RGB")
    else:
        image = None
    if mask_image:        #inpaint マスク イメージ
            mask_data =mask_image.file.read()
            mask_image = Image.open(BytesIO( mask_data))  # バイナリデータをPIL形式に変換
            mask_image = mask_image.convert("RGB")
    else:
        mask_image = None
    # −−−−− IP-Adapter用の画像の再現      
    if ip_adapter:
        if ip_image:
            ip_data= ip_image.file.read()
            ip_image = Image.open(BytesIO(ip_data))  # バイナリデータをPIL形式に変換
            ip_image = ip_image.convert("RGB")
             #face_image .show()
    # −−−−− "t2i_mip"と"i2i_mip"の時、受け取ったStyle IPイメージをstyle_images_listへ復元
            if pipeline=="t2i_mip" or  pipeline=="i2i_mip":
                style_images_rb=style_images.file.read()
                style_images_list = pickle.loads(style_images_rb)
            else:
                style_images_list=None
    else:
        ip_image =None
        style_images_list=None
        
    #−−−−−freeuの設定
    if freeu:
        #freeu_list=eval(freeu_list[0])
        freeu_list=[float(item) for item in freeu_list]
        print("++++++++++++++++++++++++++++++++++++++  freeu_list", freeu_list)
    crops_coords_top_left=(crops_coords_top_left[0],crops_coords_top_left[1])
    #−−−−−こから画像の生成
    # t2i_adapter専用
    if pipeline=="t2i_adapter":
        image_list = Df.generate_t2a_adapter(
             pipe,
             image=image,
             prompt=prompt ,
             negative_prompt = negative_prompt,
             generator=generator,
             height = height,
             width = width,
             num_images_per_prompt = num_images_per_prompt,
             freeu=freeu,
             freeu_list=freeu_list,
             output_type=output_type,
             num_inference_steps= num_inference_steps,
             adapter_conditioning_scale=adapter_conditioning_scale,   
             adapter_conditioning_factor= adapter_conditioning_factor,
             guidance_scale=guidance_scale,
        )
    # t2i/i2i/inpaint/canny/openpose/depth
    else:
        image_list = Df.generate(
             pipe,
             image=image,
             prompt=prompt ,
             mask_image=mask_image,
             embe=embe,
             conditioning=conditioning ,#prompt_embeds=conditioning,
             pooled=pooled ,#pooled_prompt_embeds=pooled,
             negative_prompt = negative_prompt,
             num_inference_steps= num_inference_steps,
             strength=strength,
             guidance_scale=guidance_scale,
             generator=generator,
             height = height,
             width = width,
             num_images_per_prompt = num_images_per_prompt,
             guess_mode=True,
             freeu=freeu,
             freeu_list=freeu_list,
             output_type=output_type,
             crops_coords_top_left=crops_coords_top_left,
             controlnet_conditioning_scale=controlnet_conditioning_scale,
             ip_adapter=ip_adapter,
             ip_image = ip_image ,
             style_images=style_images_list,
        )
    #−−−−−生成画像を返信
    images_data = pickle.dumps(image_list, 5)  # tx_dataはpklデータ
    return Response(content= images_data, media_type="application/octet-stream")
 
if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)