upload a cn

This commit is contained in:
lllyasviel
2024-01-27 10:34:31 -08:00
parent ef02d8fa39
commit ccea2f3305
940 changed files with 150956 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
import gradio as gr
from typing import List
class ModalInterface(gr.Interface):
modal_id_counter = 0
def __init__(
self,
html_content: str,
open_button_text: str,
open_button_classes: List[str] = [],
open_button_extra_attrs: str = ''
):
self.html_content = html_content
self.open_button_text = open_button_text
self.open_button_classes = open_button_classes
self.open_button_extra_attrs = open_button_extra_attrs
self.modal_id = ModalInterface.modal_id_counter
ModalInterface.modal_id_counter += 1
def __call__(self):
return self.create_modal()
def create_modal(self, visible=True):
html_code = f"""
<div id="cnet-modal-{self.modal_id}" class="cnet-modal">
<span class="cnet-modal-close">&times;</span>
<div class="cnet-modal-content">
{self.html_content}
</div>
</div>
<div id="cnet-modal-open-{self.modal_id}"
class="cnet-modal-open {' '.join(self.open_button_classes)}"
{self.open_button_extra_attrs}
>{self.open_button_text}</div>
"""
return gr.HTML(value=html_code, visible=visible)

View File

@@ -0,0 +1,154 @@
import base64
import gradio as gr
import json
from typing import List, Dict, Any, Tuple
from annotator.openpose import decode_json_as_poses, draw_poses
from annotator.openpose.animalpose import draw_animalposes
from scripts.controlnet_ui.modal import ModalInterface
from modules import shared
from scripts.logging import logger
def parse_data_url(data_url: str):
# Split the URL at the comma
media_type, data = data_url.split(",", 1)
# Check if the data is base64-encoded
assert ";base64" in media_type
# Decode the base64 data
return base64.b64decode(data)
def encode_data_url(json_string: str) -> str:
base64_encoded_json = base64.b64encode(json_string.encode("utf-8")).decode("utf-8")
return f"data:application/json;base64,{base64_encoded_json}"
class OpenposeEditor(object):
# Filename used when user click the download link.
download_file = "pose.json"
# URL the openpose editor is mounted on.
editor_url = "/openpose_editor_index"
def __init__(self) -> None:
self.render_button = None
self.pose_input = None
self.download_link = None
self.upload_link = None
self.modal = None
def render_edit(self):
"""Renders the buttons in preview image control button group."""
# The hidden button to trigger a re-render of generated image.
self.render_button = gr.Button(visible=False, elem_classes=["cnet-render-pose"])
# The hidden element that stores the pose json for backend retrieval.
# The front-end javascript will write the edited JSON data to the element.
self.pose_input = gr.Textbox(visible=False, elem_classes=["cnet-pose-json"])
self.modal = ModalInterface(
# Use about:blank here as placeholder so that the iframe does not
# immediately navigate. Most of controlnet units do not need
# openpose editor active. Only navigate when the user first click
# 'Edit'. The navigation logic is in `openpose_editor.js`.
f'<iframe src="about:blank"></iframe>',
open_button_text="Edit",
open_button_classes=["cnet-edit-pose"],
open_button_extra_attrs=f'title="Send pose to {OpenposeEditor.editor_url} for edit."',
).create_modal(visible=False)
self.download_link = gr.HTML(
value=f"""<a href='' download='{OpenposeEditor.download_file}'>JSON</a>""",
visible=False,
elem_classes=["cnet-download-pose"],
)
def render_upload(self):
"""Renders the button in input image control button group."""
self.upload_link = gr.HTML(
value="""
<label>Upload JSON</label>
<input type="file" accept=".json"/>
""",
visible=False,
elem_classes=["cnet-upload-pose"],
)
def register_callbacks(
self,
generated_image: gr.Image,
use_preview_as_input: gr.Checkbox,
model: gr.Dropdown,
):
def render_pose(pose_url: str) -> Tuple[Dict, Dict]:
json_string = parse_data_url(pose_url).decode("utf-8")
poses, animals, height, width = decode_json_as_poses(
json.loads(json_string)
)
logger.info("Preview as input is enabled.")
return (
# Generated image.
gr.update(
value=(
draw_poses(
poses,
height,
width,
draw_body=True,
draw_hand=True,
draw_face=True,
)
if poses
else draw_animalposes(animals, height, width)
),
visible=True,
),
# Use preview as input.
gr.update(value=True),
# Self content.
*self.update(json_string),
)
self.render_button.click(
fn=render_pose,
inputs=[self.pose_input],
outputs=[generated_image, use_preview_as_input, *self.outputs()],
)
def update_upload_link(model: str) -> Dict:
return gr.update(visible="openpose" in model.lower())
model.change(fn=update_upload_link, inputs=[model], outputs=[self.upload_link])
def outputs(self) -> List[Any]:
return [
self.download_link,
self.modal,
]
def update(self, json_string: str) -> List[Dict]:
"""
Called when there is a new JSON pose value generated by running
preprocessor.
Args:
json_string: The new JSON string generated by preprocessor.
Returns:
An gr.update event.
"""
hint = "Download the pose as .json file"
html = f"""<a href='{encode_data_url(json_string)}'
download='{OpenposeEditor.download_file}' title="{hint}">
JSON</a>"""
visible = json_string != ""
return [
# Download link update.
gr.update(value=html, visible=visible),
# Modal update.
gr.update(
visible=visible
and not shared.opts.data.get("controlnet_disable_openpose_edit", False)
),
]

