|
import gradio as gr |
|
import torch |
|
import numpy as np |
|
import spaces |
|
from utils import utils, tools, preprocess |
|
|
|
BASE_MODEL_REPO_ID = "neta-art/neta-xl-2.0" |
|
BASE_MODEL_FILENAME = "neta-xl-v2.fp16.safetensors" |
|
VAE_PATH = "madebyollin/sdxl-vae-fp16-fix" |
|
CONTROLNEXT_REPO_ID = "Eugeoter/controlnext-sdxl-anime-canny" |
|
CACHE_DIR = None |
|
|
|
DEFAULT_PROMPT = "" |
|
DEFAULT_NEGATIVE_PROMPT = "worst quality, abstract, clumsy pose, deformed hand, dynamic malformation, fused fingers, extra digits, fewer digits, fewer fingers, extra fingers, extra arm, missing arm, extra leg, missing leg, signature, artist name, multi views, disfigured, ugly" |
|
|
|
|
|
def ui(): |
|
device = "cuda" if torch.cuda.is_available() else "cpu" |
|
pipeline = tools.get_pipeline( |
|
pretrained_model_name_or_path=BASE_MODEL_REPO_ID, |
|
unet_model_name_or_path=CONTROLNEXT_REPO_ID, |
|
controlnet_model_name_or_path=CONTROLNEXT_REPO_ID, |
|
vae_model_name_or_path=VAE_PATH, |
|
load_weight_increasement=True, |
|
device=device, |
|
hf_cache_dir=CACHE_DIR, |
|
use_safetensors=True, |
|
) |
|
|
|
schedulers = ['Euler A', 'UniPC', 'Euler', 'DDIM', 'DDPM'] |
|
|
|
css = """ |
|
#col-container { |
|
margin: 0 auto; |
|
max-width: 520px; |
|
} |
|
""" |
|
|
|
with gr.Blocks(css=css) as demo: |
|
gr.Markdown(f""" |
|
# [ControlNeXt-SDXL](https://github.com/dvlab-research/ControlNeXt) Demo (Anime Canny) |
|
Base model: [Neta-Art-XL-2.0](https://civitai.com/models/410737/neta-art-xl) |
|
""") |
|
with gr.Row(): |
|
with gr.Column(scale=9): |
|
prompt = gr.Textbox(label='Prompt', value=DEFAULT_PROMPT, lines=3, placeholder='prompt', container=False) |
|
negative_prompt = gr.Textbox(label='Negative Prompt', value=DEFAULT_NEGATIVE_PROMPT, lines=3, placeholder='negative prompt', container=False) |
|
with gr.Column(scale=1): |
|
generate_button = gr.Button("Generate", variant='primary', min_width=96) |
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
with gr.Row(): |
|
control_image = gr.Image( |
|
value=None, |
|
label='Condition', |
|
sources=['upload'], |
|
type='pil', |
|
height=512, |
|
image_mode='RGB', |
|
format='png', |
|
show_download_button=True, |
|
show_share_button=True, |
|
) |
|
with gr.Accordion(label='Preprocess', open=True): |
|
with gr.Row(): |
|
threshold1 = gr.Slider(minimum=-1, maximum=255, step=1, value=-1, label='Threshold 1', info='-1 for auto') |
|
threshold2 = gr.Slider(minimum=-1, maximum=255, step=1, value=-1, label='Threshold 2', info='-1 for auto') |
|
process_button = gr.Button("Process", variant='primary', min_width=96, scale=0) |
|
with gr.Row(): |
|
scheduler = gr.Dropdown( |
|
label='Scheduler', |
|
choices=schedulers, |
|
value='Euler A', |
|
multiselect=False, |
|
allow_custom_value=False, |
|
filterable=True, |
|
) |
|
num_inference_steps = gr.Slider(minimum=1, maximum=100, step=1, value=28, label='Steps') |
|
with gr.Row(): |
|
cfg_scale = gr.Slider(minimum=1, maximum=30, step=1, value=7.5, label='CFG Scale') |
|
controlnet_scale = gr.Slider(minimum=0, maximum=1, step=0.01, value=1, label='ControlNet Scale') |
|
with gr.Row(): |
|
seed = gr.Number(label='Seed', step=1, precision=0, value=-1) |
|
with gr.Column(scale=1): |
|
with gr.Row(): |
|
output = gr.Gallery( |
|
label='Output', |
|
value=None, |
|
object_fit='scale-down', |
|
columns=4, |
|
height=512, |
|
show_download_button=True, |
|
show_share_button=True, |
|
) |
|
|
|
with gr.Row(): |
|
examples = gr.Examples( |
|
label='Examples', |
|
examples=[ |
|
[ |
|
'best quality, 1girl, solo, open hand, outdoors, indoor, cute, young, cat, cat ear, glasses', |
|
'examples/example_1.jpg', |
|
], |
|
[ |
|
'best quality, 1 komeiji koishi, solo, the pose, indoors, smile', |
|
'examples/example_2.jpg', |
|
] |
|
], |
|
inputs=[ |
|
prompt, |
|
control_image, |
|
], |
|
cache_examples=False, |
|
) |
|
|
|
@spaces.GPU |
|
def generate( |
|
prompt, |
|
control_image, |
|
negative_prompt, |
|
cfg_scale, |
|
controlnet_scale, |
|
num_inference_steps, |
|
scheduler, |
|
seed, |
|
): |
|
pipeline.scheduler = tools.get_scheduler(scheduler, pipeline.scheduler.config) |
|
generator = torch.Generator(device=device).manual_seed(max(0, min(seed, np.iinfo(np.int32).max))) if seed != -1 else None |
|
|
|
if control_image is None: |
|
raise gr.Error('Please upload an image.') |
|
width, height = utils.around_reso(control_image.width, control_image.height, reso=1024, max_width=2048, max_height=2048, divisible=32) |
|
control_image = control_image.resize((width, height)).convert('RGB') |
|
|
|
with torch.autocast(device): |
|
output_images = pipeline.__call__( |
|
prompt=prompt, |
|
negative_prompt=negative_prompt, |
|
controlnet_image=control_image, |
|
controlnet_scale=controlnet_scale, |
|
width=width, |
|
height=height, |
|
generator=generator, |
|
guidance_scale=cfg_scale, |
|
num_inference_steps=num_inference_steps, |
|
).images |
|
|
|
return output_images |
|
|
|
def process( |
|
image, |
|
threshold1, |
|
threshold2, |
|
): |
|
threshold1 = None if threshold1 == -1 else threshold1 |
|
threshold2 = None if threshold2 == -1 else threshold2 |
|
return preprocess.canny_extractor(image, threshold1, threshold2) |
|
|
|
generate_button.click( |
|
fn=generate, |
|
inputs=[prompt, control_image, negative_prompt, cfg_scale, controlnet_scale, num_inference_steps, scheduler, seed], |
|
outputs=[output], |
|
) |
|
|
|
process_button.click( |
|
fn=process, |
|
inputs=[control_image, threshold1, threshold2], |
|
outputs=[control_image], |
|
) |
|
|
|
return demo |
|
|
|
|
|
if __name__ == '__main__': |
|
demo = ui() |
|
demo.queue().launch() |
|
|