├── .gitignore ├── images └── img1.jpg ├── README.md ├── metadata.ini └── scripts └── cn_in_extras_tab.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .vscode 3 | .directory 4 | -------------------------------------------------------------------------------- /images/img1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/light-and-ray/sd-webui-cn-in-extras-tab/HEAD/images/img1.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ControlNet Preprocessor in extras tab 2 | 3 | This extension adds controlnet preprocessing feature from [Mikubill/sd-webui-controlnet](https://github.com/Mikubill/sd-webui-controlnet) into [stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) into "Extras" tab. You need to have installed sd-webui-controlnet 4 | 5 | ![](images/img1.jpg) 6 | 7 | You can use it for processing videos, using my other extension: [sd-webui-video-extras-tab](https://github.com/light-and-ray/sd-webui-video-extras-tab) 8 | 9 | Also it supports `sd-webui-forge` 10 | -------------------------------------------------------------------------------- /metadata.ini: -------------------------------------------------------------------------------- 1 | # This section contains information about the extension itself. 2 | # This section is optional. 3 | [Extension] 4 | 5 | # A canonical name of the extension. 6 | # Only lowercase letters, numbers, dashes and underscores are allowed. 7 | # This is a unique identifier of the extension, and the loader will refuse to 8 | # load two extensions with the same name. If the name is not supplied, the 9 | # name of the extension directory is used. Other extensions can use this 10 | # name to refer to this extension in the file. 11 | Name = sd-webui-cn-in-extras-tab 12 | 13 | # A comma-or-space-separated list of extensions that this extension requires 14 | # to be installed and enabled. 15 | # The loader will generate a warning if any of the extensions in this list is 16 | # not installed or disabled. 17 | Requires = sd-webui-controlnet 18 | 19 | ; # Declaring relationships of folders 20 | ; # 21 | ; # This section declares relations of all files in `scripts` directory. 22 | ; # By changing the section name, it can also be used on other directories 23 | ; # walked by `load_scripts` function (for example `javascript` and `localization`). 24 | ; # This section is optional. 25 | ; [scripts] 26 | 27 | ; # A comma-or-space-separated list of extensions that files in this folder requires 28 | ; # to be present. 29 | ; # It is only allowed to specify an extension here. 30 | ; # The loader will generate a warning if any of the extensions in this list is 31 | ; # not installed or disabled. 32 | ; Requires = another-extension, yet-another-extension 33 | 34 | ; # A comma-or-space-separated list of extensions that files in this folder wants 35 | ; # to be loaded before. 36 | ; # It is only allowed to specify an extension here. 37 | ; # The loading order of all files in the specified folder will be moved so that 38 | ; # the files in the current extension are loaded before the files in the same 39 | ; # folder in the listed extension. 40 | ; Before = another-extension, yet-another-extension 41 | 42 | ; # A comma-or-space-separated list of extensions that files in this folder wants 43 | ; # to be loaded after. 44 | ; # Other details are the same as `Before` key. 45 | ; After = another-extension, yet-another-extension 46 | 47 | ; # Declaring relationships of a specific file 48 | ; # 49 | ; # This section declares relations of a specific file to files in the same 50 | ; # folder of other extensions. 51 | ; # By changing the section name, it can also be used on other directories 52 | ; # walked by `load_scripts` function (for example `javascript` and `localization`). 53 | ; # This section is optional. 54 | ; [scripts/another-script.py] 55 | 56 | ; # A comma-or-space-separated list of extensions/files that this file requires 57 | ; # to be present. 58 | ; # The `Requires` key in the folder section will be prepended to this list. 59 | ; # The loader will generate a warning if any of the extensions/files in this list is 60 | ; # not installed or disabled. 61 | ; # It is allowed to specify either an extension or a specific file. 62 | ; # When referencing a file, the folder name must be omitted. 63 | ; # 64 | ; # For example, the `yet-another-extension/another-script.py` item refers to 65 | ; # `scripts/another-script.py` in `yet-another-extension`. 66 | ; Requires = another-extension, yet-another-extension/another-script.py, xyz_grid.py 67 | 68 | ; # A comma-or-space-separated list of extensions that this file wants 69 | ; # to be loaded before. 70 | ; # The `Before` key in the folder section will be prepended to this list. 71 | ; # The loading order of this file will be moved so that this file is 72 | ; # loaded before the referenced file in the list. 73 | ; Before = another-extension, yet-another-extension/another-script.py, xyz_grid.py 74 | 75 | ; # A comma-or-space-separated list of extensions that this file wants 76 | ; # to be loaded after. 77 | ; # Other details are the same as `Before` key. 78 | ; After = another-extension, yet-another-extension/another-script.py, xyz_grid.py 79 | -------------------------------------------------------------------------------- /scripts/cn_in_extras_tab.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import numpy as np 3 | import gradio as gr 4 | from PIL import Image 5 | from modules import scripts_postprocessing 6 | from modules import shared, errors 7 | if hasattr(scripts_postprocessing.ScriptPostprocessing, 'process_firstpass'): # webui >= 1.7 8 | from modules.ui_components import InputAccordion 9 | else: 10 | InputAccordion = None 11 | 12 | try: 13 | from lib_controlnet import global_state 14 | supported_preprocessor = None 15 | IS_WEBUI_FORGE = True 16 | except ImportError: 17 | global_state = None 18 | supported_preprocessor = None 19 | IS_WEBUI_FORGE = False 20 | 21 | NAME = 'ControlNet Preprocessor' 22 | 23 | 24 | g_cn_modules = None 25 | def getCNModules(): 26 | global g_cn_modules 27 | if g_cn_modules is None: 28 | g_cn_modules = copy.copy(scripts.global_state.cn_preprocessor_modules) 29 | return g_cn_modules 30 | 31 | 32 | forbidden_pefixes = ['inpaint', 'tile', 't2ia_style', 'revision', 'reference', 33 | 'ip-adapter', 'instant_id_face_embedding', 'CLIP', 'InsightFace', 'facexlib'] 34 | 35 | g_preprocessor_names = None 36 | def getPreprocessorNames(): 37 | global g_preprocessor_names 38 | if g_preprocessor_names is None: 39 | if IS_WEBUI_FORGE: 40 | tmp_list = global_state.get_all_preprocessor_names() 41 | else: 42 | tmp_list = [p.label for p in supported_preprocessor.Preprocessor.get_sorted_preprocessors()] 43 | g_preprocessor_names = [] 44 | for preprocessor in tmp_list: 45 | if any(preprocessor.lower().startswith(x.lower()) for x in forbidden_pefixes): 46 | continue 47 | g_preprocessor_names.append(preprocessor) 48 | 49 | return g_preprocessor_names 50 | 51 | 52 | 53 | g_pixel_perfect_resolution = None 54 | g_resize_mode = None 55 | def getPixelPerfectResolution( 56 | image: np.ndarray, 57 | target_H: int, 58 | target_W: int): 59 | global g_pixel_perfect_resolution, g_resize_mode 60 | if g_pixel_perfect_resolution is None: 61 | if IS_WEBUI_FORGE: 62 | from lib_controlnet import external_code 63 | else: 64 | from scripts import external_code 65 | g_pixel_perfect_resolution = external_code.pixel_perfect_resolution 66 | g_resize_mode = external_code.ResizeMode.RESIZE 67 | 68 | return g_pixel_perfect_resolution( 69 | image, target_H=target_H, target_W=target_W, resize_mode=g_resize_mode) 70 | 71 | 72 | g_cn_HWC3 = None 73 | def convertIntoCNImageFromat(image): 74 | global g_cn_HWC3 75 | if g_cn_HWC3 is None: 76 | from annotator.util import HWC3 77 | g_cn_HWC3 = HWC3 78 | 79 | color = g_cn_HWC3(np.asarray(image).astype(np.uint8)) 80 | return color 81 | 82 | 83 | def convertImageIntoPILFormat(image): 84 | return Image.fromarray( 85 | np.ascontiguousarray(image.clip(0, 255).astype(np.uint8)).copy() 86 | ) 87 | 88 | 89 | def get_default_ui_unit(is_ui=True): 90 | if IS_WEBUI_FORGE: 91 | from lib_controlnet import external_code 92 | else: 93 | from scripts import external_code 94 | cls = external_code.ControlNetUnit 95 | return cls( 96 | enabled=False, 97 | module="none", 98 | model="None" 99 | ) 100 | 101 | class CNInExtrasTab(scripts_postprocessing.ScriptPostprocessing): 102 | name = NAME 103 | order = 18000 104 | 105 | def ui(self): 106 | global global_state, supported_preprocessor 107 | if global_state is None: 108 | from scripts import global_state 109 | from scripts import supported_preprocessor 110 | 111 | try: 112 | self.default_unit = get_default_ui_unit() 113 | with ( 114 | InputAccordion(False, label=NAME) if InputAccordion 115 | else gr.Accordion(NAME, open=False) 116 | as self.enable 117 | ): 118 | if not InputAccordion: 119 | self.enable = gr.Checkbox(False, label="Enable") 120 | with gr.Row(): 121 | modulesList = getPreprocessorNames() 122 | self.module = gr.Dropdown(modulesList, label="Module", value=modulesList[0]) 123 | self.pixel_perfect = gr.Checkbox( 124 | label="Pixel Perfect", 125 | value=True, 126 | elem_id=f"extras_controlnet_pixel_perfect_checkbox", 127 | ) 128 | with gr.Row(): 129 | self.create_sliders() 130 | self.register_build_sliders() 131 | args = { 132 | 'enable': self.enable, 133 | 'module': self.module, 134 | 'pixel_perfect': self.pixel_perfect, 135 | 'processor_res' : self.processor_res, 136 | 'threshold_a' : self.threshold_a, 137 | 'threshold_b' : self.threshold_b, 138 | } 139 | return args 140 | except Exception as e: 141 | errors.report(f"Cannot init {NAME}: {e}", exc_info=True) 142 | return {} 143 | 144 | 145 | def create_sliders(self): 146 | # advanced options 147 | with gr.Column(visible=False) as self.advanced: 148 | self.processor_res = gr.Slider( 149 | label="Preprocessor resolution", 150 | value=self.default_unit.processor_res, 151 | minimum=64, 152 | maximum=2048, 153 | visible=False, 154 | interactive=True, 155 | elem_id=f"extras_controlnet_preprocessor_resolution_slider", 156 | ) 157 | self.threshold_a = gr.Slider( 158 | label="Threshold A", 159 | value=self.default_unit.threshold_a, 160 | minimum=64, 161 | maximum=1024, 162 | visible=False, 163 | interactive=True, 164 | elem_id=f"extras_controlnet_threshold_A_slider", 165 | ) 166 | self.threshold_b = gr.Slider( 167 | label="Threshold B", 168 | value=self.default_unit.threshold_b, 169 | minimum=64, 170 | maximum=1024, 171 | visible=False, 172 | interactive=True, 173 | elem_id=f"extras_controlnet_threshold_B_slider", 174 | ) 175 | 176 | 177 | def register_build_sliders(self): 178 | def build_sliders(module: str, pp: bool): 179 | if IS_WEBUI_FORGE: 180 | preprocessor = global_state.get_preprocessor(module) 181 | else: 182 | preprocessor = supported_preprocessor.Preprocessor.get_preprocessor(module) 183 | 184 | slider_resolution_kwargs = preprocessor.slider_resolution.gradio_update_kwargs.copy() 185 | 186 | if pp: 187 | slider_resolution_kwargs['visible'] = False 188 | 189 | grs = [ 190 | gr.update(**slider_resolution_kwargs), 191 | gr.update(**preprocessor.slider_1.gradio_update_kwargs.copy()), 192 | gr.update(**preprocessor.slider_2.gradio_update_kwargs.copy()), 193 | gr.update(visible=True), 194 | ] 195 | 196 | return grs 197 | inputs = [ 198 | self.module, 199 | self.pixel_perfect, 200 | ] 201 | outputs = [ 202 | self.processor_res, 203 | self.threshold_a, 204 | self.threshold_b, 205 | self.advanced, 206 | ] 207 | self.module.change( 208 | build_sliders, inputs=inputs, outputs=outputs, show_progress=False 209 | ) 210 | 211 | self.pixel_perfect.change( 212 | build_sliders, inputs=inputs, outputs=outputs, show_progress=False 213 | ) 214 | 215 | 216 | 217 | def process(self, pp: scripts_postprocessing.PostprocessedImage, **args): 218 | if args == {} or args['enable'] == False: 219 | return 220 | 221 | w, h = pp.image.size 222 | image = convertIntoCNImageFromat(pp.image) 223 | 224 | if args['pixel_perfect']: 225 | processor_res = getPixelPerfectResolution( 226 | image, 227 | target_H=h, 228 | target_W=w, 229 | ) 230 | else: 231 | processor_res = args['processor_res'] 232 | 233 | if IS_WEBUI_FORGE: 234 | module = global_state.get_preprocessor(args['module']) 235 | else: 236 | module = supported_preprocessor.Preprocessor.get_preprocessor(args['module']) 237 | 238 | detected_map = module( 239 | input_image=image, 240 | resolution=processor_res, 241 | slider_1=args['threshold_a'], 242 | slider_2=args['threshold_b'], 243 | ) 244 | 245 | pp.image = convertImageIntoPILFormat(detected_map) 246 | info = copy.copy(args) 247 | del info['enable'] 248 | if info['pixel_perfect']: 249 | del info['processor_res'] 250 | pp.info[NAME] = str(info) 251 | --------------------------------------------------------------------------------