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'', 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"""JSON""", 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=""" """, 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""" JSON""" 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) ), ]