View File

@@ -0,0 +1,182 @@
import gradio as gr
from scripts.controlnet_ui.modal import ModalInterface
PHOTOPEA_LOGO = """
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="100%" viewBox="0 0 256 256" enable-background="new 0 0 256 256" xml:space="preserve"
style="width: 0.75rem; height 0.75rem; margin-left: 2px;"
>
<path fill="#18A497" opacity="1.000000" stroke="none"
d="
M1.000000,228.000000
C1.000000,162.312439 1.000000,96.624878 1.331771,30.719650
C2.026278,30.171114 2.594676,29.904894 2.721949,29.500008
C6.913495,16.165672 15.629609,7.322631 28.880219,2.875538
C29.404272,2.699659 29.633436,1.645129 30.000000,1.000000
C95.687561,1.000000 161.375122,1.000000 227.258057,1.317018
C227.660217,1.893988 227.815079,2.296565 228.081207,2.393433
C241.304657,7.206383 250.980164,15.550970 255.215851,29.410040
C255.321625,29.756128 256.383850,29.809898 257.000000,30.000000
C257.000000,95.687561 257.000000,161.375122 256.682983,227.257858
C256.106049,227.659790 255.699371,227.815521 255.607178,228.080658
C250.953033,241.462830 242.292618,250.822968 228.591782,255.214935
C228.239929,255.327698 228.190491,256.383820 228.000000,257.000000
C175.312439,257.000000 122.624878,257.000000 69.468582,256.531342
C68.672188,244.948196 68.218323,233.835587 68.052299,222.718674
C67.885620,211.557587 67.886772,200.390717 68.027298,189.229050
C68.255180,171.129044 68.084618,152.997421 69.151917,134.942368
C70.148468,118.083969 77.974228,103.689308 89.758743,91.961365
C104.435837,77.354736 122.313736,69.841736 143.417328,69.901505
C168.662338,69.972984 186.981964,90.486633 187.961487,114.156334
C189.042435,140.277435 166.783981,163.607941 140.303482,160.823074
C137.092346,160.485382 133.490692,158.365784 131.192612,155.987366
C126.434669,151.063141 126.975357,144.720825 129.168777,138.834930
C131.533630,132.489014 137.260605,130.548050 143.413757,130.046677
C150.288467,129.486496 156.424942,123.757378 157.035324,117.320816
C157.953949,107.633820 150.959381,101.769096 145.533951,101.194389
C132.238846,99.786079 120.699944,104.963120 111.676735,114.167313
C102.105782,123.930222 97.469498,136.194061 99.003151,150.234955
C100.540352,164.308228 107.108505,175.507980 118.864334,183.311539
C128.454544,189.677597 138.866959,191.786957 150.657837,190.245651
C166.242554,188.208420 179.874283,182.443329 191.251801,172.056793
C209.355011,155.530380 217.848694,134.938721 216.116119,110.085892
C214.834335,91.699440 207.721039,76.015915 195.289444,62.978828
C175.658447,42.391735 150.833389,37.257801 123.833740,42.281937
C98.675804,46.963364 78.315033,60.084667 62.208153,80.157814
C46.645889,99.552216 39.305275,121.796379 39.149052,146.201981
C38.912663,183.131317 39.666767,220.067017 40.000000,257.000000
C36.969406,257.000000 33.938812,257.000000 30.705070,256.668213
C30.298622,256.078369 30.144913,255.669220 29.884926,255.583878
C16.317770,251.131058 7.127485,242.317780 2.778462,228.591797
C2.667588,228.241821 1.613958,228.190567 1.000000,228.000000
z"/>
<path fill="#000000" opacity="1.000000" stroke="none"
d="
M40.468658,257.000000
C39.666767,220.067017 38.912663,183.131317 39.149052,146.201981
C39.305275,121.796379 46.645889,99.552216 62.208153,80.157814
C78.315033,60.084667 98.675804,46.963364 123.833740,42.281937
C150.833389,37.257801 175.658447,42.391735 195.289444,62.978828
C207.721039,76.015915 214.834335,91.699440 216.116119,110.085892
C217.848694,134.938721 209.355011,155.530380 191.251801,172.056793
C179.874283,182.443329 166.242554,188.208420 150.657837,190.245651
C138.866959,191.786957 128.454544,189.677597 118.864334,183.311539
C107.108505,175.507980 100.540352,164.308228 99.003151,150.234955
C97.469498,136.194061 102.105782,123.930222 111.676735,114.167313
C120.699944,104.963120 132.238846,99.786079 145.533951,101.194389
C150.959381,101.769096 157.953949,107.633820 157.035324,117.320816
C156.424942,123.757378 150.288467,129.486496 143.413757,130.046677
C137.260605,130.548050 131.533630,132.489014 129.168777,138.834930
C126.975357,144.720825 126.434669,151.063141 131.192612,155.987366
C133.490692,158.365784 137.092346,160.485382 140.303482,160.823074
C166.783981,163.607941 189.042435,140.277435 187.961487,114.156334
C186.981964,90.486633 168.662338,69.972984 143.417328,69.901505
C122.313736,69.841736 104.435837,77.354736 89.758743,91.961365
C77.974228,103.689308 70.148468,118.083969 69.151917,134.942368
C68.084618,152.997421 68.255180,171.129044 68.027298,189.229050
C67.886772,200.390717 67.885620,211.557587 68.052299,222.718674
C68.218323,233.835587 68.672188,244.948196 68.999924,256.531342
C59.645771,257.000000 50.291542,257.000000 40.468658,257.000000
z"/>
<path fill="#000000" opacity="1.000000" stroke="none"
d="
M257.000000,29.531342
C256.383850,29.809898 255.321625,29.756128 255.215851,29.410040
C250.980164,15.550970 241.304657,7.206383 228.081207,2.393433
C227.815079,2.296565 227.660217,1.893988 227.726715,1.317018
C237.593155,1.000000 247.186295,1.000000 257.000000,1.000000
C257.000000,10.353075 257.000000,19.707878 257.000000,29.531342
z"/>
<path fill="#000000" opacity="1.000000" stroke="none"
d="
M228.468658,257.000000
C228.190491,256.383820 228.239929,255.327698 228.591782,255.214935
C242.292618,250.822968 250.953033,241.462830 255.607178,228.080658
C255.699371,227.815521 256.106049,227.659790 256.682983,227.726517
C257.000000,237.593155 257.000000,247.186295 257.000000,257.000000
C247.646927,257.000000 238.292114,257.000000 228.468658,257.000000
z"/>
<path fill="#000000" opacity="1.000000" stroke="none"
d="
M1.000000,228.468658
C1.613958,228.190567 2.667588,228.241821 2.778462,228.591797
C7.127485,242.317780 16.317770,251.131058 29.884926,255.583878
C30.144913,255.669220 30.298622,256.078369 30.250959,256.668213
C20.406853,257.000000 10.813705,257.000000 1.000000,257.000000
C1.000000,247.646927 1.000000,238.292114 1.000000,228.468658
z"/>
<path fill="#000000" opacity="1.000000" stroke="none"
d="
M29.531342,1.000000
C29.633436,1.645129 29.404272,2.699659 28.880219,2.875538
C15.629609,7.322631 6.913495,16.165672 2.721949,29.500008
C2.594676,29.904894 2.026278,30.171114 1.331771,30.250992
C1.000000,20.406855 1.000000,10.813709 1.000000,1.000000
C10.353074,1.000000 19.707878,1.000000 29.531342,1.000000
z"/>
</svg>"""
class Photopea(object):
def __init__(self) -> None:
self.modal = None
self.triggers = []
self.render_editor()
def render_editor(self):
"""Render the editor modal."""
with gr.Group(elem_classes=["cnet-photopea-edit"]):
self.modal = ModalInterface(
# Use about:blank here as placeholder so that the iframe does not
# immediately navigate. Only navigate when the user first click
# 'Edit'. The navigation logic is in `photopea.js`.
f"""
<div class="photopea-button-group">
<button class="photopea-button photopea-fetch">Fetch from ControlNet</button>
<button class="photopea-button photopea-send">Send to ControlNet</button>
</div>
<iframe class="photopea-iframe" src="about:blank"></iframe>
""",
open_button_text="Edit",
open_button_classes=["cnet-photopea-main-trigger"],
open_button_extra_attrs="hidden",
).create_modal(visible=True)
def render_child_trigger(self):
self.triggers.append(
gr.HTML(
f"""<div class="cnet-photopea-child-trigger">
Edit {PHOTOPEA_LOGO}
</div>"""
)
)
def attach_photopea_output(self, generated_image: gr.Image):
"""Called in ControlNetUiGroup to attach preprocessor preview image Gradio element
as the photopea output. If the front-end directly change the img HTML element's src
to reflect the edited image result from photopea, the backend won't be notified.
In this method we let the front-end upload the result image an invisible gr.Image
instance and mirrors the value to preprocessor preview gr.Image. This is because
the generated image gr.Image instance is inferred to be an output image by Gradio
and has no ability to accept image upload directly.
Arguments:
generated_image: preprocessor result Gradio Image output element.
Returns:
None
"""
output = gr.Image(
visible=False,
source="upload",
type="numpy",
elem_classes=[f"cnet-photopea-output"],
)
output.upload(
fn=lambda img: img,
inputs=[output],
outputs=[generated_image],
)

View File

@@ -0,0 +1,319 @@
import os
import gradio as gr
from typing import Dict, List
from modules import scripts
from scripts.infotext import parse_unit, serialize_unit
from scripts.controlnet_ui.tool_button import ToolButton
from scripts.logging import logger
from scripts.processor import preprocessor_filters
from scripts import external_code
save_symbol = "\U0001f4be" # 💾
delete_symbol = "\U0001f5d1\ufe0f" # 🗑️
refresh_symbol = "\U0001f504" # 🔄
reset_symbol = "\U000021A9" # ↩
NEW_PRESET = "New Preset"
def load_presets(preset_dir: str) -> Dict[str, str]:
if not os.path.exists(preset_dir):
os.makedirs(preset_dir)
return {}
presets = {}
for filename in os.listdir(preset_dir):
if filename.endswith(".txt"):
with open(os.path.join(preset_dir, filename), "r") as f:
name = filename.replace(".txt", "")
if name == NEW_PRESET:
continue
presets[name] = f.read()
return presets
def infer_control_type(module: str, model: str) -> str:
def matches_control_type(input_string: str, control_type: str) -> bool:
return any(t.lower() in input_string for t in control_type.split("/"))
control_types = preprocessor_filters.keys()
control_type_candidates = [
control_type
for control_type in control_types
if (
matches_control_type(module, control_type)
or matches_control_type(model, control_type)
)
]
if len(control_type_candidates) != 1:
raise ValueError(
f"Unable to infer control type from module {module} and model {model}"
)
return control_type_candidates[0]
class ControlNetPresetUI(object):
preset_directory = os.path.join(scripts.basedir(), "presets")
presets = load_presets(preset_directory)
def __init__(self, id_prefix: str):
with gr.Row():
self.dropdown = gr.Dropdown(
label="Presets",
show_label=True,
elem_classes=["cnet-preset-dropdown"],
choices=ControlNetPresetUI.dropdown_choices(),
value=NEW_PRESET,
)
self.reset_button = ToolButton(
value=reset_symbol,
elem_classes=["cnet-preset-reset"],
tooltip="Reset preset",
visible=False,
)
self.save_button = ToolButton(
value=save_symbol,
elem_classes=["cnet-preset-save"],
tooltip="Save preset",
)
self.delete_button = ToolButton(
value=delete_symbol,
elem_classes=["cnet-preset-delete"],
tooltip="Delete preset",
)
self.refresh_button = ToolButton(
value=refresh_symbol,
elem_classes=["cnet-preset-refresh"],
tooltip="Refresh preset",
)
with gr.Box(
elem_classes=["popup-dialog", "cnet-preset-enter-name"],
elem_id=f"{id_prefix}_cnet_preset_enter_name",
) as self.name_dialog:
with gr.Row():
self.preset_name = gr.Textbox(
label="Preset name",
show_label=True,
lines=1,
elem_classes=["cnet-preset-name"],
)
self.confirm_preset_name = ToolButton(
value=save_symbol,
elem_classes=["cnet-preset-confirm-name"],
tooltip="Save preset",
)
def register_callbacks(
self,
uigroup,
control_type: gr.Radio,
*ui_states,
):
def apply_preset(name: str, control_type: str, *ui_states):
if name == NEW_PRESET:
return (
gr.update(visible=False),
*(
(gr.skip(),)
* (len(vars(external_code.ControlNetUnit()).keys()) + 1)
),
)
assert name in ControlNetPresetUI.presets
infotext = ControlNetPresetUI.presets[name]
preset_unit = parse_unit(infotext)
current_unit = external_code.ControlNetUnit(*ui_states)
preset_unit.image = None
current_unit.image = None
# Do not compare module param that are not used in preset.
for module_param in ("processor_res", "threshold_a", "threshold_b"):
if getattr(preset_unit, module_param) == -1:
setattr(current_unit, module_param, -1)
# No update necessary.
if vars(current_unit) == vars(preset_unit):
return (
gr.update(visible=False),
*(
(gr.skip(),)
* (len(vars(external_code.ControlNetUnit()).keys()) + 1)
),
)
unit = preset_unit
try:
new_control_type = infer_control_type(unit.module, unit.model)
except ValueError as e:
logger.error(e)
new_control_type = control_type
if new_control_type != control_type:
uigroup.prevent_next_n_module_update += 1
if preset_unit.module != current_unit.module:
uigroup.prevent_next_n_slider_value_update += 1
if preset_unit.pixel_perfect != current_unit.pixel_perfect:
uigroup.prevent_next_n_slider_value_update += 1
return (
gr.update(visible=True),
gr.update(value=new_control_type),
*[
gr.update(value=value) if value is not None else gr.update()
for value in vars(unit).values()
],
)
for element, action in (
(self.dropdown, "change"),
(self.reset_button, "click"),
):
getattr(element, action)(
fn=apply_preset,
inputs=[self.dropdown, control_type, *ui_states],
outputs=[self.delete_button, control_type, *ui_states],
show_progress="hidden",
).then(
fn=lambda: gr.update(visible=False),
inputs=None,
outputs=[self.reset_button],
)
def save_preset(name: str, *ui_states):
if name == NEW_PRESET:
return gr.update(visible=True), gr.update(), gr.update()
ControlNetPresetUI.save_preset(
name, external_code.ControlNetUnit(*ui_states)
)
return (
gr.update(), # name dialog
gr.update(choices=ControlNetPresetUI.dropdown_choices(), value=name),
gr.update(visible=False), # Reset button
)
self.save_button.click(
fn=save_preset,
inputs=[self.dropdown, *ui_states],
outputs=[self.name_dialog, self.dropdown, self.reset_button],
show_progress="hidden",
).then(
fn=None,
_js=f"""
(name) => {{
if (name === "{NEW_PRESET}")
popup(gradioApp().getElementById('{self.name_dialog.elem_id}'));
}}""",
inputs=[self.dropdown],
)
def delete_preset(name: str):
ControlNetPresetUI.delete_preset(name)
return gr.Dropdown.update(
choices=ControlNetPresetUI.dropdown_choices(),
value=NEW_PRESET,
), gr.update(visible=False)
self.delete_button.click(
fn=delete_preset,
inputs=[self.dropdown],
outputs=[self.dropdown, self.reset_button],
show_progress="hidden",
)
self.name_dialog.visible = False
def save_new_preset(new_name: str, *ui_states):
if new_name == NEW_PRESET:
logger.warn(f"Cannot save preset with reserved name '{NEW_PRESET}'")
return gr.update(visible=False), gr.update()
ControlNetPresetUI.save_preset(
new_name, external_code.ControlNetUnit(*ui_states)
)
return gr.update(visible=False), gr.update(
choices=ControlNetPresetUI.dropdown_choices(), value=new_name
)
self.confirm_preset_name.click(
fn=save_new_preset,
inputs=[self.preset_name, *ui_states],
outputs=[self.name_dialog, self.dropdown],
show_progress="hidden",
).then(fn=None, _js="closePopup")
self.refresh_button.click(
fn=ControlNetPresetUI.refresh_preset,
inputs=None,
outputs=[self.dropdown],
show_progress="hidden",
)
def update_reset_button(preset_name: str, *ui_states):
if preset_name == NEW_PRESET:
return gr.update(visible=False)
infotext = ControlNetPresetUI.presets[preset_name]
preset_unit = parse_unit(infotext)
current_unit = external_code.ControlNetUnit(*ui_states)
preset_unit.image = None
current_unit.image = None
# Do not compare module param that are not used in preset.
for module_param in ("processor_res", "threshold_a", "threshold_b"):
if getattr(preset_unit, module_param) == -1:
setattr(current_unit, module_param, -1)
return gr.update(visible=vars(current_unit) != vars(preset_unit))
for ui_state in ui_states:
if isinstance(ui_state, gr.Image):
continue
for action in ("edit", "click", "change", "clear", "release"):
if action == "release" and not isinstance(ui_state, gr.Slider):
continue
if hasattr(ui_state, action):
getattr(ui_state, action)(
fn=update_reset_button,
inputs=[self.dropdown, *ui_states],
outputs=[self.reset_button],
)
@staticmethod
def dropdown_choices() -> List[str]:
return list(ControlNetPresetUI.presets.keys()) + [NEW_PRESET]
@staticmethod
def save_preset(name: str, unit: external_code.ControlNetUnit):
infotext = serialize_unit(unit)
with open(
os.path.join(ControlNetPresetUI.preset_directory, f"{name}.txt"), "w"
) as f:
f.write(infotext)
ControlNetPresetUI.presets[name] = infotext
@staticmethod
def delete_preset(name: str):
if name not in ControlNetPresetUI.presets:
return
del ControlNetPresetUI.presets[name]
file = os.path.join(ControlNetPresetUI.preset_directory, f"{name}.txt")
if os.path.exists(file):
os.unlink(file)
@staticmethod
def refresh_preset():
ControlNetPresetUI.presets = load_presets(ControlNetPresetUI.preset_directory)
return gr.update(choices=ControlNetPresetUI.dropdown_choices())

View File

@@ -0,0 +1,12 @@
import gradio as gr
class ToolButton(gr.Button, gr.components.FormComponent):
"""Small button with single emoji as text, fits inside gradio forms"""
def __init__(self, **kwargs):
super().__init__(variant="tool",
elem_classes=kwargs.pop('elem_classes', []) + ["cnet-toolbutton"],
**kwargs)
def get_block_name(self):
return "button"